@cmmd-center/forge 0.13.1 → 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 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.1";
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.1";
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
- /** The failure activities `appendProviderFailureActivity` emits, all `provider.*.failed`. */
96208
- function isProviderFailureActivity(activity) {
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" || !("detail" in payload)) return null;
96214
- const detail = payload.detail;
96215
- return typeof detail === "string" && detail.trim() ? detail.trim() : null;
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 — only `tool.`
96221
- * activities were serialized — so durable history could not show one and every
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 rather than a new kind because both the Forge
96228
- * protocol and CMMD's ingest schema already accept it; nothing here needed a
96229
- * contract change. The detail carries the actionable sentence, so it is joined
96230
- * onto the summary — the event has no separate detail field.
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 serializeProviderFailureActivity(activity) {
96233
- if (!isProviderFailureActivity(activity)) return null;
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 = serializeProviderFailureActivity(activity);
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
- function isAppliedClaudeSnapshot(snapshot) {
101138
- return snapshot.connected === true && snapshot.deliveryStatus === "applied" && snapshot.nativeExecutable === true && typeof snapshot.generationId === "string" && snapshot.generationId.trim().length > 0;
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
- function classifyUnappliedClaudeSnapshot(snapshot) {
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 (!isAppliedClaudeSnapshot(snapshot)) return classifyUnappliedClaudeSnapshot(snapshot);
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 "9243995fc67ca68aa610fabccbf48bba35a5ffd1";
295312
+ return "1881e36c16315be6322e1a109dd4a331b484b963";
295278
295313
  }
295279
295314
  async function resolveServerBuildCommit(input) {
295280
295315
  if (isFullCommitSha(input.baked)) return input.baked;