@cmmd-center/forge 0.13.70 → 0.13.71

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
@@ -66275,7 +66275,7 @@ function normalizeNumberish(value) {
66275
66275
  }
66276
66276
  //#endregion
66277
66277
  //#region package.json
66278
- var version$1 = "0.13.70";
66278
+ var version$1 = "0.13.71";
66279
66279
  //#endregion
66280
66280
  //#region src/sentry.ts
66281
66281
  const SERVER_APP_NAME = "forge-server";
@@ -67235,6 +67235,7 @@ const runtimeReplicationPublicationAttemptsTotal = require_Schema$1.counter("t3_
67235
67235
  const runtimeReplicationPublicationRetriesTotal = require_Schema$1.counter("t3_runtime_replication_publication_retries_total", { description: "Total runtime replication retries scheduled after recoverable failures." });
67236
67236
  const runtimeReplicationPublicationLatency = require_Schema$1.timer("t3_runtime_replication_publication_latency", { description: "Time from durable enqueue to canonical CMMD acknowledgement." });
67237
67237
  const cmmdCredentialRefreshTotal = require_Schema$1.counter("t3_cmmd_credential_refresh_total", { description: "CMMD JWT refresh attempts for a Forge session, by rung (self-refresh | grant-redemption) and outcome (attempted | succeeded)." });
67238
+ const cmmdTokenMintTotal = require_Schema$1.counter("t3_cmmd_token_mint_total", { description: "CMMD short-lived forge-token mints through the mint lane (forge#3518), by outcome (attempted | succeeded). The legacy rotation ladder keeps reporting t3_cmmd_credential_refresh_total, so the two lanes stay separable in every dashboard." });
67238
67239
  const cmmdRotationFenceAppliedTotal = require_Schema$1.counter("t3_cmmd_rotation_fence_applied_total", { description: "Sessions Forge refused to rotate CMMD credentials for, because the rotation fence applied." });
67239
67240
  const cmmdCredentialRowDeletedTotal = require_Schema$1.counter("t3_cmmd_credential_row_deleted_total", { description: "command_access_tokens rows actually deleted by clearUnrecoverableCmmdAuthForSession, by reason." });
67240
67241
  const metricAttributes = (attributes) => Object.entries(compactMetricAttributes(attributes));
@@ -71265,6 +71266,63 @@ function parseCmmdOAuthRefreshResponse(value) {
71265
71266
  };
71266
71267
  }
71267
71268
  /**
71269
+ * Read the one field the mint lane consumes from CMMD's mint response.
71270
+ * Returns null unless `access_token` is present and non-empty.
71271
+ */
71272
+ function parseCmmdTokenMintResponse(value) {
71273
+ if (!value || typeof value !== "object") return null;
71274
+ const accessToken = value["access_token"];
71275
+ if (typeof accessToken !== "string" || accessToken.trim().length === 0) return null;
71276
+ return { accessToken: accessToken.trim() };
71277
+ }
71278
+ /**
71279
+ * How long the mint may take before the lane gives up on it. Minting is one
71280
+ * grant lookup and one signature on CMMD's side; anything slower is treated
71281
+ * like a failure and the resolver falls through to the legacy ladder.
71282
+ */
71283
+ const MINT_TIMEOUT_MS = 1e4;
71284
+ /**
71285
+ * Mint a short-lived forge-scoped access token from a durable session grant
71286
+ * (forge#3518 expand phase).
71287
+ *
71288
+ * The grant is non-rotating and redeemable on demand, so this is the mint
71289
+ * lane's entire refresh story: call it again when the short-lived token
71290
+ * expires. CMMD answers with `access_token` only (no refresh token, no MCP
71291
+ * PAT, nothing durable), so a session that lives entirely on this lane never
71292
+ * accumulates long-lived credential state on Forge's side.
71293
+ *
71294
+ * Returns null for every failure because the resolver wants a value, not a
71295
+ * thrown error; the failure modes are logged by status and OAuth error code
71296
+ * the same way `refreshCmmdJwt` logs them. A rejection here is never fed to
71297
+ * the credential-rejection ledger: the ledger exists to prove a session's
71298
+ * *rotation* credential is dead, and the grant dying says nothing about the
71299
+ * refresh token the legacy fallback still owns.
71300
+ */
71301
+ function mintCmmdShortLivedToken(grantToken) {
71302
+ return fetch(new URL("/api/mcp-server/oauth/token", resolveCommandAppUrl()).toString(), {
71303
+ method: "POST",
71304
+ headers: { "Content-Type": "application/json" },
71305
+ body: JSON.stringify({
71306
+ grant_type: "forge_token_mint",
71307
+ grant_token: grantToken,
71308
+ client_id: "cmmd-forge-web"
71309
+ }),
71310
+ signal: AbortSignal.timeout(MINT_TIMEOUT_MS)
71311
+ }).then(async (response) => {
71312
+ if (!response.ok) {
71313
+ const errorCode = await readOAuthErrorCode(response);
71314
+ console.warn(`[cmmd-auth] CMMD refused a forge token mint with ${response.status}${errorCode === null ? "" : ` (${errorCode})`}`);
71315
+ return null;
71316
+ }
71317
+ const parsed = parseCmmdTokenMintResponse(await response.json());
71318
+ if (!parsed) console.warn("[cmmd-auth] CMMD accepted a forge token mint but returned no usable access token");
71319
+ return parsed;
71320
+ }).catch((cause) => {
71321
+ console.warn(`[cmmd-auth] CMMD forge token mint could not be completed: ${String(cause)}`);
71322
+ return null;
71323
+ });
71324
+ }
71325
+ /**
71268
71326
  * Redeem a durable CMMD session grant for a fresh credential set.
71269
71327
  *
71270
71328
  * The grant is CMMD's designed recovery path for exactly the case a rotating
@@ -71355,6 +71413,42 @@ async function readOAuthErrorCode(response) {
71355
71413
  }
71356
71414
  }
71357
71415
  //#endregion
71416
+ //#region src/command/cmmdTokenMintLane.ts
71417
+ /**
71418
+ * The CMMD token-mint lane (forge#3518, expand phase).
71419
+ *
71420
+ * A new FIRST rung beside the legacy credential ladder: instead of rotating
71421
+ * CMMD's single-use refresh tokens, the resolver mints a short-lived
71422
+ * forge-scoped access token from the session's durable, non-rotating session
71423
+ * grant (`grant_type: forge_token_mint` on CMMD's token endpoint). The mint
71424
+ * spends nothing that cannot be spent again, so it cannot race itself into
71425
+ * `invalid_grant` the way a rotation can.
71426
+ *
71427
+ * Behind a config flag that is OFF unless explicitly enabled: production
71428
+ * keeps the legacy ladder as the only lane until this is switched on
71429
+ * deliberately. When enabled, the lane is tried before the legacy ladder;
71430
+ * every legacy rung (rotation fence, self-refresh, grant redemption,
71431
+ * bootstrap promotion, subject fallback) still exists below it unchanged and
71432
+ * still guards itself with the rejection ledger.
71433
+ */
71434
+ /** Env var that enables the mint lane. Unset and anything but an affirmative value is off. */
71435
+ const CMMD_TOKEN_MINT_LANE_ENV = "FORGE_CMMD_TOKEN_MINT_LANE";
71436
+ const AFFIRMATIVE_VALUES = new Set([
71437
+ "1",
71438
+ "on",
71439
+ "true",
71440
+ "yes"
71441
+ ]);
71442
+ /**
71443
+ * Whether the mint lane may run. Default off everywhere: a missing env var is
71444
+ * the production posture, and an opt-in flag must not half-default to on in
71445
+ * any environment a misconfigured deploy might land in.
71446
+ */
71447
+ function isCmmdTokenMintLaneEnabled(env = process.env) {
71448
+ const raw = env[CMMD_TOKEN_MINT_LANE_ENV]?.trim().toLowerCase();
71449
+ return raw !== void 0 && AFFIRMATIVE_VALUES.has(raw);
71450
+ }
71451
+ //#endregion
71358
71452
  //#region src/command/cmmdRotationFence.ts
71359
71453
  /**
71360
71454
  * Log and count a fence application, without changing the verdict it reports.
@@ -71443,12 +71537,13 @@ function usableCmmdUserJwt(token) {
71443
71537
  function storeResolvedJwt(sessionId, accessToken, refreshToken) {
71444
71538
  return setCommandJwtForSession(sessionId, accessToken, refreshToken ? { refreshToken } : {}).pipe(require_Schema$1.ignore({ log: true }));
71445
71539
  }
71446
- function logCmmdJwtRung(context, rung, outcome, reason) {
71540
+ function logCmmdJwtRung(context, rung, outcome, reason, lane = "legacy") {
71447
71541
  return (outcome === "failed" ? require_Schema$1.logWarning : require_Schema$1.logInfo)("CMMD JWT resolver rung.", {
71448
71542
  sessionId: context.sessionId,
71449
71543
  subject: context.subject,
71450
71544
  deviceType: context.deviceType,
71451
71545
  caller: context.caller,
71546
+ lane,
71452
71547
  rung,
71453
71548
  outcome,
71454
71549
  reason
@@ -71461,6 +71556,25 @@ function recordCmmdCredentialRefreshMetric(rung, outcome) {
71461
71556
  })), 1);
71462
71557
  }
71463
71558
  const inFlightRefreshes = /* @__PURE__ */ new Map();
71559
+ /** In-flight mints, keyed by session, same shape as `inFlightRefreshes`. */
71560
+ const inFlightMints = /* @__PURE__ */ new Map();
71561
+ /**
71562
+ * Collapse concurrent mints for one session into a single upstream call. A
71563
+ * mint cannot race itself into `invalid_grant` (the grant is non-rotating),
71564
+ * so this is call-rate hygiene, not correctness: two tabs observing the same
71565
+ * expiry must not both hit CMMD when one mint would do.
71566
+ */
71567
+ function mintOnceForSession(sessionId, grantToken) {
71568
+ return require_Schema$1.promise(() => {
71569
+ const existing = inFlightMints.get(sessionId);
71570
+ if (existing) return existing;
71571
+ const pending = mintCmmdShortLivedToken(grantToken).catch(() => null).finally(() => {
71572
+ inFlightMints.delete(sessionId);
71573
+ });
71574
+ inFlightMints.set(sessionId, pending);
71575
+ return pending;
71576
+ });
71577
+ }
71464
71578
  function refreshOnceForSession(sessionId, refreshToken) {
71465
71579
  return require_Schema$1.promise(() => {
71466
71580
  const existing = inFlightRefreshes.get(sessionId);
@@ -71500,6 +71614,39 @@ function trySelfRefresh(sessionId, context) {
71500
71614
  });
71501
71615
  }
71502
71616
  /**
71617
+ * The mint lane's rung (forge#3518, expand phase): mint a short-lived
71618
+ * forge-scoped access token from this session's durable session grant.
71619
+ *
71620
+ * Tried FIRST, before the rotation fence and the whole legacy ladder, when
71621
+ * `FORGE_CMMD_TOKEN_MINT_LANE` is on. Minting spends nothing that cannot be
71622
+ * spent again (the grant is non-rotating), so unlike the legacy rungs it is
71623
+ * safe for a session the rotation fence fences off, and it never feeds the
71624
+ * credential-rejection ledger: this rung failing says nothing about whether
71625
+ * the legacy rungs below it can still succeed.
71626
+ *
71627
+ * Only the JWT is persisted (`setCommandJwtForSession`, no refresh token):
71628
+ * the short lifetime is the expiry story, and re-minting is the refresh
71629
+ * story. A session that lives entirely on this lane accumulates no new
71630
+ * long-lived credential state beyond the grant it already had.
71631
+ */
71632
+ function tryMintShortLivedToken(sessionId, context) {
71633
+ const grantToken = readCommandSessionGrantForSession(sessionId);
71634
+ if (!grantToken) return logCmmdJwtRung(context, "mint", "skipped", "no session grant stored for this session", "mint").pipe(require_Schema$1.as(null));
71635
+ return require_Schema$1.gen(function* () {
71636
+ yield* logCmmdJwtRung(context, "mint", "attempted", "minting a short-lived forge-scoped token from this session's grant", "mint");
71637
+ yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "attempted" })), 1);
71638
+ const accessToken = usableCmmdUserJwt((yield* mintOnceForSession(sessionId, grantToken))?.accessToken);
71639
+ if (!accessToken) {
71640
+ yield* logCmmdJwtRung(context, "mint", "failed", "CMMD token mint did not return a usable user access JWT", "mint");
71641
+ return null;
71642
+ }
71643
+ yield* setCommandJwtForSession(sessionId, accessToken, {}).pipe(require_Schema$1.ignore({ log: true }));
71644
+ yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "succeeded" })), 1);
71645
+ yield* logCmmdJwtRung(context, "mint", "succeeded", "CMMD minted a short-lived forge-scoped token", "mint");
71646
+ return accessToken;
71647
+ });
71648
+ }
71649
+ /**
71503
71650
  * Last-resort recovery: redeem this session's durable CMMD session grant.
71504
71651
  *
71505
71652
  * A refresh token is single-use, so a session that lost a rotation — raced, or
@@ -71619,8 +71766,10 @@ function runRecoveryLadder(sessionId, normalizedSubject, forceRefresh, context)
71619
71766
  * Resolve the CMMD REST JWT for a Forge session.
71620
71767
  *
71621
71768
  * Forge session auth is long-lived compared with the short-lived CMMD JWT.
71622
- * When the session JWT expires, recover by promoting a valid subject-level JWT
71623
- * or rotating the stored CMMD refresh token.
71769
+ * When the session JWT expires, recover by minting a fresh short-lived token
71770
+ * from the session grant (the mint lane; default-off, forge#3518), or, on
71771
+ * the legacy lane, by promoting a valid subject-level JWT or rotating the
71772
+ * stored CMMD refresh token.
71624
71773
  *
71625
71774
  * Fail-closed: never returns a non-user / malformed Bearer. Callers must map
71626
71775
  * null to CMMD_AUTH_REQUIRED and must not call CMMD upstream with a bad token.
@@ -71640,7 +71789,8 @@ function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth",
71640
71789
  })));
71641
71790
  }
71642
71791
  const normalizedSubject = subject?.trim() || null;
71643
- if (!normalizedSubject && !forceRefresh) return require_Schema$1.succeed(null);
71792
+ const mintLaneEnabled = isCmmdTokenMintLaneEnabled();
71793
+ if (!normalizedSubject && !forceRefresh && !mintLaneEnabled) return require_Schema$1.succeed(null);
71644
71794
  return require_Schema$1.gen(function* () {
71645
71795
  const context = {
71646
71796
  sessionId,
@@ -71648,6 +71798,14 @@ function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth",
71648
71798
  deviceType: yield* readClientDeviceTypeForSession(sessionId),
71649
71799
  caller: logPrefix
71650
71800
  };
71801
+ if (mintLaneEnabled) {
71802
+ const minted = yield* tryMintShortLivedToken(sessionId, context);
71803
+ if (minted) return minted;
71804
+ if (!normalizedSubject && !forceRefresh) {
71805
+ yield* logCmmdJwtRung(context, "mint", "failed", "no subject to resolve and the mint lane produced nothing", "mint");
71806
+ return null;
71807
+ }
71808
+ }
71651
71809
  if (yield* isRotationFencedSession(sessionId)) {
71652
71810
  yield* logCmmdJwtRung(context, "rotation-fence", "failed", "session is fenced from rotating CMMD credentials");
71653
71811
  return null;
@@ -298491,7 +298649,7 @@ function resolveBuildCommitFromEnv(env) {
298491
298649
  * environment descriptor down instead of reporting an honest "unknown".
298492
298650
  */
298493
298651
  function readBakedBuildCommit() {
298494
- return "1f99504d8e9ac5a370ea5685016f9bda4160da76";
298652
+ return "8e9fcb0318612dd80655f7c9662dd9128b8f005c";
298495
298653
  }
298496
298654
  async function resolveServerBuildCommit(input) {
298497
298655
  if (isFullCommitSha(input.baked)) return input.baked;
package/dist/bin.mjs CHANGED
@@ -65979,7 +65979,7 @@ function normalizeNumberish(value) {
65979
65979
  }
65980
65980
  //#endregion
65981
65981
  //#region package.json
65982
- var version$1 = "0.13.70";
65982
+ var version$1 = "0.13.71";
65983
65983
  //#endregion
65984
65984
  //#region src/sentry.ts
65985
65985
  const SERVER_APP_NAME = "forge-server";
@@ -66939,6 +66939,7 @@ const runtimeReplicationPublicationAttemptsTotal = counter("t3_runtime_replicati
66939
66939
  const runtimeReplicationPublicationRetriesTotal = counter("t3_runtime_replication_publication_retries_total", { description: "Total runtime replication retries scheduled after recoverable failures." });
66940
66940
  const runtimeReplicationPublicationLatency = timer("t3_runtime_replication_publication_latency", { description: "Time from durable enqueue to canonical CMMD acknowledgement." });
66941
66941
  const cmmdCredentialRefreshTotal = counter("t3_cmmd_credential_refresh_total", { description: "CMMD JWT refresh attempts for a Forge session, by rung (self-refresh | grant-redemption) and outcome (attempted | succeeded)." });
66942
+ const cmmdTokenMintTotal = counter("t3_cmmd_token_mint_total", { description: "CMMD short-lived forge-token mints through the mint lane (forge#3518), by outcome (attempted | succeeded). The legacy rotation ladder keeps reporting t3_cmmd_credential_refresh_total, so the two lanes stay separable in every dashboard." });
66942
66943
  const cmmdRotationFenceAppliedTotal = counter("t3_cmmd_rotation_fence_applied_total", { description: "Sessions Forge refused to rotate CMMD credentials for, because the rotation fence applied." });
66943
66944
  const cmmdCredentialRowDeletedTotal = counter("t3_cmmd_credential_row_deleted_total", { description: "command_access_tokens rows actually deleted by clearUnrecoverableCmmdAuthForSession, by reason." });
66944
66945
  const metricAttributes = (attributes) => Object.entries(compactMetricAttributes(attributes));
@@ -70944,6 +70945,63 @@ function parseCmmdOAuthRefreshResponse(value) {
70944
70945
  };
70945
70946
  }
70946
70947
  /**
70948
+ * Read the one field the mint lane consumes from CMMD's mint response.
70949
+ * Returns null unless `access_token` is present and non-empty.
70950
+ */
70951
+ function parseCmmdTokenMintResponse(value) {
70952
+ if (!value || typeof value !== "object") return null;
70953
+ const accessToken = value["access_token"];
70954
+ if (typeof accessToken !== "string" || accessToken.trim().length === 0) return null;
70955
+ return { accessToken: accessToken.trim() };
70956
+ }
70957
+ /**
70958
+ * How long the mint may take before the lane gives up on it. Minting is one
70959
+ * grant lookup and one signature on CMMD's side; anything slower is treated
70960
+ * like a failure and the resolver falls through to the legacy ladder.
70961
+ */
70962
+ const MINT_TIMEOUT_MS = 1e4;
70963
+ /**
70964
+ * Mint a short-lived forge-scoped access token from a durable session grant
70965
+ * (forge#3518 expand phase).
70966
+ *
70967
+ * The grant is non-rotating and redeemable on demand, so this is the mint
70968
+ * lane's entire refresh story: call it again when the short-lived token
70969
+ * expires. CMMD answers with `access_token` only (no refresh token, no MCP
70970
+ * PAT, nothing durable), so a session that lives entirely on this lane never
70971
+ * accumulates long-lived credential state on Forge's side.
70972
+ *
70973
+ * Returns null for every failure because the resolver wants a value, not a
70974
+ * thrown error; the failure modes are logged by status and OAuth error code
70975
+ * the same way `refreshCmmdJwt` logs them. A rejection here is never fed to
70976
+ * the credential-rejection ledger: the ledger exists to prove a session's
70977
+ * *rotation* credential is dead, and the grant dying says nothing about the
70978
+ * refresh token the legacy fallback still owns.
70979
+ */
70980
+ function mintCmmdShortLivedToken(grantToken) {
70981
+ return fetch(new URL("/api/mcp-server/oauth/token", resolveCommandAppUrl()).toString(), {
70982
+ method: "POST",
70983
+ headers: { "Content-Type": "application/json" },
70984
+ body: JSON.stringify({
70985
+ grant_type: "forge_token_mint",
70986
+ grant_token: grantToken,
70987
+ client_id: "cmmd-forge-web"
70988
+ }),
70989
+ signal: AbortSignal.timeout(MINT_TIMEOUT_MS)
70990
+ }).then(async (response) => {
70991
+ if (!response.ok) {
70992
+ const errorCode = await readOAuthErrorCode(response);
70993
+ console.warn(`[cmmd-auth] CMMD refused a forge token mint with ${response.status}${errorCode === null ? "" : ` (${errorCode})`}`);
70994
+ return null;
70995
+ }
70996
+ const parsed = parseCmmdTokenMintResponse(await response.json());
70997
+ if (!parsed) console.warn("[cmmd-auth] CMMD accepted a forge token mint but returned no usable access token");
70998
+ return parsed;
70999
+ }).catch((cause) => {
71000
+ console.warn(`[cmmd-auth] CMMD forge token mint could not be completed: ${String(cause)}`);
71001
+ return null;
71002
+ });
71003
+ }
71004
+ /**
70947
71005
  * Redeem a durable CMMD session grant for a fresh credential set.
70948
71006
  *
70949
71007
  * The grant is CMMD's designed recovery path for exactly the case a rotating
@@ -71034,6 +71092,42 @@ async function readOAuthErrorCode(response) {
71034
71092
  }
71035
71093
  }
71036
71094
  //#endregion
71095
+ //#region src/command/cmmdTokenMintLane.ts
71096
+ /**
71097
+ * The CMMD token-mint lane (forge#3518, expand phase).
71098
+ *
71099
+ * A new FIRST rung beside the legacy credential ladder: instead of rotating
71100
+ * CMMD's single-use refresh tokens, the resolver mints a short-lived
71101
+ * forge-scoped access token from the session's durable, non-rotating session
71102
+ * grant (`grant_type: forge_token_mint` on CMMD's token endpoint). The mint
71103
+ * spends nothing that cannot be spent again, so it cannot race itself into
71104
+ * `invalid_grant` the way a rotation can.
71105
+ *
71106
+ * Behind a config flag that is OFF unless explicitly enabled: production
71107
+ * keeps the legacy ladder as the only lane until this is switched on
71108
+ * deliberately. When enabled, the lane is tried before the legacy ladder;
71109
+ * every legacy rung (rotation fence, self-refresh, grant redemption,
71110
+ * bootstrap promotion, subject fallback) still exists below it unchanged and
71111
+ * still guards itself with the rejection ledger.
71112
+ */
71113
+ /** Env var that enables the mint lane. Unset and anything but an affirmative value is off. */
71114
+ const CMMD_TOKEN_MINT_LANE_ENV = "FORGE_CMMD_TOKEN_MINT_LANE";
71115
+ const AFFIRMATIVE_VALUES = new Set([
71116
+ "1",
71117
+ "on",
71118
+ "true",
71119
+ "yes"
71120
+ ]);
71121
+ /**
71122
+ * Whether the mint lane may run. Default off everywhere: a missing env var is
71123
+ * the production posture, and an opt-in flag must not half-default to on in
71124
+ * any environment a misconfigured deploy might land in.
71125
+ */
71126
+ function isCmmdTokenMintLaneEnabled(env = process.env) {
71127
+ const raw = env[CMMD_TOKEN_MINT_LANE_ENV]?.trim().toLowerCase();
71128
+ return raw !== void 0 && AFFIRMATIVE_VALUES.has(raw);
71129
+ }
71130
+ //#endregion
71037
71131
  //#region src/command/cmmdRotationFence.ts
71038
71132
  /**
71039
71133
  * Log and count a fence application, without changing the verdict it reports.
@@ -71122,12 +71216,13 @@ function usableCmmdUserJwt(token) {
71122
71216
  function storeResolvedJwt(sessionId, accessToken, refreshToken) {
71123
71217
  return setCommandJwtForSession(sessionId, accessToken, refreshToken ? { refreshToken } : {}).pipe(ignore({ log: true }));
71124
71218
  }
71125
- function logCmmdJwtRung(context, rung, outcome, reason) {
71219
+ function logCmmdJwtRung(context, rung, outcome, reason, lane = "legacy") {
71126
71220
  return (outcome === "failed" ? logWarning$1 : logInfo)("CMMD JWT resolver rung.", {
71127
71221
  sessionId: context.sessionId,
71128
71222
  subject: context.subject,
71129
71223
  deviceType: context.deviceType,
71130
71224
  caller: context.caller,
71225
+ lane,
71131
71226
  rung,
71132
71227
  outcome,
71133
71228
  reason
@@ -71140,6 +71235,25 @@ function recordCmmdCredentialRefreshMetric(rung, outcome) {
71140
71235
  })), 1);
71141
71236
  }
71142
71237
  const inFlightRefreshes = /* @__PURE__ */ new Map();
71238
+ /** In-flight mints, keyed by session, same shape as `inFlightRefreshes`. */
71239
+ const inFlightMints = /* @__PURE__ */ new Map();
71240
+ /**
71241
+ * Collapse concurrent mints for one session into a single upstream call. A
71242
+ * mint cannot race itself into `invalid_grant` (the grant is non-rotating),
71243
+ * so this is call-rate hygiene, not correctness: two tabs observing the same
71244
+ * expiry must not both hit CMMD when one mint would do.
71245
+ */
71246
+ function mintOnceForSession(sessionId, grantToken) {
71247
+ return promise(() => {
71248
+ const existing = inFlightMints.get(sessionId);
71249
+ if (existing) return existing;
71250
+ const pending = mintCmmdShortLivedToken(grantToken).catch(() => null).finally(() => {
71251
+ inFlightMints.delete(sessionId);
71252
+ });
71253
+ inFlightMints.set(sessionId, pending);
71254
+ return pending;
71255
+ });
71256
+ }
71143
71257
  function refreshOnceForSession(sessionId, refreshToken) {
71144
71258
  return promise(() => {
71145
71259
  const existing = inFlightRefreshes.get(sessionId);
@@ -71179,6 +71293,39 @@ function trySelfRefresh(sessionId, context) {
71179
71293
  });
71180
71294
  }
71181
71295
  /**
71296
+ * The mint lane's rung (forge#3518, expand phase): mint a short-lived
71297
+ * forge-scoped access token from this session's durable session grant.
71298
+ *
71299
+ * Tried FIRST, before the rotation fence and the whole legacy ladder, when
71300
+ * `FORGE_CMMD_TOKEN_MINT_LANE` is on. Minting spends nothing that cannot be
71301
+ * spent again (the grant is non-rotating), so unlike the legacy rungs it is
71302
+ * safe for a session the rotation fence fences off, and it never feeds the
71303
+ * credential-rejection ledger: this rung failing says nothing about whether
71304
+ * the legacy rungs below it can still succeed.
71305
+ *
71306
+ * Only the JWT is persisted (`setCommandJwtForSession`, no refresh token):
71307
+ * the short lifetime is the expiry story, and re-minting is the refresh
71308
+ * story. A session that lives entirely on this lane accumulates no new
71309
+ * long-lived credential state beyond the grant it already had.
71310
+ */
71311
+ function tryMintShortLivedToken(sessionId, context) {
71312
+ const grantToken = readCommandSessionGrantForSession(sessionId);
71313
+ if (!grantToken) return logCmmdJwtRung(context, "mint", "skipped", "no session grant stored for this session", "mint").pipe(as$3(null));
71314
+ return gen(function* () {
71315
+ yield* logCmmdJwtRung(context, "mint", "attempted", "minting a short-lived forge-scoped token from this session's grant", "mint");
71316
+ yield* update$3(withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "attempted" })), 1);
71317
+ const accessToken = usableCmmdUserJwt((yield* mintOnceForSession(sessionId, grantToken))?.accessToken);
71318
+ if (!accessToken) {
71319
+ yield* logCmmdJwtRung(context, "mint", "failed", "CMMD token mint did not return a usable user access JWT", "mint");
71320
+ return null;
71321
+ }
71322
+ yield* setCommandJwtForSession(sessionId, accessToken, {}).pipe(ignore({ log: true }));
71323
+ yield* update$3(withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "succeeded" })), 1);
71324
+ yield* logCmmdJwtRung(context, "mint", "succeeded", "CMMD minted a short-lived forge-scoped token", "mint");
71325
+ return accessToken;
71326
+ });
71327
+ }
71328
+ /**
71182
71329
  * Last-resort recovery: redeem this session's durable CMMD session grant.
71183
71330
  *
71184
71331
  * A refresh token is single-use, so a session that lost a rotation — raced, or
@@ -71298,8 +71445,10 @@ function runRecoveryLadder(sessionId, normalizedSubject, forceRefresh, context)
71298
71445
  * Resolve the CMMD REST JWT for a Forge session.
71299
71446
  *
71300
71447
  * Forge session auth is long-lived compared with the short-lived CMMD JWT.
71301
- * When the session JWT expires, recover by promoting a valid subject-level JWT
71302
- * or rotating the stored CMMD refresh token.
71448
+ * When the session JWT expires, recover by minting a fresh short-lived token
71449
+ * from the session grant (the mint lane; default-off, forge#3518), or, on
71450
+ * the legacy lane, by promoting a valid subject-level JWT or rotating the
71451
+ * stored CMMD refresh token.
71303
71452
  *
71304
71453
  * Fail-closed: never returns a non-user / malformed Bearer. Callers must map
71305
71454
  * null to CMMD_AUTH_REQUIRED and must not call CMMD upstream with a bad token.
@@ -71319,7 +71468,8 @@ function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth",
71319
71468
  })));
71320
71469
  }
71321
71470
  const normalizedSubject = subject?.trim() || null;
71322
- if (!normalizedSubject && !forceRefresh) return succeed(null);
71471
+ const mintLaneEnabled = isCmmdTokenMintLaneEnabled();
71472
+ if (!normalizedSubject && !forceRefresh && !mintLaneEnabled) return succeed(null);
71323
71473
  return gen(function* () {
71324
71474
  const context = {
71325
71475
  sessionId,
@@ -71327,6 +71477,14 @@ function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth",
71327
71477
  deviceType: yield* readClientDeviceTypeForSession(sessionId),
71328
71478
  caller: logPrefix
71329
71479
  };
71480
+ if (mintLaneEnabled) {
71481
+ const minted = yield* tryMintShortLivedToken(sessionId, context);
71482
+ if (minted) return minted;
71483
+ if (!normalizedSubject && !forceRefresh) {
71484
+ yield* logCmmdJwtRung(context, "mint", "failed", "no subject to resolve and the mint lane produced nothing", "mint");
71485
+ return null;
71486
+ }
71487
+ }
71330
71488
  if (yield* isRotationFencedSession(sessionId)) {
71331
71489
  yield* logCmmdJwtRung(context, "rotation-fence", "failed", "session is fenced from rotating CMMD credentials");
71332
71490
  return null;
@@ -297936,7 +298094,7 @@ function resolveBuildCommitFromEnv(env) {
297936
298094
  * environment descriptor down instead of reporting an honest "unknown".
297937
298095
  */
297938
298096
  function readBakedBuildCommit() {
297939
- return "1f99504d8e9ac5a370ea5685016f9bda4160da76";
298097
+ return "8e9fcb0318612dd80655f7c9662dd9128b8f005c";
297940
298098
  }
297941
298099
  async function resolveServerBuildCommit(input) {
297942
298100
  if (isFullCommitSha(input.baked)) return input.baked;
@@ -1,4 +1,4 @@
1
- import{r as e}from"./rolldown-runtime-hePW80VL.js";import{Ha as t,Mi as n,Ua as r,Wa as i}from"./Stream-mUTFnSY9.js";import{Al as a,Fn as o,Gc as s,Hr as c,Jc as l,Qr as u,Rr as d,Sr as f,Tn as p,Uc as m,Xr as h,_r as g,d as _,fn as v,fr as y,gr as b,i as x,is as S,jn as C,l as w,m as T,ms as E,n as D,qc as O,qt as k,r as A,u as j,za as M,zr as N}from"./DiffPanelShell-BBd3OHCC.js";import{hn as P}from"./CompositeItem-BsSeqHOM.js";import{a as F,c as I,f as L,i as R,l as z,m as B,n as V,o as H,p as U,r as W,s as G,t as K}from"./Virtualizer-B5hUOqvZ.js";import{Dn as q,at as J,it as ee}from"./src-IM0FnQax.js";import{a as te,c as ne,d as re,h as ie,i as ae,l as oe,m as se,n as ce,o as Y,p as le,r as ue,s as de,t as fe,u as pe}from"./DiffPanel.logic-CtEl6NVr.js";var X=e(i(),1),me=typeof window>`u`?X.useEffect:X.useLayoutEffect;function he({fileDiff:e,options:t,editorOptions:n,lineAnnotations:r,selectedLines:i,prerenderedHTML:a,metrics:o,hasGutterRenderUtility:s,hasCustomHeader:c,disableWorkerPool:l,edit:u}){let d=V(),f=i!==void 0,p=(0,X.useContext)(x),m=j(),h=(0,X.useRef)(null),g=_(n=>{if(n!=null){if(h.current!=null)throw Error(`useFileDiffInstance: An instance should not already exist when a node is created`);d==null?h.current=new re(Z({controlledSelection:f,hasCustomHeader:c,hasGutterRenderUtility:s,options:t}),l?void 0:p,!0):h.current=new pe(Z({controlledSelection:f,hasCustomHeader:c,hasGutterRenderUtility:s,options:t}),d,o,l?void 0:p,!0),h.current.hydrate({fileDiff:e,fileContainer:n,lineAnnotations:r,prerenderedHTML:a})}else{if(h.current==null)throw Error(`useFileDiffInstance: A FileDiff instance should exist when unmounting`);h.current.cleanUp(),h.current=null}});return me(()=>{let{current:n}=h;if(n==null)return;let a=Z({controlledSelection:f,hasCustomHeader:c,hasGutterRenderUtility:s,options:t}),o=a!==void 0&&!k(n.options,a);n.setOptions(a),n.render({forceRender:o,fileDiff:e,lineAnnotations:r}),i!==void 0&&n.setSelectedLines(i)}),me(()=>{if(u&&h.current!=null){if(m===void 0)throw Error(`FileDiff: EditContext is not attached`);let e=m(n??{});if(e==null)throw Error(`FileDiff: EditProvider.createEditor must return an editor instance`);try{return e.edit(h.current)}catch(t){throw e.cleanUp(),t}}},[u]),{ref:g,getHoveredLine:(0,X.useCallback)(()=>h.current?.getHoveredLine(),[])}}function Z({options:e,controlledSelection:t,hasCustomHeader:n,hasGutterRenderUtility:r}){return t||r||n?{...e,controlledSelection:t,renderCustomHeader:n?w:e?.renderCustomHeader,renderGutterUtility:r?w:e?.renderGutterUtility}:e}var Q=r();function ge({fileDiff:e,options:t,editorOptions:n,metrics:r,lineAnnotations:i,selectedLines:a,className:o,style:s,prerenderedHTML:c,renderAnnotation:l,renderCustomHeader:u,renderHeaderPrefix:d,renderHeaderFilenameSuffix:f,renderHeaderMetadata:p,renderGutterUtility:m,disableWorkerPool:h=!1,edit:g=!1}){let{ref:_,getHoveredLine:y}=he({fileDiff:e,options:t,editorOptions:n,metrics:r,lineAnnotations:i,selectedLines:a,prerenderedHTML:c,hasGutterRenderUtility:m!=null,hasCustomHeader:u!=null,disableWorkerPool:h,edit:g});return(0,Q.jsx)(v,{ref:_,className:o,style:s,children:W(oe({fileDiff:e,renderCustomHeader:u,renderHeaderPrefix:d,renderHeaderFilenameSuffix:f,renderHeaderMetadata:p,renderAnnotation:l,renderGutterUtility:m,lineAnnotations:i,getHoveredLine:y}),c)})}var $=t();function _e(e){return e.fromTurnCount===0?q(ee)({threadId:e.threadId,toTurnCount:e.toTurnCount}).pipe(n(e=>({kind:`fullThreadDiff`,input:e}))):q(J)({threadId:e.threadId,fromTurnCount:e.fromTurnCount,toTurnCount:e.toTurnCount}).pipe(n(e=>({kind:`turnDiff`,input:e})))}function ve(e){return e instanceof Error?e.message:typeof e==`string`?e:``}function ye(e){let t=ve(e).trim();if(t.length===0)return`Failed to load checkpoint diff.`;let n=t.toLowerCase();if(n.includes(`not a git repository`))return`Turn diffs are unavailable because this project is not a git repository.`;if(n.includes(`checkpoint unavailable for thread`)||n.includes(`checkpoint invariant violation`)){let e=t.indexOf(`:`);if(e>=0){let n=t.slice(e+1).trim();if(n.length>0)return n}}return t}function be(e){let t=ve(e).toLowerCase();return t.includes(`exceeds current turn count`)||t.includes(`checkpoint is unavailable for turn`)||t.includes(`filesystem checkpoint is unavailable`)}function xe(e){let t=_e(e);return O({queryKey:M.checkpointDiff(e),queryFn:async({signal:n})=>{if(!e.environmentId||!e.threadId||t._tag===`None`)throw Error(`Checkpoint diff is unavailable.`);let r=c(e.environmentId);try{return t.value.kind===`fullThreadDiff`?await r.orchestration.getFullThreadDiff(t.value.input,{signal:n}):await r.orchestration.getTurnDiff(t.value.input,{signal:n})}catch(e){throw Error(ye(e),{cause:e})}},enabled:(e.enabled??!0)&&!!e.environmentId&&!!e.threadId&&t._tag===`Some`,staleTime:1/0,retry:(e,t)=>be(t)?e<12:e<3,retryDelay:(e,t)=>be(t)?Math.min(5e3,250*2**(e-1)):Math.min(1e3,100*2**(e-1))})}function Se(e){if(!(!e.canScrollLeft&&!e.canScrollRight))return{maskImage:`linear-gradient(to right, ${e.canScrollLeft?`transparent 24px, black 72px`:`black`}, ${e.canScrollRight?`black calc(100% - 72px), transparent calc(100% - 24px)`:`black`})`}}function Ce(e){return e instanceof Error?e.message:e?`Failed to load checkpoint diff.`:null}function we(e){return{environmentId:e.environmentId??null,threadId:e.threadId,fromTurnCount:e.range?.fromTurnCount??null,toTurnCount:e.range?.toTurnCount??null,cacheScope:e.selectedTurnId?`turn:${e.selectedTurnId}`:e.conversationCacheScope,enabled:e.enabled}}function Te(){let e=(0,$.c)(8),t=(0,X.useRef)(null),[n,r]=(0,X.useState)(!1),[i,a]=(0,X.useState)(!1),o;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(o=()=>{let e=t.current;if(!e){r(!1),a(!1);return}let n=de({scrollLeft:e.scrollLeft,scrollWidth:e.scrollWidth,clientWidth:e.clientWidth});r(n.canScrollLeft),a(n.canScrollRight)},e[0]=o):o=e[0];let s=o,c;e[1]===Symbol.for(`react.memo_cache_sentinel`)?(c=e=>{t.current?.scrollBy({left:e,behavior:`smooth`})},e[1]=c):c=e[1];let l=c,u;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(u=e=>{let n=t.current;n&&ne({scrollWidth:n.scrollWidth,clientWidth:n.clientWidth,deltaX:e.deltaX,deltaY:e.deltaY})&&(e.preventDefault(),n.scrollBy({left:e.deltaY,behavior:`auto`}))},e[2]=u):u=e[2];let d=u,f,p;e[3]===Symbol.for(`react.memo_cache_sentinel`)?(f=()=>{let e=t.current;if(!e)return;let n=window.requestAnimationFrame(s),r=()=>s();e.addEventListener(`scroll`,r,{passive:!0});let i=new ResizeObserver(()=>s());return i.observe(e),()=>{window.cancelAnimationFrame(n),e.removeEventListener(`scroll`,r),i.disconnect()}},p=[s],e[3]=f,e[4]=p):(f=e[3],p=e[4]),(0,X.useEffect)(f,p);let m;return e[5]!==n||e[6]!==i?(m={ref:t,canScrollLeft:n,canScrollRight:i,scrollBy:l,onWheel:d,remeasure:s},e[5]=n,e[6]=i,e[7]=m):m=e[7],m}function Ee(e,t){let n=(0,$.c)(5),r;n[0]===e.current?r=n[1]:(r=()=>{(e.current?.querySelector(`[data-turn-chip-selected='true']`))?.scrollIntoView({block:`nearest`,inline:`nearest`,behavior:`smooth`})},n[0]=e.current,n[1]=r);let i;n[2]!==e||n[3]!==t?(i=[e,t],n[2]=e,n[3]=t,n[4]=i):i=n[4],(0,X.useEffect)(r,i)}var De=`
1
+ import{r as e}from"./rolldown-runtime-hePW80VL.js";import{Ha as t,Mi as n,Ua as r,Wa as i}from"./Stream-mUTFnSY9.js";import{Al as a,Fn as o,Gc as s,Hr as c,Jc as l,Qr as u,Rr as d,Sr as f,Tn as p,Uc as m,Xr as h,_r as g,d as _,fn as v,fr as y,gr as b,i as x,is as S,jn as C,l as w,m as T,ms as E,n as D,qc as O,qt as k,r as A,u as j,za as M,zr as N}from"./DiffPanelShell-DrmtqFA3.js";import{hn as P}from"./CompositeItem-BsSeqHOM.js";import{a as F,c as I,f as L,i as R,l as z,m as B,n as V,o as H,p as U,r as W,s as G,t as K}from"./Virtualizer-BN2Oq2PR.js";import{Dn as q,at as J,it as ee}from"./src-IM0FnQax.js";import{a as te,c as ne,d as re,h as ie,i as ae,l as oe,m as se,n as ce,o as Y,p as le,r as ue,s as de,t as fe,u as pe}from"./DiffPanel.logic-QEKd4_Au.js";var X=e(i(),1),me=typeof window>`u`?X.useEffect:X.useLayoutEffect;function he({fileDiff:e,options:t,editorOptions:n,lineAnnotations:r,selectedLines:i,prerenderedHTML:a,metrics:o,hasGutterRenderUtility:s,hasCustomHeader:c,disableWorkerPool:l,edit:u}){let d=V(),f=i!==void 0,p=(0,X.useContext)(x),m=j(),h=(0,X.useRef)(null),g=_(n=>{if(n!=null){if(h.current!=null)throw Error(`useFileDiffInstance: An instance should not already exist when a node is created`);d==null?h.current=new re(Z({controlledSelection:f,hasCustomHeader:c,hasGutterRenderUtility:s,options:t}),l?void 0:p,!0):h.current=new pe(Z({controlledSelection:f,hasCustomHeader:c,hasGutterRenderUtility:s,options:t}),d,o,l?void 0:p,!0),h.current.hydrate({fileDiff:e,fileContainer:n,lineAnnotations:r,prerenderedHTML:a})}else{if(h.current==null)throw Error(`useFileDiffInstance: A FileDiff instance should exist when unmounting`);h.current.cleanUp(),h.current=null}});return me(()=>{let{current:n}=h;if(n==null)return;let a=Z({controlledSelection:f,hasCustomHeader:c,hasGutterRenderUtility:s,options:t}),o=a!==void 0&&!k(n.options,a);n.setOptions(a),n.render({forceRender:o,fileDiff:e,lineAnnotations:r}),i!==void 0&&n.setSelectedLines(i)}),me(()=>{if(u&&h.current!=null){if(m===void 0)throw Error(`FileDiff: EditContext is not attached`);let e=m(n??{});if(e==null)throw Error(`FileDiff: EditProvider.createEditor must return an editor instance`);try{return e.edit(h.current)}catch(t){throw e.cleanUp(),t}}},[u]),{ref:g,getHoveredLine:(0,X.useCallback)(()=>h.current?.getHoveredLine(),[])}}function Z({options:e,controlledSelection:t,hasCustomHeader:n,hasGutterRenderUtility:r}){return t||r||n?{...e,controlledSelection:t,renderCustomHeader:n?w:e?.renderCustomHeader,renderGutterUtility:r?w:e?.renderGutterUtility}:e}var Q=r();function ge({fileDiff:e,options:t,editorOptions:n,metrics:r,lineAnnotations:i,selectedLines:a,className:o,style:s,prerenderedHTML:c,renderAnnotation:l,renderCustomHeader:u,renderHeaderPrefix:d,renderHeaderFilenameSuffix:f,renderHeaderMetadata:p,renderGutterUtility:m,disableWorkerPool:h=!1,edit:g=!1}){let{ref:_,getHoveredLine:y}=he({fileDiff:e,options:t,editorOptions:n,metrics:r,lineAnnotations:i,selectedLines:a,prerenderedHTML:c,hasGutterRenderUtility:m!=null,hasCustomHeader:u!=null,disableWorkerPool:h,edit:g});return(0,Q.jsx)(v,{ref:_,className:o,style:s,children:W(oe({fileDiff:e,renderCustomHeader:u,renderHeaderPrefix:d,renderHeaderFilenameSuffix:f,renderHeaderMetadata:p,renderAnnotation:l,renderGutterUtility:m,lineAnnotations:i,getHoveredLine:y}),c)})}var $=t();function _e(e){return e.fromTurnCount===0?q(ee)({threadId:e.threadId,toTurnCount:e.toTurnCount}).pipe(n(e=>({kind:`fullThreadDiff`,input:e}))):q(J)({threadId:e.threadId,fromTurnCount:e.fromTurnCount,toTurnCount:e.toTurnCount}).pipe(n(e=>({kind:`turnDiff`,input:e})))}function ve(e){return e instanceof Error?e.message:typeof e==`string`?e:``}function ye(e){let t=ve(e).trim();if(t.length===0)return`Failed to load checkpoint diff.`;let n=t.toLowerCase();if(n.includes(`not a git repository`))return`Turn diffs are unavailable because this project is not a git repository.`;if(n.includes(`checkpoint unavailable for thread`)||n.includes(`checkpoint invariant violation`)){let e=t.indexOf(`:`);if(e>=0){let n=t.slice(e+1).trim();if(n.length>0)return n}}return t}function be(e){let t=ve(e).toLowerCase();return t.includes(`exceeds current turn count`)||t.includes(`checkpoint is unavailable for turn`)||t.includes(`filesystem checkpoint is unavailable`)}function xe(e){let t=_e(e);return O({queryKey:M.checkpointDiff(e),queryFn:async({signal:n})=>{if(!e.environmentId||!e.threadId||t._tag===`None`)throw Error(`Checkpoint diff is unavailable.`);let r=c(e.environmentId);try{return t.value.kind===`fullThreadDiff`?await r.orchestration.getFullThreadDiff(t.value.input,{signal:n}):await r.orchestration.getTurnDiff(t.value.input,{signal:n})}catch(e){throw Error(ye(e),{cause:e})}},enabled:(e.enabled??!0)&&!!e.environmentId&&!!e.threadId&&t._tag===`Some`,staleTime:1/0,retry:(e,t)=>be(t)?e<12:e<3,retryDelay:(e,t)=>be(t)?Math.min(5e3,250*2**(e-1)):Math.min(1e3,100*2**(e-1))})}function Se(e){if(!(!e.canScrollLeft&&!e.canScrollRight))return{maskImage:`linear-gradient(to right, ${e.canScrollLeft?`transparent 24px, black 72px`:`black`}, ${e.canScrollRight?`black calc(100% - 72px), transparent calc(100% - 24px)`:`black`})`}}function Ce(e){return e instanceof Error?e.message:e?`Failed to load checkpoint diff.`:null}function we(e){return{environmentId:e.environmentId??null,threadId:e.threadId,fromTurnCount:e.range?.fromTurnCount??null,toTurnCount:e.range?.toTurnCount??null,cacheScope:e.selectedTurnId?`turn:${e.selectedTurnId}`:e.conversationCacheScope,enabled:e.enabled}}function Te(){let e=(0,$.c)(8),t=(0,X.useRef)(null),[n,r]=(0,X.useState)(!1),[i,a]=(0,X.useState)(!1),o;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(o=()=>{let e=t.current;if(!e){r(!1),a(!1);return}let n=de({scrollLeft:e.scrollLeft,scrollWidth:e.scrollWidth,clientWidth:e.clientWidth});r(n.canScrollLeft),a(n.canScrollRight)},e[0]=o):o=e[0];let s=o,c;e[1]===Symbol.for(`react.memo_cache_sentinel`)?(c=e=>{t.current?.scrollBy({left:e,behavior:`smooth`})},e[1]=c):c=e[1];let l=c,u;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(u=e=>{let n=t.current;n&&ne({scrollWidth:n.scrollWidth,clientWidth:n.clientWidth,deltaX:e.deltaX,deltaY:e.deltaY})&&(e.preventDefault(),n.scrollBy({left:e.deltaY,behavior:`auto`}))},e[2]=u):u=e[2];let d=u,f,p;e[3]===Symbol.for(`react.memo_cache_sentinel`)?(f=()=>{let e=t.current;if(!e)return;let n=window.requestAnimationFrame(s),r=()=>s();e.addEventListener(`scroll`,r,{passive:!0});let i=new ResizeObserver(()=>s());return i.observe(e),()=>{window.cancelAnimationFrame(n),e.removeEventListener(`scroll`,r),i.disconnect()}},p=[s],e[3]=f,e[4]=p):(f=e[3],p=e[4]),(0,X.useEffect)(f,p);let m;return e[5]!==n||e[6]!==i?(m={ref:t,canScrollLeft:n,canScrollRight:i,scrollBy:l,onWheel:d,remeasure:s},e[5]=n,e[6]=i,e[7]=m):m=e[7],m}function Ee(e,t){let n=(0,$.c)(5),r;n[0]===e.current?r=n[1]:(r=()=>{(e.current?.querySelector(`[data-turn-chip-selected='true']`))?.scrollIntoView({block:`nearest`,inline:`nearest`,behavior:`smooth`})},n[0]=e.current,n[1]=r);let i;n[2]!==e||n[3]!==t?(i=[e,t],n[2]=e,n[3]=t,n[4]=i):i=n[4],(0,X.useEffect)(r,i)}var De=`
2
2
  [data-diffs-header],
3
3
  [data-diff],
4
4
  [data-file],
@@ -61,4 +61,4 @@ import{r as e}from"./rolldown-runtime-hePW80VL.js";import{Ha as t,Mi as n,Ua as
61
61
  text-decoration-color: currentColor;
62
62
  }
63
63
  `;function Oe(e){let t=(0,$.c)(44),{mode:n}=e,r=n===void 0?`inline`:n,{resolvedTheme:i}=b(),a=g(),[c,l]=(0,X.useState)(`stacked`),[u,d]=(0,X.useState)(a.diffWordWrap),h=(0,X.useRef)(null),_=(0,X.useRef)(!1),v=Te(),{activeCwd:y,activeThread:x,bodyState:S,diffOpen:C,inferredCheckpointTurnCountByTurnId:w,orderedTurnDiffSummaries:T,renderableFiles:E,renderablePatch:D,selectedFilePath:O,selectedTurn:k,selectedTurnId:j}=Le(i),M,P;t[0]!==C||t[1]!==a.diffWordWrap?(M=()=>{C&&!_.current&&d(a.diffWordWrap),_.current=C},P=[C,a.diffWordWrap],t[0]=C,t[1]=a.diffWordWrap,t[2]=M,t[3]=P):(M=t[2],P=t[3]),(0,X.useEffect)(M,P);let F;t[4]===O?F=t[5]:(F=()=>{!O||!h.current||Array.from(h.current.querySelectorAll(`[data-diff-file-path]`)).find(e=>e.dataset.diffFilePath===O)?.scrollIntoView({block:`nearest`})},t[4]=O,t[5]=F);let I;t[6]!==E||t[7]!==O?(I=[O,E],t[6]=E,t[7]=O,t[8]=I):I=t[8],(0,X.useEffect)(F,I);let L;t[9]===y?L=t[10]:(L=e=>{let t=f();if(!t)return;let n=y?p(e,y):e;N(t,n).catch(ke)},t[9]=y,t[10]=L);let R=L,z;t[11]===x?z=t[12]:(z=e=>{x&&o.getState().openDiff(s(m(x.environmentId,x.id)),e)},t[11]=x,t[12]=z);let B=z,V;t[13]===x?V=t[14]:(V=()=>{x&&o.getState().openDiff(s(m(x.environmentId,x.id)))},t[13]=x,t[14]=V);let H=V,U;t[15]===v.remeasure?U=t[16]:(U=()=>{let e=window.requestAnimationFrame(v.remeasure);return()=>window.cancelAnimationFrame(e)},t[15]=v.remeasure,t[16]=U);let W;t[17]!==T||t[18]!==j||t[19]!==v.remeasure?(W=[T,j,v.remeasure],t[17]=T,t[18]=j,t[19]=v.remeasure,t[20]=W):W=t[20],(0,X.useEffect)(U,W),Ee(v.ref,k?.turnId??j);let G;t[21]!==c||t[22]!==u||t[23]!==w||t[24]!==T||t[25]!==B||t[26]!==H||t[27]!==k||t[28]!==j||t[29]!==a||t[30]!==v?(G=(0,Q.jsx)(Ae,{diffRenderMode:c,diffWordWrap:u,inferredCheckpointTurnCountByTurnId:w,orderedTurnDiffSummaries:T,selectTurn:B,selectWholeConversation:H,selectedTurn:k,selectedTurnId:j,setDiffRenderMode:l,setDiffWordWrap:d,settings:a,turnStrip:v}),t[21]=c,t[22]=u,t[23]=w,t[24]=T,t[25]=B,t[26]=H,t[27]=k,t[28]=j,t[29]=a,t[30]=v,t[31]=G):G=t[31];let K=G,q;t[32]!==S||t[33]!==c||t[34]!==u||t[35]!==R||t[36]!==E||t[37]!==D||t[38]!==i?(q=(0,Q.jsx)(je,{bodyState:S,diffRenderMode:c,diffWordWrap:u,openDiffFileInEditor:R,patchViewportRef:h,renderableFiles:E,renderablePatch:D,resolvedTheme:i}),t[32]=S,t[33]=c,t[34]=u,t[35]=R,t[36]=E,t[37]=D,t[38]=i,t[39]=q):q=t[39];let J;return t[40]!==K||t[41]!==r||t[42]!==q?(J=(0,Q.jsx)(A,{mode:r,header:K,children:q}),t[40]=K,t[41]=r,t[42]=q,t[43]=J):J=t[43],J}function ke(e){console.warn(`Failed to open diff file in editor.`,e)}function Ae(e){let t=(0,$.c)(59),{diffRenderMode:n,diffWordWrap:r,inferredCheckpointTurnCountByTurnId:i,orderedTurnDiffSummaries:a,selectTurn:o,selectWholeConversation:s,selectedTurn:c,selectedTurnId:l,setDiffRenderMode:u,setDiffWordWrap:d,settings:f,turnStrip:p}=e,m,h;t[0]===p?(m=t[1],h=t[2]):(m=(0,Q.jsx)(Be,{direction:`left`,strip:p}),h=(0,Q.jsx)(Be,{direction:`right`,strip:p}),t[0]=p,t[1]=m,t[2]=h);let g=p.ref,_;t[3]!==p.canScrollLeft||t[4]!==p.canScrollRight?(_=Se({canScrollLeft:p.canScrollLeft,canScrollRight:p.canScrollRight}),t[3]=p.canScrollLeft,t[4]=p.canScrollRight,t[5]=_):_=t[5];let v=p.onWheel,y=l===null,b=l===null?`border-border bg-accent text-accent-foreground`:`border-border/70 bg-background/70 text-muted-foreground/80 hover:border-border hover:text-foreground/80`,x;t[6]===b?x=t[7]:(x=P(`rounded-md border px-2 py-1 text-left transition-colors`,b),t[6]=b,t[7]=x);let S;t[8]===Symbol.for(`react.memo_cache_sentinel`)?(S=(0,Q.jsx)(`div`,{className:`text-[10px] leading-tight font-medium`,children:`All turns`}),t[8]=S):S=t[8];let C;t[9]===x?C=t[10]:(C=(0,Q.jsx)(`div`,{className:x,children:S}),t[9]=x,t[10]=C);let w;t[11]!==s||t[12]!==C||t[13]!==y?(w=(0,Q.jsx)(`button`,{type:`button`,className:`shrink-0 rounded-md`,onClick:s,"data-turn-chip-selected":y,children:C}),t[11]=s,t[12]=C,t[13]=y,t[14]=w):w=t[14];let T;if(t[15]!==i||t[16]!==a||t[17]!==o||t[18]!==c?.turnId||t[19]!==f){let e;t[21]!==i||t[22]!==o||t[23]!==c?.turnId||t[24]!==f?(e=e=>(0,Q.jsx)(Ve,{summary:e,selected:e.turnId===c?.turnId,turnCount:e.checkpointTurnCount??i[e.turnId],timestampFormat:f.timestampFormat,onSelect:o},e.turnId),t[21]=i,t[22]=o,t[23]=c?.turnId,t[24]=f,t[25]=e):e=t[25],T=a.map(e),t[15]=i,t[16]=a,t[17]=o,t[18]=c?.turnId,t[19]=f,t[20]=T}else T=t[20];let E;t[26]!==w||t[27]!==T||t[28]!==_||t[29]!==p.onWheel||t[30]!==p.ref?(E=(0,Q.jsxs)(`div`,{ref:g,className:`turn-chip-strip flex gap-1 overflow-x-auto px-8 py-0.5`,style:_,onWheel:v,children:[w,T]}),t[26]=w,t[27]=T,t[28]=_,t[29]=p.onWheel,t[30]=p.ref,t[31]=E):E=t[31];let D;t[32]!==m||t[33]!==E||t[34]!==h?(D=(0,Q.jsxs)(`div`,{className:`relative min-w-0 flex-1 [-webkit-app-region:no-drag]`,children:[m,h,E]}),t[32]=m,t[33]=E,t[34]=h,t[35]=D):D=t[35];let O;t[36]===n?O=t[37]:(O=[n],t[36]=n,t[37]=O);let k;t[38]===u?k=t[39]:(k=e=>{let t=e[0];(t===`stacked`||t===`split`)&&u(t)},t[38]=u,t[39]=k);let A;t[40]===Symbol.for(`react.memo_cache_sentinel`)?(A=(0,Q.jsx)(R,{"aria-label":`Stacked diff view`,value:`stacked`,children:(0,Q.jsx)(se,{className:`size-3`})}),t[40]=A):A=t[40];let j;t[41]===Symbol.for(`react.memo_cache_sentinel`)?(j=(0,Q.jsx)(R,{"aria-label":`Split diff view`,value:`split`,children:(0,Q.jsx)(ie,{className:`size-3`})}),t[41]=j):j=t[41];let M;t[42]!==O||t[43]!==k?(M=(0,Q.jsxs)(F,{className:`shrink-0`,variant:`outline`,size:`xs`,value:O,onValueChange:k,children:[A,j]}),t[42]=O,t[43]=k,t[44]=M):M=t[44];let N=r?`Disable diff line wrapping`:`Enable diff line wrapping`,I=r?`Disable line wrapping`:`Enable line wrapping`,L;t[45]===d?L=t[46]:(L=e=>{d(!!e)},t[45]=d,t[46]=L);let z;t[47]===Symbol.for(`react.memo_cache_sentinel`)?(z=(0,Q.jsx)(le,{className:`size-3`}),t[47]=z):z=t[47];let B;t[48]!==r||t[49]!==N||t[50]!==I||t[51]!==L?(B=(0,Q.jsx)(R,{"aria-label":N,title:I,variant:`outline`,size:`xs`,pressed:r,onPressedChange:L,children:z}),t[48]=r,t[49]=N,t[50]=I,t[51]=L,t[52]=B):B=t[52];let V;t[53]!==M||t[54]!==B?(V=(0,Q.jsxs)(`div`,{className:`flex shrink-0 items-center gap-1 [-webkit-app-region:no-drag]`,children:[M,B]}),t[53]=M,t[54]=B,t[55]=V):V=t[55];let H;return t[56]!==D||t[57]!==V?(H=(0,Q.jsxs)(Q.Fragment,{children:[D,V]}),t[56]=D,t[57]=V,t[58]=H):H=t[58],H}function je(e){let t=(0,$.c)(18),{bodyState:n,diffRenderMode:r,diffWordWrap:i,openDiffFileInEditor:a,patchViewportRef:o,renderableFiles:s,renderablePatch:c,resolvedTheme:l}=e;if(n.kind===`no-thread`){let e;return t[0]===Symbol.for(`react.memo_cache_sentinel`)?(e=(0,Q.jsx)(Ne,{children:`Select a thread to inspect turn diffs.`}),t[0]=e):e=t[0],e}if(n.kind===`not-git-repo`){let e;return t[1]===Symbol.for(`react.memo_cache_sentinel`)?(e=(0,Q.jsx)(Ne,{children:`Turn diffs are unavailable because this project is not a git repository.`}),t[1]=e):e=t[1],e}if(n.kind===`no-completed-turns`){let e;return t[2]===Symbol.for(`react.memo_cache_sentinel`)?(e=(0,Q.jsx)(Ne,{children:`No completed turns yet.`}),t[2]=e):e=t[2],e}let u;t[3]!==n.error||t[4]!==c?(u=n.error&&!c&&(0,Q.jsx)(`div`,{className:`px-3`,children:(0,Q.jsx)(`p`,{className:`mb-2 text-[11px] text-destructive-foreground`,children:n.error})}),t[3]=n.error,t[4]=c,t[5]=u):u=t[5];let d;t[6]!==n||t[7]!==r||t[8]!==i||t[9]!==a||t[10]!==s||t[11]!==c||t[12]!==l?(d=c?c.kind===`files`?(0,Q.jsx)(K,{className:`diff-render-surface h-full min-h-0 overflow-auto px-2 pb-2`,config:{overscrollSize:600,intersectionObserverMargin:1200},children:s.map(e=>{let t=Y(e),n=`${fe(e)}:${l}`;return(0,Q.jsx)(`div`,{"data-diff-file-path":t,className:`diff-render-file mb-2 rounded-md first:mt-2 last:mb-0`,onClickCapture:e=>{(e.nativeEvent.composedPath?.()??[]).some(Me)&&a(t)},children:(0,Q.jsx)(ge,{fileDiff:e,options:{diffStyle:r===`split`?`split`:`unified`,lineDiffType:`none`,overflow:i?`wrap`:`scroll`,theme:T(l),themeType:l,unsafeCSS:De}})},n)})}):(0,Q.jsx)(`div`,{className:`h-full overflow-auto p-2`,children:(0,Q.jsxs)(`div`,{className:`space-y-2`,children:[(0,Q.jsx)(`p`,{className:`text-[11px] text-muted-foreground/75`,children:c.reason}),(0,Q.jsx)(`pre`,{className:P(`max-h-[72vh] rounded-md border border-border/70 bg-background/70 p-3 font-mono text-[11px] leading-relaxed text-muted-foreground/90`,i?`overflow-auto whitespace-pre-wrap wrap-break-word`:`overflow-auto`),children:c.text})]})}):(0,Q.jsx)(Re,{bodyState:n}),t[6]=n,t[7]=r,t[8]=i,t[9]=a,t[10]=s,t[11]=c,t[12]=l,t[13]=d):d=t[13];let f;return t[14]!==o||t[15]!==u||t[16]!==d?(f=(0,Q.jsx)(Q.Fragment,{children:(0,Q.jsxs)(`div`,{ref:o,className:`diff-panel-viewport min-h-0 min-w-0 flex-1 overflow-hidden`,children:[u,d]})}),t[14]=o,t[15]=u,t[16]=d,t[17]=f):f=t[17],f}function Me(e){return e instanceof Element&&e.hasAttribute(`data-title`)}function Ne(e){let t=(0,$.c)(2),{children:n}=e,r;return t[0]===n?r=t[1]:(r=(0,Q.jsx)(`div`,{className:`flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70`,children:n}),t[0]=n,t[1]=r),r}function Pe(){let e=(0,$.c)(19),t;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(t={strict:!1,select:Ie},e[0]=t):t=e[0];let n=a(t),r;e[1]===Symbol.for(`react.memo_cache_sentinel`)?(r={strict:!1,select:Fe},e[1]=r):r=e[1];let i=B(r),o=i.rightPanel===`diff`,s=n?.threadId??null,c,l;e[2]===n?l=e[3]:(l=C(n),e[2]=n,e[3]=l),c=l;let u=E(c),f=He(u),p=u?.worktreePath,m=f?.cwd,h;e[4]!==p||e[5]!==m?(h=I({threadWorktreePath:p,projectCwd:m}),e[4]=p,e[5]=m,e[6]=h):h=e[6];let g=h,_=u?.environmentId??null,v=g??null,y;e[7]!==_||e[8]!==v?(y={environmentId:_,cwd:v},e[7]=_,e[8]=v,e[9]=y):y=e[9];let b=d(y),x;e[10]===b.data?x=e[11]:(x=z(b.data),e[10]=b.data,e[11]=x);let S=x,w;return e[12]!==g||e[13]!==u||e[14]!==s||e[15]!==o||e[16]!==i||e[17]!==S?(w={activeThread:u,activeThreadId:s,activeCwd:g,isGitRepo:S,diffSearch:i,diffOpen:o},e[12]=g,e[13]=u,e[14]=s,e[15]=o,e[16]=i,e[17]=S,e[18]=w):w=e[18],w}function Fe(e){return G(e)}function Ie(e){return h(e)}function Le(e){let{activeThread:t,activeThreadId:n,activeCwd:r,isGitRepo:i,diffSearch:a,diffOpen:o}=Pe(),{turnDiffSummaries:s,inferredCheckpointTurnCountByTurnId:c}=H(t),u=a.diffTurnId??null,d=u===null?null:a.diffFilePath??null,{activeCheckpointRange:f,conversationCacheScope:p,orderedTurnDiffSummaries:m,selectedTurn:h}=(0,X.useMemo)(()=>te({summaries:s,inferredCheckpointTurnCountByTurnId:c,selectedTurnId:u}),[c,u,s]),g=l(xe(we({environmentId:t?.environmentId,threadId:n,range:f,selectedTurnId:h?.turnId,conversationCacheScope:p,enabled:i}))),_=g.isLoading,v=Ce(g.error),y=g.data?.diff,b=typeof y==`string`&&y.trim().length===0,{emptyDiffMessage:x}=(0,X.useMemo)(()=>ae(h),[h]),S=(0,X.useMemo)(()=>ce(y,`diff-panel:${e}`),[e,y]),C=(0,X.useMemo)(()=>Ue(S),[S]);return{activeCwd:r,activeThread:t,bodyState:ue({hasActiveThread:!!t,isGitRepo:i,turnSummaryCount:m.length,checkpointDiffError:v,hasRenderablePatch:!!S,isLoadingCheckpointDiff:_,hasNoNetChanges:b,emptyDiffMessage:x}),diffOpen:o,inferredCheckpointTurnCountByTurnId:c,orderedTurnDiffSummaries:m,renderableFiles:C,renderablePatch:S,selectedFilePath:d,selectedTurn:h,selectedTurnId:u}}function Re(e){let t=(0,$.c)(4),{bodyState:n}=e;if(n.kind===`loading`){let e;return t[0]===Symbol.for(`react.memo_cache_sentinel`)?(e=(0,Q.jsx)(D,{label:`Loading checkpoint diff...`}),t[0]=e):e=t[0],e}let r;t[1]===Symbol.for(`react.memo_cache_sentinel`)?(r=(0,Q.jsx)(L,{className:`size-7 text-muted-foreground/25`}),t[1]=r):r=t[1];let i=n.kind===`empty`?n.message:`No diff is available for this range.`,a;return t[2]===i?a=t[3]:(a=(0,Q.jsxs)(`div`,{className:`flex h-full flex-col items-center justify-center gap-2 px-5 py-2 text-center`,children:[r,(0,Q.jsx)(`p`,{className:`text-xs text-muted-foreground/70`,children:i})]}),t[2]=i,t[3]=a),a}var ze=180;function Be(e){let t=(0,$.c)(14),{direction:n,strip:r}=e,i=n===`left`,a=i?r.canScrollLeft:r.canScrollRight,o=i?U:u,s=i?`left-0`:`right-0`,c=a?`border-border/70 hover:border-border hover:text-foreground`:`cursor-not-allowed border-border/40 text-muted-foreground/40`,l;t[0]!==s||t[1]!==c?(l=P(`absolute top-1/2 z-20 inline-flex size-6 -translate-y-1/2 items-center justify-center rounded-md border bg-background/90 text-muted-foreground transition-colors`,s,c),t[0]=s,t[1]=c,t[2]=l):l=t[2];let d;t[3]!==i||t[4]!==r?(d=()=>r.scrollBy(i?-180:ze),t[3]=i,t[4]=r,t[5]=d):d=t[5];let f=!a,p=`Scroll turn list ${n}`,m;t[6]===o?m=t[7]:(m=(0,Q.jsx)(o,{className:`size-3.5`}),t[6]=o,t[7]=m);let h;return t[8]!==l||t[9]!==d||t[10]!==f||t[11]!==p||t[12]!==m?(h=(0,Q.jsx)(`button`,{type:`button`,className:l,onClick:d,disabled:f,"aria-label":p,children:m}),t[8]=l,t[9]=d,t[10]=f,t[11]=p,t[12]=m,t[13]=h):h=t[13],h}function Ve(e){let t=(0,$.c)(23),{summary:n,selected:r,turnCount:i,timestampFormat:a,onSelect:o}=e,s;t[0]!==o||t[1]!==n.turnId?(s=()=>o(n.turnId),t[0]=o,t[1]=n.turnId,t[2]=s):s=t[2];let c=n.turnId,l=r?`border-border bg-accent text-accent-foreground`:`border-border/70 bg-background/70 text-muted-foreground/80 hover:border-border hover:text-foreground/80`,u;t[3]===l?u=t[4]:(u=P(`rounded-md border px-2 py-1 text-left transition-colors`,l),t[3]=l,t[4]=u);let d=i??`?`,f;t[5]===d?f=t[6]:(f=(0,Q.jsxs)(`span`,{className:`text-[10px] leading-tight font-medium`,children:[`Turn `,d]}),t[5]=d,t[6]=f);let p;t[7]!==n.completedAt||t[8]!==a?(p=y(n.completedAt,a),t[7]=n.completedAt,t[8]=a,t[9]=p):p=t[9];let m;t[10]===p?m=t[11]:(m=(0,Q.jsx)(`span`,{className:`text-[9px] leading-tight opacity-70`,children:p}),t[10]=p,t[11]=m);let h;t[12]!==f||t[13]!==m?(h=(0,Q.jsxs)(`div`,{className:`flex items-center gap-1`,children:[f,m]}),t[12]=f,t[13]=m,t[14]=h):h=t[14];let g;t[15]!==u||t[16]!==h?(g=(0,Q.jsx)(`div`,{className:u,children:h}),t[15]=u,t[16]=h,t[17]=g):g=t[17];let _;return t[18]!==r||t[19]!==n.turnId||t[20]!==s||t[21]!==g?(_=(0,Q.jsx)(`button`,{type:`button`,className:`shrink-0 rounded-md`,onClick:s,title:c,"data-turn-chip-selected":r,children:g}),t[18]=r,t[19]=n.turnId,t[20]=s,t[21]=g,t[22]=_):_=t[22],_}function He(e){let t=(0,$.c)(3),n=e?.environmentId,r=e?.projectId,i;return t[0]!==n||t[1]!==r?(i=e=>n&&r?S(e,{environmentId:n,projectId:r}):void 0,t[0]=n,t[1]=r,t[2]=i):i=t[2],E(i)}function Ue(e){return!e||e.kind!==`files`?[]:e.files.toSorted((e,t)=>Y(e).localeCompare(Y(t),void 0,{numeric:!0,sensitivity:`base`}))}export{Oe as default};
64
- //# sourceMappingURL=DiffPanel-DILjIwCG.js.map
64
+ //# sourceMappingURL=DiffPanel-7EXLivet.js.map