@cmmd-center/forge 0.13.2 → 0.13.3
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 +133 -98
- package/dist/bin.mjs +133 -98
- package/dist/client/assets/{DiffPanel-Bh9UFe_U.js → DiffPanel-B_HjSs6Q.js} +3 -3
- package/dist/client/assets/{DiffPanel.logic-A3r3u_Vr.js → DiffPanel.logic-_zUBJ9_7.js} +2 -2
- package/dist/client/assets/{DiffPanelShell-B30xoHOs.js → DiffPanelShell-DmjIQbsj.js} +101 -101
- package/dist/client/assets/{DiffWorkerPoolProvider-CYkhfTpq.js → DiffWorkerPoolProvider-CwUTD5yv.js} +2 -2
- package/dist/client/assets/{PullRequestCodePanel-T32DALQE.js → PullRequestCodePanel-CAaS8yoZ.js} +2 -2
- package/dist/client/assets/{Virtualizer-DbDh3RHG.js → diffTargetResolution-IthQ5nAw.js} +2 -2
- package/dist/client/assets/{index-FpNJGy15.js → index-CgwIqrjs.js} +58 -58
- package/dist/client/index.html +4 -4
- package/dist/forge-build.json +1 -1
- package/package.json +1 -1
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.13.
|
|
16547
|
+
const FORGE_RELEASE_VERSION = "0.13.3";
|
|
16548
16548
|
//#endregion
|
|
16549
16549
|
//#region ../../packages/shared/src/hostProcess.ts
|
|
16550
16550
|
/**
|
|
@@ -65864,7 +65864,7 @@ function normalizeNumberish(value) {
|
|
|
65864
65864
|
}
|
|
65865
65865
|
//#endregion
|
|
65866
65866
|
//#region package.json
|
|
65867
|
-
var version$1 = "0.13.
|
|
65867
|
+
var version$1 = "0.13.3";
|
|
65868
65868
|
//#endregion
|
|
65869
65869
|
//#region src/sentry.ts
|
|
65870
65870
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -96204,33 +96204,34 @@ function serializeNativeToolActivity(activity) {
|
|
|
96204
96204
|
}
|
|
96205
96205
|
};
|
|
96206
96206
|
}
|
|
96207
|
-
|
|
96208
|
-
|
|
96209
|
-
return activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
|
|
96207
|
+
function isReplicatedFailureActivity(activity) {
|
|
96208
|
+
return activity.kind === "runtime.error" || activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
|
|
96210
96209
|
}
|
|
96211
96210
|
function failureDetail(activity) {
|
|
96212
96211
|
const payload = activity.payload;
|
|
96213
|
-
if (!payload || typeof payload !== "object"
|
|
96214
|
-
const
|
|
96215
|
-
|
|
96212
|
+
if (!payload || typeof payload !== "object") return null;
|
|
96213
|
+
const record = payload;
|
|
96214
|
+
const detail = typeof record.detail === "string" ? record.detail.trim() : "";
|
|
96215
|
+
if (detail) return detail;
|
|
96216
|
+
return (typeof record.message === "string" ? record.message.trim() : "") || null;
|
|
96216
96217
|
}
|
|
96217
96218
|
/**
|
|
96218
96219
|
* Replicates why a turn failed.
|
|
96219
96220
|
*
|
|
96220
|
-
* Without this a failure never crossed the replication boundary
|
|
96221
|
-
* activities were serialized
|
|
96221
|
+
* Without this a failure never crossed the replication boundary. Only `tool.`
|
|
96222
|
+
* activities were serialized, so durable history could not show one and every
|
|
96222
96223
|
* failure reached the user as an endless "Working" spinner. Measured in
|
|
96223
96224
|
* production on 2026-08-30: zero failure events had EVER been recorded against
|
|
96224
96225
|
* 652 tool_call rows, which is how a workspace-path block, a broker-token
|
|
96225
96226
|
* refusal and a dispatch into a deleted directory all looked identical.
|
|
96226
96227
|
*
|
|
96227
|
-
* `error_summary` is used
|
|
96228
|
-
*
|
|
96229
|
-
*
|
|
96230
|
-
*
|
|
96228
|
+
* `error_summary` is used because both the Forge protocol and CMMD's ingest
|
|
96229
|
+
* schema already accept it. Provider startup failures store `detail`, while
|
|
96230
|
+
* active runtime failures store `message`. The event has no separate detail
|
|
96231
|
+
* field, so either value is joined onto the summary.
|
|
96231
96232
|
*/
|
|
96232
|
-
function
|
|
96233
|
-
if (!
|
|
96233
|
+
function serializeFailureActivity(activity) {
|
|
96234
|
+
if (!isReplicatedFailureActivity(activity)) return null;
|
|
96234
96235
|
const summary = text(activity.summary, 4e3);
|
|
96235
96236
|
if (summary === null) return null;
|
|
96236
96237
|
const detail = failureDetail(activity);
|
|
@@ -96572,7 +96573,7 @@ function isTurnUsageTokens(value) {
|
|
|
96572
96573
|
function serializeRuntimeThreadEvents(thread) {
|
|
96573
96574
|
const messages = thread.messages.flatMap(serializeThreadMessage);
|
|
96574
96575
|
const failureActivities = thread.activities.flatMap((activity) => {
|
|
96575
|
-
const event =
|
|
96576
|
+
const event = serializeFailureActivity(activity);
|
|
96576
96577
|
return event === null ? [] : [event];
|
|
96577
96578
|
});
|
|
96578
96579
|
const replicaActivities = thread.activities.flatMap((activity) => {
|
|
@@ -100840,6 +100841,31 @@ function claudeWriteToken(env = process.env) {
|
|
|
100840
100841
|
return accessor.read(env);
|
|
100841
100842
|
}
|
|
100842
100843
|
//#endregion
|
|
100844
|
+
//#region ../../packages/shared/src/claudeReadinessReason.ts
|
|
100845
|
+
/**
|
|
100846
|
+
* The reason literals Forge acts on, confirmed against CMMD-36441's readiness
|
|
100847
|
+
* service. If the route renames one, the rename lands here as a fall-through
|
|
100848
|
+
* to null, never as the wrong kind.
|
|
100849
|
+
*
|
|
100850
|
+
* Reading the reason is what makes `stale` legible. CMMD-36441 answers
|
|
100851
|
+
* `credential_reconnect_required` on a `stale` answer that still reports
|
|
100852
|
+
* `connected: true`, and the older `credential_generation_not_applied` on a
|
|
100853
|
+
* `stale` answer that simply has not landed yet. Those want opposite
|
|
100854
|
+
* handling, and both used to get "Reconnect Claude in Settings" because the
|
|
100855
|
+
* guard read only the delivery status.
|
|
100856
|
+
*/
|
|
100857
|
+
const CODE_BY_UPSTREAM_REASON$1 = {
|
|
100858
|
+
credential_expired_refreshing: "CREDENTIAL_EXPIRED_REFRESHING",
|
|
100859
|
+
credential_reconnect_required: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
100860
|
+
credential_not_connected: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
100861
|
+
credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED",
|
|
100862
|
+
runtime_unavailable: "RUNTIME_UNAVAILABLE"
|
|
100863
|
+
};
|
|
100864
|
+
function claudeReadinessReasonCode(reason) {
|
|
100865
|
+
if (typeof reason !== "string" || !Object.hasOwn(CODE_BY_UPSTREAM_REASON$1, reason)) return null;
|
|
100866
|
+
return CODE_BY_UPSTREAM_REASON$1[reason] ?? null;
|
|
100867
|
+
}
|
|
100868
|
+
//#endregion
|
|
100843
100869
|
//#region src/egress/providerDeliveryReceipt.ts
|
|
100844
100870
|
const PROVIDER_ACCOUNT_IDENTITY$2 = /^pai_v1_[A-Za-z0-9_-]{43}$/;
|
|
100845
100871
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
@@ -101014,82 +101040,6 @@ function providerDeliveryBindingsEqual(left, right) {
|
|
|
101014
101040
|
return classifyProviderDeliveryBindingChange(left, right) === "unchanged";
|
|
101015
101041
|
}
|
|
101016
101042
|
//#endregion
|
|
101017
|
-
//#region src/provider/claudeReadinessReason.ts
|
|
101018
|
-
/**
|
|
101019
|
-
* The reason literals Forge has copy for, confirmed against CMMD-36441's
|
|
101020
|
-
* readiness service. If the route renames one, the rename lands here as a
|
|
101021
|
-
* fall-through to the generic sentence, never as a wrong sentence.
|
|
101022
|
-
*
|
|
101023
|
-
* Reading the reason is what makes `stale` legible. CMMD-36441 answers
|
|
101024
|
-
* `credential_reconnect_required` on a `stale` answer that still reports
|
|
101025
|
-
* `connected: true`, and the older `credential_generation_not_applied` on a
|
|
101026
|
-
* `stale` answer that simply has not landed yet. Those want opposite
|
|
101027
|
-
* sentences, and both used to get "Reconnect Claude in Settings" because the
|
|
101028
|
-
* guard read only the delivery status.
|
|
101029
|
-
*/
|
|
101030
|
-
const CODE_BY_UPSTREAM_REASON$1 = {
|
|
101031
|
-
credential_expired_refreshing: "CREDENTIAL_EXPIRED_REFRESHING",
|
|
101032
|
-
credential_reconnect_required: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
101033
|
-
credential_not_connected: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
101034
|
-
credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED",
|
|
101035
|
-
runtime_unavailable: "RUNTIME_UNAVAILABLE"
|
|
101036
|
-
};
|
|
101037
|
-
const CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY = "The delivered Claude credential is unavailable or unusable in this runtime.";
|
|
101038
|
-
/**
|
|
101039
|
-
* An ISO 8601 instant, with or without fractional seconds, UTC or offset.
|
|
101040
|
-
* Anchored to nothing on purpose: CMMD wraps the time in a sentence, and the
|
|
101041
|
-
* time is the only part of that sentence Forge repeats.
|
|
101042
|
-
*/
|
|
101043
|
-
const ISO_INSTANT_PATTERN = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
|
|
101044
|
-
function claudeReadinessReasonCode(reason) {
|
|
101045
|
-
if (typeof reason !== "string" || !Object.hasOwn(CODE_BY_UPSTREAM_REASON$1, reason)) return null;
|
|
101046
|
-
return CODE_BY_UPSTREAM_REASON$1[reason] ?? null;
|
|
101047
|
-
}
|
|
101048
|
-
function expiredAtFromReadinessMessage(message) {
|
|
101049
|
-
if (typeof message !== "string") return null;
|
|
101050
|
-
return ISO_INSTANT_PATTERN.exec(message)?.[0] ?? null;
|
|
101051
|
-
}
|
|
101052
|
-
function claudeCredentialExpiredTurnCopy(expiredAt) {
|
|
101053
|
-
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one, so retry this turn in a moment.`;
|
|
101054
|
-
}
|
|
101055
|
-
function claudeCredentialExpiredCardCopy(expiredAt) {
|
|
101056
|
-
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one to this runtime.`;
|
|
101057
|
-
}
|
|
101058
|
-
/**
|
|
101059
|
-
* The last allowlisted readiness reason this process saw.
|
|
101060
|
-
*
|
|
101061
|
-
* A server process serves exactly one runtime, so one slot is the whole store.
|
|
101062
|
-
* It holds a reason code and a timestamp, never credential bytes. The provider
|
|
101063
|
-
* catalog reads a local credential file and cannot tell an expired snapshot
|
|
101064
|
-
* from a blank one, so without this the card can only offer the generic
|
|
101065
|
-
* sentence while the guard already knows better.
|
|
101066
|
-
*/
|
|
101067
|
-
let latestVerdict = null;
|
|
101068
|
-
/**
|
|
101069
|
-
* Remember, or clear, what the readiness answer said.
|
|
101070
|
-
*
|
|
101071
|
-
* An answer carrying no allowlisted reason clears the slot, which is what makes
|
|
101072
|
-
* the card go back to the generic sentence the moment CMMD applies a
|
|
101073
|
-
* credential: an applied answer carries no reason.
|
|
101074
|
-
*/
|
|
101075
|
-
function recordClaudeReadinessVerdict(readiness) {
|
|
101076
|
-
const code = claudeReadinessReasonCode(readiness.reason);
|
|
101077
|
-
latestVerdict = code === null ? null : {
|
|
101078
|
-
code,
|
|
101079
|
-
expiredAt: expiredAtFromReadinessMessage(readiness.lastErrorMessage)
|
|
101080
|
-
};
|
|
101081
|
-
}
|
|
101082
|
-
/**
|
|
101083
|
-
* What the provider status card says about a delivered credential it cannot
|
|
101084
|
-
* use. Names the expiry only when a readiness answer supplied the time; a
|
|
101085
|
-
* guessed time would be worse than the generic sentence.
|
|
101086
|
-
*/
|
|
101087
|
-
function claudeNativeCredentialUnusableMessage() {
|
|
101088
|
-
const verdict = latestVerdict;
|
|
101089
|
-
if (verdict?.code === "CREDENTIAL_EXPIRED_REFRESHING" && verdict.expiredAt !== null) return claudeCredentialExpiredCardCopy(verdict.expiredAt);
|
|
101090
|
-
return CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY;
|
|
101091
|
-
}
|
|
101092
|
-
//#endregion
|
|
101093
101043
|
//#region src/provider/providerReadiness.ts
|
|
101094
101044
|
const GENERATION_HASH_PATTERN$1 = /^[a-f0-9]{64}$/;
|
|
101095
101045
|
const STILL_APPLYING_STATUSES = new Set([
|
|
@@ -101134,10 +101084,18 @@ async function queryProviderReadiness(input) {
|
|
|
101134
101084
|
}
|
|
101135
101085
|
return classifyClaudeSnapshot(snapshot, sprite);
|
|
101136
101086
|
}
|
|
101137
|
-
|
|
101138
|
-
|
|
101087
|
+
/**
|
|
101088
|
+
* Whether CMMD allows a turn to start on this runtime. The delivery status is
|
|
101089
|
+
* not part of it: a refresh that failed over a credential the guest verified
|
|
101090
|
+
* and that has not expired reads as failed with nativeExecutable true, and the
|
|
101091
|
+
* turn is the probe (FOR-3085). The generation fence and the receipt below
|
|
101092
|
+
* still bind the answer.
|
|
101093
|
+
*/
|
|
101094
|
+
function claudeSnapshotAllowsTurn(snapshot) {
|
|
101095
|
+
return snapshot.connected === true && snapshot.nativeExecutable === true && typeof snapshot.generationId === "string" && snapshot.generationId.trim().length > 0;
|
|
101139
101096
|
}
|
|
101140
|
-
|
|
101097
|
+
/** Why a turn may not start, for an answer that does not allow one. */
|
|
101098
|
+
function classifyClaudeTurnRefusal(snapshot) {
|
|
101141
101099
|
const code = claudeReadinessReasonCode(snapshot.reason);
|
|
101142
101100
|
if (code === "RUNTIME_UNAVAILABLE") return {
|
|
101143
101101
|
kind: "not_ready",
|
|
@@ -101165,7 +101123,7 @@ function classifyUnappliedClaudeSnapshot(snapshot) {
|
|
|
101165
101123
|
};
|
|
101166
101124
|
}
|
|
101167
101125
|
function classifyClaudeSnapshot(snapshot, sprite) {
|
|
101168
|
-
if (!
|
|
101126
|
+
if (!claudeSnapshotAllowsTurn(snapshot)) return classifyClaudeTurnRefusal(snapshot);
|
|
101169
101127
|
const generationId = snapshot.generationId.trim();
|
|
101170
101128
|
if ((0, node_crypto.createHash)("sha256").update(generationId).digest("hex") !== sprite.localGenerationHash) return {
|
|
101171
101129
|
kind: "not_ready",
|
|
@@ -211732,6 +211690,83 @@ const makeProviderAuthProvenance = (input) => {
|
|
|
211732
211690
|
};
|
|
211733
211691
|
};
|
|
211734
211692
|
//#endregion
|
|
211693
|
+
//#region src/provider/claudeReadinessReason.ts
|
|
211694
|
+
/**
|
|
211695
|
+
* What Forge says about a delivered Claude credential that expired.
|
|
211696
|
+
*
|
|
211697
|
+
* CMMD's credential authority rotates the refresh token on every refresh, and a
|
|
211698
|
+
* runtime holds a one-time snapshot, so the snapshot's refresh token dies at
|
|
211699
|
+
* the next rotation. Nothing re-delivers on its own, and on 2026-09-04 23:07Z
|
|
211700
|
+
* the turn met a dead credential: the CLI said "OAuth session expired and could
|
|
211701
|
+
* not be refreshed", the thread said Claude was unavailable on this
|
|
211702
|
+
* Environment, and the provider card said the credential was "unavailable or
|
|
211703
|
+
* unusable". Three sentences, none of them the fact, which is that the
|
|
211704
|
+
* credential expired at a known time and a replacement is on its way.
|
|
211705
|
+
*
|
|
211706
|
+
* CMMD-36441 now answers the pre-turn readiness read with a typed reason and
|
|
211707
|
+
* queues a same-generation re-delivery. This module is the one place that
|
|
211708
|
+
* turns those reasons into words.
|
|
211709
|
+
*
|
|
211710
|
+
* Which kind of answer a reason names comes from
|
|
211711
|
+
* `@cmmd-forge/shared/claudeReadinessReason`, which the browser readiness poll
|
|
211712
|
+
* reads too, so the two cannot disagree about a reason (FOR-3085). A reason
|
|
211713
|
+
* that maps to nothing falls through to the generic copy, and every sentence a
|
|
211714
|
+
* person reads is written here rather than forwarded from upstream. Only an
|
|
211715
|
+
* ISO timestamp is ever lifted out of CMMD's message, so no credential detail
|
|
211716
|
+
* in an upstream string can reach a browser.
|
|
211717
|
+
*/
|
|
211718
|
+
const CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY = "The delivered Claude credential is unavailable or unusable in this runtime.";
|
|
211719
|
+
/**
|
|
211720
|
+
* An ISO 8601 instant, with or without fractional seconds, UTC or offset.
|
|
211721
|
+
* Anchored to nothing on purpose: CMMD wraps the time in a sentence, and the
|
|
211722
|
+
* time is the only part of that sentence Forge repeats.
|
|
211723
|
+
*/
|
|
211724
|
+
const ISO_INSTANT_PATTERN = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
|
|
211725
|
+
function expiredAtFromReadinessMessage(message) {
|
|
211726
|
+
if (typeof message !== "string") return null;
|
|
211727
|
+
return ISO_INSTANT_PATTERN.exec(message)?.[0] ?? null;
|
|
211728
|
+
}
|
|
211729
|
+
function claudeCredentialExpiredTurnCopy(expiredAt) {
|
|
211730
|
+
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one, so retry this turn in a moment.`;
|
|
211731
|
+
}
|
|
211732
|
+
function claudeCredentialExpiredCardCopy(expiredAt) {
|
|
211733
|
+
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one to this runtime.`;
|
|
211734
|
+
}
|
|
211735
|
+
/**
|
|
211736
|
+
* The last allowlisted readiness reason this process saw.
|
|
211737
|
+
*
|
|
211738
|
+
* A server process serves exactly one runtime, so one slot is the whole store.
|
|
211739
|
+
* It holds a reason code and a timestamp, never credential bytes. The provider
|
|
211740
|
+
* catalog reads a local credential file and cannot tell an expired snapshot
|
|
211741
|
+
* from a blank one, so without this the card can only offer the generic
|
|
211742
|
+
* sentence while the guard already knows better.
|
|
211743
|
+
*/
|
|
211744
|
+
let latestVerdict = null;
|
|
211745
|
+
/**
|
|
211746
|
+
* Remember, or clear, what the readiness answer said.
|
|
211747
|
+
*
|
|
211748
|
+
* An answer carrying no allowlisted reason clears the slot, which is what makes
|
|
211749
|
+
* the card go back to the generic sentence the moment CMMD applies a
|
|
211750
|
+
* credential: an applied answer carries no reason.
|
|
211751
|
+
*/
|
|
211752
|
+
function recordClaudeReadinessVerdict(readiness) {
|
|
211753
|
+
const code = claudeReadinessReasonCode(readiness.reason);
|
|
211754
|
+
latestVerdict = code === null ? null : {
|
|
211755
|
+
code,
|
|
211756
|
+
expiredAt: expiredAtFromReadinessMessage(readiness.lastErrorMessage)
|
|
211757
|
+
};
|
|
211758
|
+
}
|
|
211759
|
+
/**
|
|
211760
|
+
* What the provider status card says about a delivered credential it cannot
|
|
211761
|
+
* use. Names the expiry only when a readiness answer supplied the time; a
|
|
211762
|
+
* guessed time would be worse than the generic sentence.
|
|
211763
|
+
*/
|
|
211764
|
+
function claudeNativeCredentialUnusableMessage() {
|
|
211765
|
+
const verdict = latestVerdict;
|
|
211766
|
+
if (verdict?.code === "CREDENTIAL_EXPIRED_REFRESHING" && verdict.expiredAt !== null) return claudeCredentialExpiredCardCopy(verdict.expiredAt);
|
|
211767
|
+
return CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY;
|
|
211768
|
+
}
|
|
211769
|
+
//#endregion
|
|
211735
211770
|
//#region src/provider/claudeCredentialStore.ts
|
|
211736
211771
|
/**
|
|
211737
211772
|
* Which Credential Store the Claude CLI uses on this machine, read the way the
|
|
@@ -295274,7 +295309,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
295274
295309
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
295275
295310
|
*/
|
|
295276
295311
|
function readBakedBuildCommit() {
|
|
295277
|
-
return "
|
|
295312
|
+
return "1881e36c16315be6322e1a109dd4a331b484b963";
|
|
295278
295313
|
}
|
|
295279
295314
|
async function resolveServerBuildCommit(input) {
|
|
295280
295315
|
if (isFullCommitSha(input.baked)) return input.baked;
|
package/dist/bin.mjs
CHANGED
|
@@ -16520,7 +16520,7 @@ function makeNetServiceShape(createServer = Net.createServer) {
|
|
|
16520
16520
|
}
|
|
16521
16521
|
//#endregion
|
|
16522
16522
|
//#region ../../packages/shared/src/releaseInfo.generated.ts
|
|
16523
|
-
const FORGE_RELEASE_VERSION = "0.13.
|
|
16523
|
+
const FORGE_RELEASE_VERSION = "0.13.3";
|
|
16524
16524
|
//#endregion
|
|
16525
16525
|
//#region ../../packages/shared/src/hostProcess.ts
|
|
16526
16526
|
/**
|
|
@@ -65582,7 +65582,7 @@ function normalizeNumberish(value) {
|
|
|
65582
65582
|
}
|
|
65583
65583
|
//#endregion
|
|
65584
65584
|
//#region package.json
|
|
65585
|
-
var version$1 = "0.13.
|
|
65585
|
+
var version$1 = "0.13.3";
|
|
65586
65586
|
//#endregion
|
|
65587
65587
|
//#region src/sentry.ts
|
|
65588
65588
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -95829,33 +95829,34 @@ function serializeNativeToolActivity(activity) {
|
|
|
95829
95829
|
}
|
|
95830
95830
|
};
|
|
95831
95831
|
}
|
|
95832
|
-
|
|
95833
|
-
|
|
95834
|
-
return activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
|
|
95832
|
+
function isReplicatedFailureActivity(activity) {
|
|
95833
|
+
return activity.kind === "runtime.error" || activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
|
|
95835
95834
|
}
|
|
95836
95835
|
function failureDetail(activity) {
|
|
95837
95836
|
const payload = activity.payload;
|
|
95838
|
-
if (!payload || typeof payload !== "object"
|
|
95839
|
-
const
|
|
95840
|
-
|
|
95837
|
+
if (!payload || typeof payload !== "object") return null;
|
|
95838
|
+
const record = payload;
|
|
95839
|
+
const detail = typeof record.detail === "string" ? record.detail.trim() : "";
|
|
95840
|
+
if (detail) return detail;
|
|
95841
|
+
return (typeof record.message === "string" ? record.message.trim() : "") || null;
|
|
95841
95842
|
}
|
|
95842
95843
|
/**
|
|
95843
95844
|
* Replicates why a turn failed.
|
|
95844
95845
|
*
|
|
95845
|
-
* Without this a failure never crossed the replication boundary
|
|
95846
|
-
* activities were serialized
|
|
95846
|
+
* Without this a failure never crossed the replication boundary. Only `tool.`
|
|
95847
|
+
* activities were serialized, so durable history could not show one and every
|
|
95847
95848
|
* failure reached the user as an endless "Working" spinner. Measured in
|
|
95848
95849
|
* production on 2026-08-30: zero failure events had EVER been recorded against
|
|
95849
95850
|
* 652 tool_call rows, which is how a workspace-path block, a broker-token
|
|
95850
95851
|
* refusal and a dispatch into a deleted directory all looked identical.
|
|
95851
95852
|
*
|
|
95852
|
-
* `error_summary` is used
|
|
95853
|
-
*
|
|
95854
|
-
*
|
|
95855
|
-
*
|
|
95853
|
+
* `error_summary` is used because both the Forge protocol and CMMD's ingest
|
|
95854
|
+
* schema already accept it. Provider startup failures store `detail`, while
|
|
95855
|
+
* active runtime failures store `message`. The event has no separate detail
|
|
95856
|
+
* field, so either value is joined onto the summary.
|
|
95856
95857
|
*/
|
|
95857
|
-
function
|
|
95858
|
-
if (!
|
|
95858
|
+
function serializeFailureActivity(activity) {
|
|
95859
|
+
if (!isReplicatedFailureActivity(activity)) return null;
|
|
95859
95860
|
const summary = text(activity.summary, 4e3);
|
|
95860
95861
|
if (summary === null) return null;
|
|
95861
95862
|
const detail = failureDetail(activity);
|
|
@@ -96197,7 +96198,7 @@ function isTurnUsageTokens(value) {
|
|
|
96197
96198
|
function serializeRuntimeThreadEvents(thread) {
|
|
96198
96199
|
const messages = thread.messages.flatMap(serializeThreadMessage);
|
|
96199
96200
|
const failureActivities = thread.activities.flatMap((activity) => {
|
|
96200
|
-
const event =
|
|
96201
|
+
const event = serializeFailureActivity(activity);
|
|
96201
96202
|
return event === null ? [] : [event];
|
|
96202
96203
|
});
|
|
96203
96204
|
const replicaActivities = thread.activities.flatMap((activity) => {
|
|
@@ -100459,6 +100460,31 @@ function claudeWriteToken(env = process.env) {
|
|
|
100459
100460
|
return accessor.read(env);
|
|
100460
100461
|
}
|
|
100461
100462
|
//#endregion
|
|
100463
|
+
//#region ../../packages/shared/src/claudeReadinessReason.ts
|
|
100464
|
+
/**
|
|
100465
|
+
* The reason literals Forge acts on, confirmed against CMMD-36441's readiness
|
|
100466
|
+
* service. If the route renames one, the rename lands here as a fall-through
|
|
100467
|
+
* to null, never as the wrong kind.
|
|
100468
|
+
*
|
|
100469
|
+
* Reading the reason is what makes `stale` legible. CMMD-36441 answers
|
|
100470
|
+
* `credential_reconnect_required` on a `stale` answer that still reports
|
|
100471
|
+
* `connected: true`, and the older `credential_generation_not_applied` on a
|
|
100472
|
+
* `stale` answer that simply has not landed yet. Those want opposite
|
|
100473
|
+
* handling, and both used to get "Reconnect Claude in Settings" because the
|
|
100474
|
+
* guard read only the delivery status.
|
|
100475
|
+
*/
|
|
100476
|
+
const CODE_BY_UPSTREAM_REASON$1 = {
|
|
100477
|
+
credential_expired_refreshing: "CREDENTIAL_EXPIRED_REFRESHING",
|
|
100478
|
+
credential_reconnect_required: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
100479
|
+
credential_not_connected: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
100480
|
+
credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED",
|
|
100481
|
+
runtime_unavailable: "RUNTIME_UNAVAILABLE"
|
|
100482
|
+
};
|
|
100483
|
+
function claudeReadinessReasonCode(reason) {
|
|
100484
|
+
if (typeof reason !== "string" || !Object.hasOwn(CODE_BY_UPSTREAM_REASON$1, reason)) return null;
|
|
100485
|
+
return CODE_BY_UPSTREAM_REASON$1[reason] ?? null;
|
|
100486
|
+
}
|
|
100487
|
+
//#endregion
|
|
100462
100488
|
//#region src/egress/providerDeliveryReceipt.ts
|
|
100463
100489
|
const PROVIDER_ACCOUNT_IDENTITY$2 = /^pai_v1_[A-Za-z0-9_-]{43}$/;
|
|
100464
100490
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
@@ -100633,82 +100659,6 @@ function providerDeliveryBindingsEqual(left, right) {
|
|
|
100633
100659
|
return classifyProviderDeliveryBindingChange(left, right) === "unchanged";
|
|
100634
100660
|
}
|
|
100635
100661
|
//#endregion
|
|
100636
|
-
//#region src/provider/claudeReadinessReason.ts
|
|
100637
|
-
/**
|
|
100638
|
-
* The reason literals Forge has copy for, confirmed against CMMD-36441's
|
|
100639
|
-
* readiness service. If the route renames one, the rename lands here as a
|
|
100640
|
-
* fall-through to the generic sentence, never as a wrong sentence.
|
|
100641
|
-
*
|
|
100642
|
-
* Reading the reason is what makes `stale` legible. CMMD-36441 answers
|
|
100643
|
-
* `credential_reconnect_required` on a `stale` answer that still reports
|
|
100644
|
-
* `connected: true`, and the older `credential_generation_not_applied` on a
|
|
100645
|
-
* `stale` answer that simply has not landed yet. Those want opposite
|
|
100646
|
-
* sentences, and both used to get "Reconnect Claude in Settings" because the
|
|
100647
|
-
* guard read only the delivery status.
|
|
100648
|
-
*/
|
|
100649
|
-
const CODE_BY_UPSTREAM_REASON$1 = {
|
|
100650
|
-
credential_expired_refreshing: "CREDENTIAL_EXPIRED_REFRESHING",
|
|
100651
|
-
credential_reconnect_required: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
100652
|
-
credential_not_connected: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
100653
|
-
credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED",
|
|
100654
|
-
runtime_unavailable: "RUNTIME_UNAVAILABLE"
|
|
100655
|
-
};
|
|
100656
|
-
const CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY = "The delivered Claude credential is unavailable or unusable in this runtime.";
|
|
100657
|
-
/**
|
|
100658
|
-
* An ISO 8601 instant, with or without fractional seconds, UTC or offset.
|
|
100659
|
-
* Anchored to nothing on purpose: CMMD wraps the time in a sentence, and the
|
|
100660
|
-
* time is the only part of that sentence Forge repeats.
|
|
100661
|
-
*/
|
|
100662
|
-
const ISO_INSTANT_PATTERN = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
|
|
100663
|
-
function claudeReadinessReasonCode(reason) {
|
|
100664
|
-
if (typeof reason !== "string" || !Object.hasOwn(CODE_BY_UPSTREAM_REASON$1, reason)) return null;
|
|
100665
|
-
return CODE_BY_UPSTREAM_REASON$1[reason] ?? null;
|
|
100666
|
-
}
|
|
100667
|
-
function expiredAtFromReadinessMessage(message) {
|
|
100668
|
-
if (typeof message !== "string") return null;
|
|
100669
|
-
return ISO_INSTANT_PATTERN.exec(message)?.[0] ?? null;
|
|
100670
|
-
}
|
|
100671
|
-
function claudeCredentialExpiredTurnCopy(expiredAt) {
|
|
100672
|
-
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one, so retry this turn in a moment.`;
|
|
100673
|
-
}
|
|
100674
|
-
function claudeCredentialExpiredCardCopy(expiredAt) {
|
|
100675
|
-
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one to this runtime.`;
|
|
100676
|
-
}
|
|
100677
|
-
/**
|
|
100678
|
-
* The last allowlisted readiness reason this process saw.
|
|
100679
|
-
*
|
|
100680
|
-
* A server process serves exactly one runtime, so one slot is the whole store.
|
|
100681
|
-
* It holds a reason code and a timestamp, never credential bytes. The provider
|
|
100682
|
-
* catalog reads a local credential file and cannot tell an expired snapshot
|
|
100683
|
-
* from a blank one, so without this the card can only offer the generic
|
|
100684
|
-
* sentence while the guard already knows better.
|
|
100685
|
-
*/
|
|
100686
|
-
let latestVerdict = null;
|
|
100687
|
-
/**
|
|
100688
|
-
* Remember, or clear, what the readiness answer said.
|
|
100689
|
-
*
|
|
100690
|
-
* An answer carrying no allowlisted reason clears the slot, which is what makes
|
|
100691
|
-
* the card go back to the generic sentence the moment CMMD applies a
|
|
100692
|
-
* credential: an applied answer carries no reason.
|
|
100693
|
-
*/
|
|
100694
|
-
function recordClaudeReadinessVerdict(readiness) {
|
|
100695
|
-
const code = claudeReadinessReasonCode(readiness.reason);
|
|
100696
|
-
latestVerdict = code === null ? null : {
|
|
100697
|
-
code,
|
|
100698
|
-
expiredAt: expiredAtFromReadinessMessage(readiness.lastErrorMessage)
|
|
100699
|
-
};
|
|
100700
|
-
}
|
|
100701
|
-
/**
|
|
100702
|
-
* What the provider status card says about a delivered credential it cannot
|
|
100703
|
-
* use. Names the expiry only when a readiness answer supplied the time; a
|
|
100704
|
-
* guessed time would be worse than the generic sentence.
|
|
100705
|
-
*/
|
|
100706
|
-
function claudeNativeCredentialUnusableMessage() {
|
|
100707
|
-
const verdict = latestVerdict;
|
|
100708
|
-
if (verdict?.code === "CREDENTIAL_EXPIRED_REFRESHING" && verdict.expiredAt !== null) return claudeCredentialExpiredCardCopy(verdict.expiredAt);
|
|
100709
|
-
return CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY;
|
|
100710
|
-
}
|
|
100711
|
-
//#endregion
|
|
100712
100662
|
//#region src/provider/providerReadiness.ts
|
|
100713
100663
|
const GENERATION_HASH_PATTERN$1 = /^[a-f0-9]{64}$/;
|
|
100714
100664
|
const STILL_APPLYING_STATUSES = new Set([
|
|
@@ -100753,10 +100703,18 @@ async function queryProviderReadiness(input) {
|
|
|
100753
100703
|
}
|
|
100754
100704
|
return classifyClaudeSnapshot(snapshot, sprite);
|
|
100755
100705
|
}
|
|
100756
|
-
|
|
100757
|
-
|
|
100706
|
+
/**
|
|
100707
|
+
* Whether CMMD allows a turn to start on this runtime. The delivery status is
|
|
100708
|
+
* not part of it: a refresh that failed over a credential the guest verified
|
|
100709
|
+
* and that has not expired reads as failed with nativeExecutable true, and the
|
|
100710
|
+
* turn is the probe (FOR-3085). The generation fence and the receipt below
|
|
100711
|
+
* still bind the answer.
|
|
100712
|
+
*/
|
|
100713
|
+
function claudeSnapshotAllowsTurn(snapshot) {
|
|
100714
|
+
return snapshot.connected === true && snapshot.nativeExecutable === true && typeof snapshot.generationId === "string" && snapshot.generationId.trim().length > 0;
|
|
100758
100715
|
}
|
|
100759
|
-
|
|
100716
|
+
/** Why a turn may not start, for an answer that does not allow one. */
|
|
100717
|
+
function classifyClaudeTurnRefusal(snapshot) {
|
|
100760
100718
|
const code = claudeReadinessReasonCode(snapshot.reason);
|
|
100761
100719
|
if (code === "RUNTIME_UNAVAILABLE") return {
|
|
100762
100720
|
kind: "not_ready",
|
|
@@ -100784,7 +100742,7 @@ function classifyUnappliedClaudeSnapshot(snapshot) {
|
|
|
100784
100742
|
};
|
|
100785
100743
|
}
|
|
100786
100744
|
function classifyClaudeSnapshot(snapshot, sprite) {
|
|
100787
|
-
if (!
|
|
100745
|
+
if (!claudeSnapshotAllowsTurn(snapshot)) return classifyClaudeTurnRefusal(snapshot);
|
|
100788
100746
|
const generationId = snapshot.generationId.trim();
|
|
100789
100747
|
if (createHash("sha256").update(generationId).digest("hex") !== sprite.localGenerationHash) return {
|
|
100790
100748
|
kind: "not_ready",
|
|
@@ -211320,6 +211278,83 @@ const makeProviderAuthProvenance = (input) => {
|
|
|
211320
211278
|
};
|
|
211321
211279
|
};
|
|
211322
211280
|
//#endregion
|
|
211281
|
+
//#region src/provider/claudeReadinessReason.ts
|
|
211282
|
+
/**
|
|
211283
|
+
* What Forge says about a delivered Claude credential that expired.
|
|
211284
|
+
*
|
|
211285
|
+
* CMMD's credential authority rotates the refresh token on every refresh, and a
|
|
211286
|
+
* runtime holds a one-time snapshot, so the snapshot's refresh token dies at
|
|
211287
|
+
* the next rotation. Nothing re-delivers on its own, and on 2026-09-04 23:07Z
|
|
211288
|
+
* the turn met a dead credential: the CLI said "OAuth session expired and could
|
|
211289
|
+
* not be refreshed", the thread said Claude was unavailable on this
|
|
211290
|
+
* Environment, and the provider card said the credential was "unavailable or
|
|
211291
|
+
* unusable". Three sentences, none of them the fact, which is that the
|
|
211292
|
+
* credential expired at a known time and a replacement is on its way.
|
|
211293
|
+
*
|
|
211294
|
+
* CMMD-36441 now answers the pre-turn readiness read with a typed reason and
|
|
211295
|
+
* queues a same-generation re-delivery. This module is the one place that
|
|
211296
|
+
* turns those reasons into words.
|
|
211297
|
+
*
|
|
211298
|
+
* Which kind of answer a reason names comes from
|
|
211299
|
+
* `@cmmd-forge/shared/claudeReadinessReason`, which the browser readiness poll
|
|
211300
|
+
* reads too, so the two cannot disagree about a reason (FOR-3085). A reason
|
|
211301
|
+
* that maps to nothing falls through to the generic copy, and every sentence a
|
|
211302
|
+
* person reads is written here rather than forwarded from upstream. Only an
|
|
211303
|
+
* ISO timestamp is ever lifted out of CMMD's message, so no credential detail
|
|
211304
|
+
* in an upstream string can reach a browser.
|
|
211305
|
+
*/
|
|
211306
|
+
const CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY = "The delivered Claude credential is unavailable or unusable in this runtime.";
|
|
211307
|
+
/**
|
|
211308
|
+
* An ISO 8601 instant, with or without fractional seconds, UTC or offset.
|
|
211309
|
+
* Anchored to nothing on purpose: CMMD wraps the time in a sentence, and the
|
|
211310
|
+
* time is the only part of that sentence Forge repeats.
|
|
211311
|
+
*/
|
|
211312
|
+
const ISO_INSTANT_PATTERN = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
|
|
211313
|
+
function expiredAtFromReadinessMessage(message) {
|
|
211314
|
+
if (typeof message !== "string") return null;
|
|
211315
|
+
return ISO_INSTANT_PATTERN.exec(message)?.[0] ?? null;
|
|
211316
|
+
}
|
|
211317
|
+
function claudeCredentialExpiredTurnCopy(expiredAt) {
|
|
211318
|
+
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one, so retry this turn in a moment.`;
|
|
211319
|
+
}
|
|
211320
|
+
function claudeCredentialExpiredCardCopy(expiredAt) {
|
|
211321
|
+
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one to this runtime.`;
|
|
211322
|
+
}
|
|
211323
|
+
/**
|
|
211324
|
+
* The last allowlisted readiness reason this process saw.
|
|
211325
|
+
*
|
|
211326
|
+
* A server process serves exactly one runtime, so one slot is the whole store.
|
|
211327
|
+
* It holds a reason code and a timestamp, never credential bytes. The provider
|
|
211328
|
+
* catalog reads a local credential file and cannot tell an expired snapshot
|
|
211329
|
+
* from a blank one, so without this the card can only offer the generic
|
|
211330
|
+
* sentence while the guard already knows better.
|
|
211331
|
+
*/
|
|
211332
|
+
let latestVerdict = null;
|
|
211333
|
+
/**
|
|
211334
|
+
* Remember, or clear, what the readiness answer said.
|
|
211335
|
+
*
|
|
211336
|
+
* An answer carrying no allowlisted reason clears the slot, which is what makes
|
|
211337
|
+
* the card go back to the generic sentence the moment CMMD applies a
|
|
211338
|
+
* credential: an applied answer carries no reason.
|
|
211339
|
+
*/
|
|
211340
|
+
function recordClaudeReadinessVerdict(readiness) {
|
|
211341
|
+
const code = claudeReadinessReasonCode(readiness.reason);
|
|
211342
|
+
latestVerdict = code === null ? null : {
|
|
211343
|
+
code,
|
|
211344
|
+
expiredAt: expiredAtFromReadinessMessage(readiness.lastErrorMessage)
|
|
211345
|
+
};
|
|
211346
|
+
}
|
|
211347
|
+
/**
|
|
211348
|
+
* What the provider status card says about a delivered credential it cannot
|
|
211349
|
+
* use. Names the expiry only when a readiness answer supplied the time; a
|
|
211350
|
+
* guessed time would be worse than the generic sentence.
|
|
211351
|
+
*/
|
|
211352
|
+
function claudeNativeCredentialUnusableMessage() {
|
|
211353
|
+
const verdict = latestVerdict;
|
|
211354
|
+
if (verdict?.code === "CREDENTIAL_EXPIRED_REFRESHING" && verdict.expiredAt !== null) return claudeCredentialExpiredCardCopy(verdict.expiredAt);
|
|
211355
|
+
return CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY;
|
|
211356
|
+
}
|
|
211357
|
+
//#endregion
|
|
211323
211358
|
//#region src/provider/claudeCredentialStore.ts
|
|
211324
211359
|
/**
|
|
211325
211360
|
* Which Credential Store the Claude CLI uses on this machine, read the way the
|
|
@@ -294724,7 +294759,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
294724
294759
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
294725
294760
|
*/
|
|
294726
294761
|
function readBakedBuildCommit() {
|
|
294727
|
-
return "
|
|
294762
|
+
return "1881e36c16315be6322e1a109dd4a331b484b963";
|
|
294728
294763
|
}
|
|
294729
294764
|
async function resolveServerBuildCommit(input) {
|
|
294730
294765
|
if (isFullCommitSha(input.baked)) return input.baked;
|