@cmmd-center/forge 0.13.131 → 0.13.133

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.
Files changed (31) hide show
  1. package/dist/bin.cjs +453 -734
  2. package/dist/bin.mjs +450 -734
  3. package/dist/client/assets/{CanvasPanel-BBU-hosR.js → CanvasPanel-BZ55yFCm.js} +2 -2
  4. package/dist/client/assets/{DiffPanel-CsjpjcNO.js → DiffPanel-RwcR62h1.js} +2 -2
  5. package/dist/client/assets/{DiffPanel.logic-D0tJmmDH.js → DiffPanel.logic-p0v4whSE.js} +2 -2
  6. package/dist/client/assets/{DiffWorkerPoolProvider-BaYfOG54.js → DiffWorkerPoolProvider-DuQCCe4H.js} +2 -2
  7. package/dist/client/assets/{PullRequestCodePanel-FsC6jwvA.js → PullRequestCodePanel-dQ0P1BYv.js} +2 -2
  8. package/dist/client/assets/{ThemeEditorPanel-DHyAZtjN.js → ThemeEditorPanel-Bgrpa0cR.js} +2 -2
  9. package/dist/client/assets/{Virtualizer-Bt_R5UQD.js → Virtualizer-B7qA3afH.js} +2 -2
  10. package/dist/client/assets/{WorkerPoolContext-DSayjMnB.js → WorkerPoolContext-DJIHDFVZ.js} +44 -44
  11. package/dist/client/assets/cmmdAuthRedirect-B_Ad6N4c.js +3 -0
  12. package/dist/client/assets/{cmmdForgeRuntimeListResource-DFLs7Ho_.js → cmmdForgeRuntimeListResource-uAVdZfT3.js} +4 -4
  13. package/dist/client/assets/{currentForgeWorkspaceStore-B5FQv1gc.js → currentForgeWorkspaceStore-DgPGQMqz.js} +2 -2
  14. package/dist/client/assets/{dialog-mwxYKYq-.js → dialog-DMVibvkf.js} +2 -2
  15. package/dist/client/assets/index-BSqrLyPh.js +301 -0
  16. package/dist/client/assets/{popover-BMHhFoom.js → popover-DZpHoNco.js} +2 -2
  17. package/dist/client/assets/{scroll-area-9mmf9iy-.js → scroll-area-dh4WduWB.js} +2 -2
  18. package/dist/client/assets/{sidebarV2CustomSnoozeDialog-vLvqIwRU.js → sidebarV2CustomSnoozeDialog-DsUlSvAq.js} +2 -2
  19. package/dist/client/assets/src-DpAPOFgC.js +3 -0
  20. package/dist/client/assets/{switch-DpGoyzXB.js → switch-Bt0PG_99.js} +2 -2
  21. package/dist/client/assets/{themePalette-BII_gGLK.js → themePalette-rKk-Jh5K.js} +2 -2
  22. package/dist/client/assets/{toggle-CiTk7ztA.js → toggle-CM3FO84q.js} +2 -2
  23. package/dist/client/assets/{toggle-group-B0GQb1vL.js → toggle-group-sUSKwOOz.js} +2 -2
  24. package/dist/client/assets/{tooltip-DlvXFVEM.js → tooltip-DHkQTarv.js} +2 -2
  25. package/dist/client/assets/{utils-BWD4ATf-.js → utils-B9FLzOgF.js} +2 -2
  26. package/dist/client/index.html +18 -18
  27. package/dist/forge-build.json +1 -1
  28. package/package.json +1 -1
  29. package/dist/client/assets/cmmdAuthRedirect-B2FNK6pk.js +0 -3
  30. package/dist/client/assets/index-BFu1LcEj.js +0 -301
  31. package/dist/client/assets/src-DICOHE3E.js +0 -3
package/dist/bin.cjs CHANGED
@@ -20670,6 +20670,43 @@ var FilesystemBrowseError = class extends require_Schema$1.TaggedErrorClass()("F
20670
20670
  }
20671
20671
  };
20672
20672
  //#endregion
20673
+ //#region ../../packages/contracts/src/workspaceDirectory.ts
20674
+ const WORKSPACE_DIRECTORY_PATH_MAX_LENGTH = 512;
20675
+ const WorkspaceDirectoryEntryKind = require_Schema$1.Literals(["file", "directory"]);
20676
+ /**
20677
+ * One immediate child of a listed workspace directory.
20678
+ *
20679
+ * `hasChildren` is what lets the Files tree draw an expander only on
20680
+ * directories that open onto something, without listing the whole workspace
20681
+ * up front: a directory entry is one stat-sized readdir, not a recursive walk.
20682
+ */
20683
+ const WorkspaceDirectoryEntry = require_Schema$1.Struct({
20684
+ name: TrimmedNonEmptyString,
20685
+ kind: WorkspaceDirectoryEntryKind,
20686
+ hasChildren: require_Schema$1.Boolean
20687
+ });
20688
+ const WorkspaceListDirectoryInput = require_Schema$1.Struct({
20689
+ cwd: TrimmedNonEmptyString,
20690
+ relativePath: TrimmedString.check(require_Schema$1.isMaxLength(WORKSPACE_DIRECTORY_PATH_MAX_LENGTH))
20691
+ });
20692
+ const WorkspaceListDirectoryResult = require_Schema$1.Struct({ entries: require_Schema$1.ArraySchema(WorkspaceDirectoryEntry) });
20693
+ const WorkspaceListDirectoryFailure = require_Schema$1.Literals([
20694
+ "workspace_root_invalid",
20695
+ "path_outside_workspace",
20696
+ "read_directory_failed"
20697
+ ]);
20698
+ var WorkspaceListDirectoryError = class extends require_Schema$1.TaggedErrorClass()("WorkspaceListDirectoryError", {
20699
+ cwd: require_Schema$1.String,
20700
+ relativePath: require_Schema$1.String,
20701
+ failure: WorkspaceListDirectoryFailure,
20702
+ detail: require_Schema$1.String,
20703
+ cause: require_Schema$1.optional(require_Schema$1.Defect)
20704
+ }) {
20705
+ get message() {
20706
+ return this.detail;
20707
+ }
20708
+ };
20709
+ //#endregion
20673
20710
  //#region ../../packages/contracts/src/terminal.ts
20674
20711
  const DEFAULT_TERMINAL_ID = "default";
20675
20712
  const TrimmedNonEmptyStringSchema$3 = TrimmedNonEmptyString;
@@ -26292,6 +26329,7 @@ const WS_METHODS = {
26292
26329
  projectsWriteFile: "projects.writeFile",
26293
26330
  projectCheckoutDeletionPreview: "projectCheckoutDeletion.preview",
26294
26331
  projectCheckoutDeletionExecute: "projectCheckoutDeletion.execute",
26332
+ workspaceListDirectory: "workspace.listDirectory",
26295
26333
  workspaceReadFile: "workspace.readFile",
26296
26334
  workspaceBeginWriteFile: "workspace.beginWriteFile",
26297
26335
  workspaceWriteFileChunk: "workspace.writeFileChunk",
@@ -26603,6 +26641,17 @@ const WsWorkspaceCancelWriteFileRpc = make$29(WS_METHODS.workspaceCancelWriteFil
26603
26641
  success: BulkTransferCancelResult,
26604
26642
  error: BulkTransferError
26605
26643
  });
26644
+ /**
26645
+ * One workspace directory's immediate children, for the right panel's Files
26646
+ * tree. Read-scoped and confined to the workspace root on the server; the tree
26647
+ * expands one directory at a time through this method rather than listing the
26648
+ * whole workspace up front.
26649
+ */
26650
+ const WsWorkspaceListDirectoryRpc = make$29(WS_METHODS.workspaceListDirectory, {
26651
+ payload: WorkspaceListDirectoryInput,
26652
+ success: WorkspaceListDirectoryResult,
26653
+ error: WorkspaceListDirectoryError
26654
+ });
26606
26655
  const WsShellOpenInEditorRpc = make$29(WS_METHODS.shellOpenInEditor, {
26607
26656
  payload: OpenInEditorInput,
26608
26657
  error: OpenError$1
@@ -27026,7 +27075,7 @@ const WsRpcGroup = make$28(WsServerGetConfigRpc, WsServerRefreshProvidersRpc, Ws
27026
27075
  payload: UsageSummaryInput,
27027
27076
  success: UsageSummary,
27028
27077
  error: UsageReadError
27029
- }), WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsActivityRpc, WsPullRequestsThreadCommentsRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsProjectsSearchContentsRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsProjectCheckoutDeletionPreviewRpc, WsProjectCheckoutDeletionExecuteRpc, WsWorkspaceReadFileRpc, WsWorkspaceBeginWriteFileRpc, WsWorkspaceWriteFileChunkRpc, WsWorkspaceCommitWriteFileRpc, WsWorkspaceCancelWriteFileRpc, WsShellOpenInEditorRpc, WsSubscribeGitStatusRpc, WsGitPullRpc, WsGitRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsGitListBranchesRpc, WsVcsListRefsRpc, WsVcsRefreshStatusRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsGitCreateWorktreeRpc, WsGitRemoveWorktreeRpc, WsGitCreateBranchRpc, WsGitCheckoutRpc, WsGitInitRpc, WsFilesystemBrowseRpc, WsProjectsReadFileRpc, WsReviewGetDiffPreviewRpc, WsCloudListRuntimesRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalSignalRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsBrowserOpenSessionRpc, WsBrowserListSessionsRpc, WsBrowserCloseSessionRpc, WsBrowserGetSessionRpc, WsBrowserCommandRpc, WsBrowserClearBrowsingDataRpc, WsBrowserAttachDisplayRpc, WsBrowserAddDisplayIceCandidateRpc, WsComputerInputRpc, WsDevTunnelRegisterRpc, WsDevTunnelUnregisterRpc, WsDevTunnelListRpc, WsThreadDevRuntimeGetRpc, WsThreadDevRuntimeAllocatePortRpc, WsThreadDevRuntimeReleaseRpc, WsWorkspaceEnvironmentPreflightRpc, WsWorkspaceEnsureScratchRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsProjectsListEntriesRpc, WsServerDiscoverSourceControlRpc, WsServerReportClientActivityRpc, WsSubscribeBrowserEventsRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsSubscribeWorkspaceReadinessRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationStreamTurnDiffRpc, WsOrchestrationStreamFullThreadDiffRpc, WsOrchestrationReplayEventsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc, make$29(WS_METHODS.assetsCreateUrl, {
27078
+ }), WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsActivityRpc, WsPullRequestsThreadCommentsRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsProjectsSearchContentsRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsProjectCheckoutDeletionPreviewRpc, WsProjectCheckoutDeletionExecuteRpc, WsWorkspaceReadFileRpc, WsWorkspaceListDirectoryRpc, WsWorkspaceBeginWriteFileRpc, WsWorkspaceWriteFileChunkRpc, WsWorkspaceCommitWriteFileRpc, WsWorkspaceCancelWriteFileRpc, WsShellOpenInEditorRpc, WsSubscribeGitStatusRpc, WsGitPullRpc, WsGitRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsGitListBranchesRpc, WsVcsListRefsRpc, WsVcsRefreshStatusRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsGitCreateWorktreeRpc, WsGitRemoveWorktreeRpc, WsGitCreateBranchRpc, WsGitCheckoutRpc, WsGitInitRpc, WsFilesystemBrowseRpc, WsProjectsReadFileRpc, WsReviewGetDiffPreviewRpc, WsCloudListRuntimesRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalSignalRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsBrowserOpenSessionRpc, WsBrowserListSessionsRpc, WsBrowserCloseSessionRpc, WsBrowserGetSessionRpc, WsBrowserCommandRpc, WsBrowserClearBrowsingDataRpc, WsBrowserAttachDisplayRpc, WsBrowserAddDisplayIceCandidateRpc, WsComputerInputRpc, WsDevTunnelRegisterRpc, WsDevTunnelUnregisterRpc, WsDevTunnelListRpc, WsThreadDevRuntimeGetRpc, WsThreadDevRuntimeAllocatePortRpc, WsThreadDevRuntimeReleaseRpc, WsWorkspaceEnvironmentPreflightRpc, WsWorkspaceEnsureScratchRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsProjectsListEntriesRpc, WsServerDiscoverSourceControlRpc, WsServerReportClientActivityRpc, WsSubscribeBrowserEventsRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsSubscribeWorkspaceReadinessRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationStreamTurnDiffRpc, WsOrchestrationStreamFullThreadDiffRpc, WsOrchestrationReplayEventsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc, make$29(WS_METHODS.assetsCreateUrl, {
27030
27079
  payload: AssetsRpcSchemas.createUrl.input,
27031
27080
  success: AssetsRpcSchemas.createUrl.output,
27032
27081
  error: AssetAccessError
@@ -31776,6 +31825,11 @@ const RELAY_METHOD_POLICY_BASES = [
31776
31825
  requiredScope: "workspace.read",
31777
31826
  deliveryClass: "read"
31778
31827
  },
31828
+ {
31829
+ method: WS_METHODS.workspaceListDirectory,
31830
+ requiredScope: "workspace.read",
31831
+ deliveryClass: "read"
31832
+ },
31779
31833
  {
31780
31834
  method: WS_METHODS.workspaceReadFile,
31781
31835
  requiredScope: "workspace.read",
@@ -66353,7 +66407,7 @@ function normalizeNumberish(value) {
66353
66407
  }
66354
66408
  //#endregion
66355
66409
  //#region package.json
66356
- var version$1 = "0.13.131";
66410
+ var version$1 = "0.13.133";
66357
66411
  //#endregion
66358
66412
  //#region src/sentry.ts
66359
66413
  const SERVER_APP_NAME = "forge-server";
@@ -67312,9 +67366,7 @@ const runtimeReplicationOutboxOldestEventAge = require_Schema$1.gauge("t3_runtim
67312
67366
  const runtimeReplicationPublicationAttemptsTotal = require_Schema$1.counter("t3_runtime_replication_publication_attempts_total", { description: "Total canonical runtime replication publication attempts." });
67313
67367
  const runtimeReplicationPublicationRetriesTotal = require_Schema$1.counter("t3_runtime_replication_publication_retries_total", { description: "Total runtime replication retries scheduled after recoverable failures." });
67314
67368
  const runtimeReplicationPublicationLatency = require_Schema$1.timer("t3_runtime_replication_publication_latency", { description: "Time from durable enqueue to canonical CMMD acknowledgement." });
67315
- 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)." });
67316
- 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." });
67317
- 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." });
67369
+ const cmmdTokenMintTotal = require_Schema$1.counter("t3_cmmd_token_mint_total", { description: "CMMD short-lived forge-token mints, by outcome (attempted | succeeded | failed). The mint is the only CMMD JWT refresh story since forge#3521 deleted the legacy rotation ladder." });
67318
67370
  const cmmdCredentialRowDeletedTotal = require_Schema$1.counter("t3_cmmd_credential_row_deleted_total", { description: "command_access_tokens rows actually deleted by clearUnrecoverableCmmdAuthForSession, by reason." });
67319
67371
  const metricAttributes = (attributes) => Object.entries(compactMetricAttributes(attributes));
67320
67372
  const increment = (metric, attributes, amount = 1) => require_Schema$1.update(require_Schema$1.withAttributes(metric, metricAttributes(attributes)), amount);
@@ -67395,12 +67447,10 @@ const isPersistenceError = (u) => require_Schema$1.is(PersistenceSqlError)(u) ||
67395
67447
  const cache = /* @__PURE__ */ new Map();
67396
67448
  const jwtCache = /* @__PURE__ */ new Map();
67397
67449
  const jwtExpiryCache = /* @__PURE__ */ new Map();
67398
- const refreshTokenCache = /* @__PURE__ */ new Map();
67399
67450
  const identityCache = /* @__PURE__ */ new Map();
67400
67451
  const sessionGrantCache = /* @__PURE__ */ new Map();
67401
67452
  const ACCESS_TOKEN_SCOPE = "cmmd-access-token";
67402
67453
  const JWT_TOKEN_SCOPE = "cmmd-jwt-token";
67403
- const REFRESH_TOKEN_SCOPE = "cmmd-refresh-token";
67404
67454
  const IDENTITY_SCOPE = "cmmd-user-identity";
67405
67455
  const SESSION_GRANT_SCOPE = "cmmd-session-grant";
67406
67456
  const CommandAccessTokenRow = require_Schema$1.Struct({
@@ -67408,27 +67458,15 @@ const CommandAccessTokenRow = require_Schema$1.Struct({
67408
67458
  accessToken: require_Schema$1.String,
67409
67459
  jwtToken: require_Schema$1.NullOr(require_Schema$1.String),
67410
67460
  jwtExpiresAt: require_Schema$1.NullOr(require_Schema$1.String),
67411
- refreshToken: require_Schema$1.NullOr(require_Schema$1.String),
67412
67461
  cmmdUserIdentity: require_Schema$1.NullOr(require_Schema$1.String),
67413
67462
  sessionGrant: require_Schema$1.NullOr(require_Schema$1.String)
67414
67463
  });
67415
67464
  const SubjectCommandAccessTokenRow = require_Schema$1.Struct({ accessToken: require_Schema$1.String });
67416
- const SubjectJwtRow = require_Schema$1.Struct({
67417
- accessToken: require_Schema$1.String,
67418
- jwtToken: require_Schema$1.NullOr(require_Schema$1.String),
67419
- jwtExpiresAt: require_Schema$1.NullOr(require_Schema$1.String),
67420
- refreshToken: require_Schema$1.NullOr(require_Schema$1.String),
67421
- sessionId: require_Schema$1.String
67422
- });
67423
- const SubjectRefreshTokenRow = require_Schema$1.Struct({
67424
- refreshToken: require_Schema$1.String,
67425
- sessionId: require_Schema$1.String
67426
- });
67427
67465
  const OwnerCommandAuthBundleRow = require_Schema$1.Struct({
67428
67466
  accessToken: require_Schema$1.String,
67429
67467
  jwtToken: require_Schema$1.NullOr(require_Schema$1.String),
67430
67468
  jwtExpiresAt: require_Schema$1.NullOr(require_Schema$1.String),
67431
- refreshToken: require_Schema$1.NullOr(require_Schema$1.String),
67469
+ sessionGrant: require_Schema$1.NullOr(require_Schema$1.String),
67432
67470
  sessionId: require_Schema$1.String,
67433
67471
  cmmdUserIdentity: require_Schema$1.String
67434
67472
  });
@@ -67473,8 +67511,6 @@ function decryptNullable(scope, value) {
67473
67511
  }
67474
67512
  const decodeTokenRows = require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(CommandAccessTokenRow));
67475
67513
  const decodeSubjectTokenRows = require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(SubjectCommandAccessTokenRow));
67476
- const decodeSubjectJwtRows = require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(SubjectJwtRow));
67477
- require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(SubjectRefreshTokenRow));
67478
67514
  const decodeOwnerCommandAuthBundleRows = require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(OwnerCommandAuthBundleRow));
67479
67515
  const decodeOwnerCommandAuthSessionRows = require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(OwnerCommandAuthSessionRow));
67480
67516
  const decodeCloudOwnerSessionRows = require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(CloudOwnerSessionRow));
@@ -67500,9 +67536,9 @@ function extractJwtExpiresAt(jwtToken) {
67500
67536
  * Comparing against `exp` exactly leaves a window where Forge sends a token it
67501
67537
  * believes is good and CMMD has already stopped accepting it: the clocks differ by a
67502
67538
  * little, the request spends time in flight, and the answer comes back 401 on a
67503
- * session that had a perfectly good refresh token sitting next to it. Two
67504
- * minutes is far inside the JWT's own lifetime, so this costs one early
67505
- * rotation and removes the race.
67539
+ * session whose grant could have minted a good replacement. Two minutes is far
67540
+ * inside the JWT's own lifetime, so this costs one early mint and removes the
67541
+ * race.
67506
67542
  */
67507
67543
  const JWT_EXPIRY_SKEW_MS = 2 * 6e4;
67508
67544
  /**
@@ -67528,7 +67564,6 @@ const hydrateCommandAccessTokenStore = require_Schema$1.gen(function* () {
67528
67564
  access_token AS "accessToken",
67529
67565
  jwt_token AS "jwtToken",
67530
67566
  jwt_expires_at AS "jwtExpiresAt",
67531
- refresh_token AS "refreshToken",
67532
67567
  cmmd_user_identity AS "cmmdUserIdentity",
67533
67568
  session_grant AS "sessionGrant"
67534
67569
  FROM command_access_tokens
@@ -67536,7 +67571,6 @@ const hydrateCommandAccessTokenStore = require_Schema$1.gen(function* () {
67536
67571
  cache.clear();
67537
67572
  jwtCache.clear();
67538
67573
  jwtExpiryCache.clear();
67539
- refreshTokenCache.clear();
67540
67574
  identityCache.clear();
67541
67575
  sessionGrantCache.clear();
67542
67576
  yield* require_Schema$1.try_({
@@ -67547,8 +67581,6 @@ const hydrateCommandAccessTokenStore = require_Schema$1.gen(function* () {
67547
67581
  const jwtToken = decryptNullable(JWT_TOKEN_SCOPE, row.jwtToken);
67548
67582
  if (jwtToken) jwtCache.set(sessionId, jwtToken);
67549
67583
  if (row.jwtExpiresAt) jwtExpiryCache.set(sessionId, row.jwtExpiresAt);
67550
- const refreshToken = decryptNullable(REFRESH_TOKEN_SCOPE, row.refreshToken);
67551
- if (refreshToken) refreshTokenCache.set(sessionId, refreshToken);
67552
67584
  const identity = decodeCmmdUserIdentity(decryptNullable(IDENTITY_SCOPE, row.cmmdUserIdentity));
67553
67585
  if (identity) identityCache.set(sessionId, identity);
67554
67586
  const sessionGrant = decryptNullable(SESSION_GRANT_SCOPE, row.sessionGrant);
@@ -67655,19 +67687,11 @@ function readCommandJwtForSession(sessionId) {
67655
67687
  return jwtCache.get(sessionId) ?? null;
67656
67688
  }
67657
67689
  /**
67658
- * Read the CMMD OAuth refresh token for a session. Sync; hits the in-memory
67659
- * cache only. Used to rotate a REST JWT after CMMD rejects a locally cached JWT
67660
- * that still appears unexpired.
67661
- */
67662
- function readCommandRefreshTokenForSession(sessionId) {
67663
- return refreshTokenCache.get(sessionId) ?? null;
67664
- }
67665
- /**
67666
67690
  * The durable CMMD session grant for a session, if one was issued.
67667
67691
  *
67668
- * Unlike the refresh token this is NON-rotating: redeeming it does not
67669
- * invalidate it, so it stays valid until CMMD expires or revokes it. That is
67670
- * what makes it a safe recovery credential after a rotation is lost.
67692
+ * The grant is NON-rotating: minting from it does not invalidate it, so it
67693
+ * stays valid until CMMD expires or revokes it. That is what makes it a safe
67694
+ * long-lived credential: the short-lived JWT it mints can always be replaced.
67671
67695
  */
67672
67696
  function readCommandSessionGrantForSession(sessionId) {
67673
67697
  return sessionGrantCache.get(sessionId) ?? null;
@@ -67717,77 +67741,26 @@ const setCmmdUserIdentityForSession = (sessionId, identity) => require_Schema$1.
67717
67741
  * `jwt_expires_at` so callers can detect when the token has expired.
67718
67742
  * Updates only the JWT columns; the PAT row must already exist
67719
67743
  * (call `setCommandAccessTokenForSession` first in the OAuth callback).
67744
+ *
67745
+ * The JWT is short-lived by contract (CMMD clamps the mint TTL to one hour);
67746
+ * replacing one is the resolver's mint, so no sibling credential rides along.
67720
67747
  */
67721
- const setCommandJwtForSession = (sessionId, jwtToken, opts = {}) => require_Schema$1.gen(function* () {
67748
+ const setCommandJwtForSession = (sessionId, jwtToken) => require_Schema$1.gen(function* () {
67722
67749
  const token = jwtToken.trim();
67723
67750
  if (token.length === 0) return;
67724
67751
  const expiresAt = extractJwtExpiresAt(token);
67725
- const refreshToken = opts.refreshToken?.trim();
67726
67752
  const storedToken = encodeSensitiveValue(JWT_TOKEN_SCOPE, token);
67727
- const storedRefreshToken = refreshToken ? encodeSensitiveValue(REFRESH_TOKEN_SCOPE, refreshToken) : null;
67728
- const sql = yield* require_SqlError.SqlClient;
67729
- const now = (/* @__PURE__ */ new Date()).toISOString();
67730
- if (refreshToken) yield* sql`
67731
- UPDATE command_access_tokens
67732
- SET
67733
- jwt_token = ${storedToken},
67734
- jwt_expires_at = ${expiresAt},
67735
- refresh_token = ${storedRefreshToken},
67736
- updated_at = ${now}
67737
- WHERE session_id = ${sessionId}
67738
- `.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.setJwt:query")));
67739
- else yield* sql`
67740
- UPDATE command_access_tokens
67741
- SET
67742
- jwt_token = ${storedToken},
67743
- jwt_expires_at = ${expiresAt},
67744
- updated_at = ${now}
67745
- WHERE session_id = ${sessionId}
67746
- `.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.setJwt:query")));
67753
+ yield* (yield* require_SqlError.SqlClient)`
67754
+ UPDATE command_access_tokens
67755
+ SET
67756
+ jwt_token = ${storedToken},
67757
+ jwt_expires_at = ${expiresAt},
67758
+ updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
67759
+ WHERE session_id = ${sessionId}
67760
+ `.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.setJwt:query")));
67747
67761
  jwtCache.set(sessionId, token);
67748
67762
  if (expiresAt) jwtExpiryCache.set(sessionId, expiresAt);
67749
67763
  else jwtExpiryCache.delete(sessionId);
67750
- if (refreshToken) refreshTokenCache.set(sessionId, refreshToken);
67751
- });
67752
- /**
67753
- * Look up the most recent non-expired JWT for an auth subject across active
67754
- * owner sessions. Used as a continuity fallback when the current session ID
67755
- * changed (e.g. after re-authentication) but the user's CMMD JWT should still
67756
- * be usable.
67757
- *
67758
- * Also returns the sessionId that owns the JWT so callers can promote it to
67759
- * the current session, avoiding repeated cross-session lookups.
67760
- */
67761
- const readMostRecentJwtForSubject = (subject) => require_Schema$1.gen(function* () {
67762
- const row = (yield* decodeSubjectJwtRows(yield* (yield* require_SqlError.SqlClient)`
67763
- SELECT
67764
- cat.access_token AS "accessToken",
67765
- cat.jwt_token AS "jwtToken",
67766
- cat.jwt_expires_at AS "jwtExpiresAt",
67767
- cat.refresh_token AS "refreshToken",
67768
- cat.session_id AS "sessionId"
67769
- FROM command_access_tokens AS cat
67770
- JOIN auth_sessions AS s ON s.session_id = cat.session_id
67771
- WHERE s.subject = ${subject}
67772
- AND s.role = 'owner'
67773
- AND s.revoked_at IS NULL
67774
- AND cat.jwt_token IS NOT NULL
67775
- AND cat.jwt_expires_at IS NOT NULL
67776
- AND cat.jwt_expires_at > ${(/* @__PURE__ */ new Date()).toISOString()}
67777
- ORDER BY s.issued_at DESC
67778
- LIMIT 1
67779
- `.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.readMostRecentJwtBySubject:query")))).pipe(require_Schema$1.mapError(toPersistenceDecodeError("CommandAccessTokenStore.readMostRecentJwtBySubject:decodeRows"))))[0];
67780
- if (!row?.jwtToken) return null;
67781
- const jwtToken = decodeSensitiveValue(JWT_TOKEN_SCOPE, row.jwtToken);
67782
- const refreshToken = decryptNullable(REFRESH_TOKEN_SCOPE, row.refreshToken);
67783
- const accessToken = decodeSensitiveValue(ACCESS_TOKEN_SCOPE, row.accessToken);
67784
- return {
67785
- jwt: jwtToken,
67786
- sessionId: row.sessionId,
67787
- expiresAt: row.jwtExpiresAt ?? null,
67788
- refreshToken,
67789
- accessToken
67790
- };
67791
67764
  });
67792
67765
  /**
67793
67766
  * Look up the latest CMMD PAT for an auth subject across active owner sessions.
@@ -67826,7 +67799,7 @@ const readMostRecentCommandAccessTokenForSubject = (subject) => require_Schema$1
67826
67799
  * `desktop-bootstrap` session that minted them, and nothing later rewrites that
67827
67800
  * subject. So the clause matched no row on any machine, promotion always
67828
67801
  * returned null, and a phone whose short-lived JWT had expired could not reach
67829
- * the refresh token sitting on its own previous session. It surfaced as "This
67802
+ * the credentials sitting on its own previous session. It surfaced as "This
67830
67803
  * session is no longer accepted. Sign in again." about a day after signing in,
67831
67804
  * with nothing in the log, because the ladder never got as far as a call that
67832
67805
  * could fail out loud.
@@ -67887,6 +67860,9 @@ const readMostRecentOwnerCommandAuthSession = require_Schema$1.gen(function* ()
67887
67860
  * a CMMD user subject. The CMMD token is still account-owned, so the trusted
67888
67861
  * local owner may inherit the most recent active owner token with an identity.
67889
67862
  * Client pairings must not call this.
67863
+ *
67864
+ * The copy includes the session grant: the promoted session can then mint its
67865
+ * own short-lived JWT, which since forge#3521 is the only refresh story.
67890
67866
  */
67891
67867
  const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema$1.gen(function* () {
67892
67868
  const sql = yield* require_SqlError.SqlClient;
@@ -67895,7 +67871,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema
67895
67871
  cat.access_token AS "accessToken",
67896
67872
  cat.jwt_token AS "jwtToken",
67897
67873
  cat.jwt_expires_at AS "jwtExpiresAt",
67898
- cat.refresh_token AS "refreshToken",
67874
+ cat.session_grant AS "sessionGrant",
67899
67875
  cat.session_id AS "sessionId",
67900
67876
  cat.cmmd_user_identity AS "cmmdUserIdentity"
67901
67877
  FROM command_access_tokens AS cat
@@ -67907,7 +67883,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema
67907
67883
  if (!row) return null;
67908
67884
  const accessToken = decodeSensitiveValue(ACCESS_TOKEN_SCOPE, row.accessToken);
67909
67885
  const jwtToken = decryptNullable(JWT_TOKEN_SCOPE, row.jwtToken);
67910
- const refreshToken = decryptNullable(REFRESH_TOKEN_SCOPE, row.refreshToken);
67886
+ const sessionGrant = decryptNullable(SESSION_GRANT_SCOPE, row.sessionGrant);
67911
67887
  const identity = decodeCmmdUserIdentity(decodeSensitiveValue(IDENTITY_SCOPE, row.cmmdUserIdentity));
67912
67888
  if (!identity) return null;
67913
67889
  const updateNow = (/* @__PURE__ */ new Date()).toISOString();
@@ -67917,7 +67893,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema
67917
67893
  access_token,
67918
67894
  jwt_token,
67919
67895
  jwt_expires_at,
67920
- refresh_token,
67896
+ session_grant,
67921
67897
  cmmd_user_identity,
67922
67898
  created_at,
67923
67899
  updated_at
@@ -67927,7 +67903,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema
67927
67903
  ${row.accessToken},
67928
67904
  ${row.jwtToken},
67929
67905
  ${row.jwtExpiresAt},
67930
- ${row.refreshToken},
67906
+ ${row.sessionGrant},
67931
67907
  ${row.cmmdUserIdentity},
67932
67908
  ${updateNow},
67933
67909
  ${updateNow}
@@ -67936,7 +67912,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema
67936
67912
  access_token = excluded.access_token,
67937
67913
  jwt_token = excluded.jwt_token,
67938
67914
  jwt_expires_at = excluded.jwt_expires_at,
67939
- refresh_token = excluded.refresh_token,
67915
+ session_grant = excluded.session_grant,
67940
67916
  cmmd_user_identity = excluded.cmmd_user_identity,
67941
67917
  updated_at = excluded.updated_at
67942
67918
  `.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.promoteLatestOwnerAuth:upsert")));
@@ -67945,14 +67921,14 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema
67945
67921
  else jwtCache.delete(sessionId);
67946
67922
  if (row.jwtExpiresAt) jwtExpiryCache.set(sessionId, row.jwtExpiresAt);
67947
67923
  else jwtExpiryCache.delete(sessionId);
67948
- if (refreshToken) refreshTokenCache.set(sessionId, refreshToken);
67949
- else refreshTokenCache.delete(sessionId);
67924
+ if (sessionGrant) sessionGrantCache.set(sessionId, sessionGrant);
67925
+ else sessionGrantCache.delete(sessionId);
67950
67926
  identityCache.set(sessionId, identity);
67951
67927
  return {
67952
67928
  accessToken,
67953
67929
  jwtToken,
67954
67930
  jwtExpiresAt: row.jwtExpiresAt ?? null,
67955
- refreshToken,
67931
+ sessionGrant,
67956
67932
  sourceSessionId: row.sessionId,
67957
67933
  cmmdUserIdentity: identity
67958
67934
  };
@@ -67997,11 +67973,11 @@ const clearCommandAccessTokenForSession = (sessionId) => require_Schema$1.gen(fu
67997
67973
  `.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.clear:query")));
67998
67974
  forgetCommandAccessTokenCachesForSession(sessionId);
67999
67975
  });
67976
+ /** Evict a session's credentials, JWT expiry, identity, and grant from memory without changing storage. */
68000
67977
  function forgetCommandAccessTokenCachesForSession(sessionId) {
68001
67978
  cache.delete(sessionId);
68002
67979
  jwtCache.delete(sessionId);
68003
67980
  jwtExpiryCache.delete(sessionId);
68004
- refreshTokenCache.delete(sessionId);
68005
67981
  identityCache.delete(sessionId);
68006
67982
  sessionGrantCache.delete(sessionId);
68007
67983
  }
@@ -68033,14 +68009,14 @@ const scheduleExpiredForgeCloudOwnerCredentialMaintenance = require_Schema$1.sle
68033
68009
  * is dead too, so the promotion sources are cleared with it — otherwise the
68034
68010
  * next promotion resurrects the same dead bundle in a loop.
68035
68011
  * - **Every other session** owns its credentials outright: each OAuth exchange
68036
- * mints a distinct PAT and refresh token (CMMD `createAccountScopedToken` /
68037
- * `issueTokenPair`). One session's dead credential says nothing about a
68012
+ * mints its own PAT, JWT and session grant. One session's dead credential
68013
+ * says nothing about a
68038
68014
  * sibling browser or device, so the clear stays local to that session.
68039
68015
  *
68040
68016
  * Clearing every active owner row unconditionally (the pre-CMMD-1871 behaviour)
68041
68017
  * turned a single expired tab into a sign-out on every browser and device.
68042
68018
  *
68043
- * Destroys a refresh token good for months, so every call is logged with a
68019
+ * Destroys durable credentials (the PAT, the grant), so every call is logged with a
68044
68020
  * `reason` naming the caller's own decision (never the credential itself) and
68045
68021
  * the actual row count deleted, at info level so it shows up without raising
68046
68022
  * the server's log level. `reason` is optional only so a caller that has not
@@ -68081,65 +68057,10 @@ const clearUnrecoverableCmmdAuthForSession = (sessionId, reason) => require_Sche
68081
68057
  }
68082
68058
  });
68083
68059
  /**
68084
- * Retire refresh credentials copied into historical desktop bearer sessions.
68085
- * Electron owns rotation for native desktop OAuth; Forge Local keeps only the
68086
- * derived access authority needed by the current bearer session.
68087
- *
68088
- * Scoped to the bootstrapping client, not to the whole subject. The phone and
68089
- * Electron sign in as the same `user:<id>`, so a subject-wide sweep let one
68090
- * desktop launch null out a phone's refresh token and end its session a day
68091
- * later. Two clauses keep that from happening:
68092
- *
68093
- * - `client_device_type = 'desktop'`, which now excludes phones outright,
68094
- * because the mobile app declares itself at bootstrap.
68095
- * - the same recorded `client_user_agent` as the bootstrap doing the
68096
- * retirement, which is the only thing that separates a phone row written
68097
- * before that stamp existed from a real Electron row. Both are stamped
68098
- * `desktop`; neither carries any other mark. An empty or unmatched user agent
68099
- * retires nothing, which is the direction that keeps a live credential.
68100
- *
68101
- * Electron re-bootstraps under a stable user agent, so its own historical rows
68102
- * still match and still get swept.
68103
- */
68104
- const clearDesktopBearerRefreshTokensForSubject = (input) => require_Schema$1.gen(function* () {
68105
- const normalizedSubject = input.subject.trim();
68106
- const normalizedUserAgent = input.clientUserAgent?.trim() ?? "";
68107
- if (!normalizedSubject || !normalizedUserAgent) return;
68108
- const rows = yield* (yield* require_SqlError.SqlClient)`
68109
- UPDATE command_access_tokens
68110
- SET
68111
- refresh_token = NULL,
68112
- updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
68113
- WHERE session_id IN (
68114
- SELECT session_id
68115
- FROM auth_sessions
68116
- WHERE subject = ${normalizedSubject}
68117
- AND role = 'owner'
68118
- AND method = 'bearer-session-token'
68119
- AND client_device_type = 'desktop'
68120
- AND client_user_agent = ${normalizedUserAgent}
68121
- )
68122
- RETURNING session_id AS "sessionId"
68123
- `.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.clearDesktopBearerRefresh:query")));
68124
- for (const row of rows) refreshTokenCache.delete(row.sessionId);
68125
- });
68126
- const isDesktopBearerAuthSession = (sessionId) => require_Schema$1.gen(function* () {
68127
- const rows = yield* (yield* require_SqlError.SqlClient)`
68128
- SELECT
68129
- method,
68130
- client_device_type AS "clientDeviceType"
68131
- FROM auth_sessions
68132
- WHERE session_id = ${sessionId}
68133
- LIMIT 1
68134
- `.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.isDesktopBearerSession:query")));
68135
- return rows[0]?.method === "bearer-session-token" && rows[0].clientDeviceType === "desktop";
68136
- });
68137
- /**
68138
68060
  * The device type a session's bearer bootstrap declared, for log lines only.
68139
68061
  *
68140
- * Mirrors `isDesktopBearerAuthSession`'s read but returns the raw column
68141
- * instead of collapsing it into a rotation verdict — a log line wants to say
68142
- * what made the request, not whether Forge may rotate for it. Never fails:
68062
+ * Returns the raw column: a log line wants to say what made the request.
68063
+ * Never fails:
68143
68064
  * an unreadable or missing row logs as `"unknown"` rather than losing the
68144
68065
  * decision-point log line it is attached to.
68145
68066
  */
@@ -68192,6 +68113,7 @@ function artifactError(message) {
68192
68113
  cause
68193
68114
  });
68194
68115
  }
68116
+ /** Store credentials under the asserted Cloud owner session and retain its durable mint grant. */
68195
68117
  function persistCloudOwnerArtifacts(input) {
68196
68118
  return require_Schema$1.gen(function* () {
68197
68119
  const sql = yield* require_SqlError.SqlClient;
@@ -68199,7 +68121,7 @@ function persistCloudOwnerArtifacts(input) {
68199
68121
  yield* setCommandAccessTokenForSession(input.assertion.sessionId, input.accessToken).pipe(require_Schema$1.mapError(artifactError("Failed to persist CMMD access token.")));
68200
68122
  yield* setCmmdUserIdentityForSession(input.assertion.sessionId, input.identity).pipe(require_Schema$1.mapError(artifactError("Failed to persist CMMD user identity.")));
68201
68123
  if (input.sessionGrant) yield* setCommandSessionGrantForSession(input.assertion.sessionId, input.sessionGrant, { expiresAt: input.sessionGrantExpiresAt ?? null }).pipe(require_Schema$1.mapError(artifactError("Failed to persist CMMD session grant.")));
68202
- if (input.jwtToken) yield* setCommandJwtForSession(input.assertion.sessionId, input.jwtToken, input.refreshToken ? { refreshToken: input.refreshToken } : {}).pipe(require_Schema$1.mapError(artifactError("Failed to persist CMMD JWT.")));
68124
+ if (input.jwtToken) yield* setCommandJwtForSession(input.assertion.sessionId, input.jwtToken).pipe(require_Schema$1.mapError(artifactError("Failed to persist CMMD JWT.")));
68203
68125
  });
68204
68126
  yield* sql.withTransaction(transaction).pipe(require_Schema$1.mapError((cause) => cause instanceof AuthError ? cause : new AuthError({
68205
68127
  message: "Failed to persist Forge Cloud owner credentials.",
@@ -68337,7 +68259,6 @@ const authSessionRenewRouteLayer = add$1("POST", "/api/auth/session/renew", requ
68337
68259
  accessToken: patToStore,
68338
68260
  identity: cmmdIdentity,
68339
68261
  jwtToken: credentials.jwtToStore,
68340
- refreshToken: credentials.refreshToken,
68341
68262
  sessionGrant,
68342
68263
  sessionGrantExpiresAt: void 0
68343
68264
  });
@@ -68414,7 +68335,6 @@ const PairingCredentialRequestHeaders = require_Schema$1.Struct({
68414
68335
  const CommandBootstrapInput = require_Schema$1.Struct({
68415
68336
  accessToken: require_Schema$1.String,
68416
68337
  mcpToken: require_Schema$1.optionalKey(require_Schema$1.String),
68417
- refreshToken: require_Schema$1.optionalKey(require_Schema$1.String),
68418
68338
  userId: require_Schema$1.optionalKey(require_Schema$1.Number),
68419
68339
  displayName: require_Schema$1.optionalKey(require_Schema$1.String),
68420
68340
  user: require_Schema$1.optionalKey(require_Schema$1.Unknown),
@@ -68648,6 +68568,7 @@ function validateCommandBootstrapPayload(payload) {
68648
68568
  };
68649
68569
  });
68650
68570
  }
68571
+ /** Store the bootstrap PAT, optional JWT, and identity; report persistence failures as auth errors. */
68651
68572
  function persistCommandBootstrapSessionArtifacts(input) {
68652
68573
  return require_Schema$1.gen(function* () {
68653
68574
  const token = input.payload.accessToken.trim();
@@ -68662,14 +68583,11 @@ function persistCommandBootstrapSessionArtifacts(input) {
68662
68583
  status: 500,
68663
68584
  cause
68664
68585
  })));
68665
- if (isCmmdJwtToken(token)) {
68666
- const refreshToken = input.payload.refreshToken?.trim();
68667
- yield* setCommandJwtForSession(input.sessionId, token, refreshToken ? { refreshToken } : {}).pipe(require_Schema$1.mapError((cause) => new AuthError({
68668
- message: "Failed to persist CMMD JWT.",
68669
- status: 500,
68670
- cause
68671
- })));
68672
- }
68586
+ if (isCmmdJwtToken(token)) yield* setCommandJwtForSession(input.sessionId, token).pipe(require_Schema$1.mapError((cause) => new AuthError({
68587
+ message: "Failed to persist CMMD JWT.",
68588
+ status: 500,
68589
+ cause
68590
+ })));
68673
68591
  const cmmdIdentity = input.cmmdIdentity ?? normalizeCmmdUserIdentity(input.payload.user);
68674
68592
  if (cmmdIdentity) yield* setCmmdUserIdentityForSession(input.sessionId, cmmdIdentity).pipe(require_Schema$1.mapError((cause) => new AuthError({
68675
68593
  message: "Failed to persist CMMD user identity.",
@@ -68771,7 +68689,6 @@ function resolveCmmdTokenBundleCredentials(tokenResult) {
68771
68689
  userId: cmmdIdentity?.id,
68772
68690
  accessToken,
68773
68691
  mcpToken,
68774
- refreshToken: readString("refresh_token"),
68775
68692
  sessionGrant: readString("session_grant"),
68776
68693
  sessionGrantExpiresAt: readString("session_grant_expires_at"),
68777
68694
  forgeOwnerAssertion: readString("forge_owner_assertion"),
@@ -68793,7 +68710,6 @@ function describeCmmdTokenResponseForLog(input) {
68793
68710
  accessTokenType: input.tokenObj ? typeof input.tokenObj["access_token"] : "n/a",
68794
68711
  hasAccessToken: input.accessToken !== void 0,
68795
68712
  hasMcpToken: input.mcpToken !== void 0,
68796
- hasRefreshToken: input.refreshToken !== void 0,
68797
68713
  expiresInType: input.tokenObj ? typeof input.tokenObj["expires_in"] : "n/a",
68798
68714
  isJwt: input.isJwt,
68799
68715
  userKeys: userObj ? Object.keys(userObj) : [],
@@ -69021,19 +68937,6 @@ const authCommandBearerBootstrapRouteLayer = add$1("POST", "/api/auth/bootstrap/
69021
68937
  payload,
69022
68938
  cmmdIdentity: validated.cmmdIdentity
69023
68939
  });
69024
- if (deviceType === "desktop" && !payload.refreshToken?.trim()) yield* clearDesktopBearerRefreshTokensForSubject({
69025
- subject: sessionSubject,
69026
- clientUserAgent: request.headers["user-agent"]
69027
- }).pipe(require_Schema$1.mapError((cause) => new AuthError({
69028
- message: "Failed to retire legacy desktop refresh credentials.",
69029
- status: 500,
69030
- cause
69031
- })), require_Schema$1.tapError(() => require_Schema$1.logWarning("CMMD bearer bootstrap failed.", {
69032
- sessionId: issued.sessionId,
69033
- deviceType,
69034
- outcome: "failed",
69035
- reason: "legacy-desktop-refresh-retirement-failed"
69036
- })));
69037
68940
  yield* require_Schema$1.logInfo("CMMD bearer bootstrap succeeded.", {
69038
68941
  sessionId: issued.sessionId,
69039
68942
  deviceType,
@@ -69131,7 +69034,6 @@ function completeCloudOwnerPasswordLogin(input) {
69131
69034
  accessToken: patToStore,
69132
69035
  identity: cmmdIdentity,
69133
69036
  jwtToken: credentials.jwtToStore,
69134
- refreshToken: credentials.refreshToken,
69135
69037
  sessionGrant: credentials.sessionGrant,
69136
69038
  sessionGrantExpiresAt: credentials.sessionGrantExpiresAt
69137
69039
  });
@@ -69214,6 +69116,10 @@ const authWebLoginRouteLayer = add$1("GET", "/api/auth/login", require_Schema$1.
69214
69116
  transientCookie
69215
69117
  ]]));
69216
69118
  }));
69119
+ /**
69120
+ * Persist the local OAuth PAT and identity, failing if either write fails.
69121
+ * Capture and ignore failures while storing the optional mint grant and JWT.
69122
+ */
69217
69123
  function persistLocalOAuthArtifacts(input) {
69218
69124
  return require_Schema$1.gen(function* () {
69219
69125
  yield* setCommandAccessTokenForSession(input.sessionId, input.accessToken).pipe(require_Schema$1.mapError((cause) => new AuthError({
@@ -69230,7 +69136,7 @@ function persistLocalOAuthArtifacts(input) {
69230
69136
  route: "oauth-callback",
69231
69137
  step: "grant-store"
69232
69138
  }))), require_Schema$1.ignore({ log: true }));
69233
- if (input.jwtToken) yield* setCommandJwtForSession(input.sessionId, input.jwtToken, input.refreshToken ? { refreshToken: input.refreshToken } : {}).pipe(require_Schema$1.tapError((error) => require_Schema$1.sync(() => captureServerError(error, {
69139
+ if (input.jwtToken) yield* setCommandJwtForSession(input.sessionId, input.jwtToken).pipe(require_Schema$1.tapError((error) => require_Schema$1.sync(() => captureServerError(error, {
69234
69140
  route: "oauth-callback",
69235
69141
  step: "jwt-store"
69236
69142
  }))), require_Schema$1.ignore({ log: true }));
@@ -69284,13 +69190,12 @@ const authWebCallbackRouteLayer = add$1("GET", "/api/auth/callback", require_Sch
69284
69190
  cause
69285
69191
  })
69286
69192
  }), "Failed to exchange authorization code with CMMD.");
69287
- const { tokenObj, cmmdIdentity, userId, accessToken, mcpToken, refreshToken, sessionGrant, sessionGrantExpiresAt, forgeOwnerAssertion, isJwt, patToStore, jwtToStore } = yield* resolveCmmdTokenBundleCredentials(tokenResult);
69193
+ const { tokenObj, cmmdIdentity, userId, accessToken, mcpToken, sessionGrant, sessionGrantExpiresAt, forgeOwnerAssertion, isJwt, patToStore, jwtToStore } = yield* resolveCmmdTokenBundleCredentials(tokenResult);
69288
69194
  const tokenResponseLogDetails = describeCmmdTokenResponseForLog({
69289
69195
  tokenObj,
69290
69196
  tokenResult,
69291
69197
  accessToken,
69292
69198
  mcpToken,
69293
- refreshToken,
69294
69199
  isJwt,
69295
69200
  userId,
69296
69201
  patToStore,
@@ -69343,7 +69248,6 @@ const authWebCallbackRouteLayer = add$1("GET", "/api/auth/callback", require_Sch
69343
69248
  accessToken: patToStore,
69344
69249
  identity: cmmdIdentity,
69345
69250
  jwtToken: jwtToStore,
69346
- refreshToken,
69347
69251
  sessionGrant,
69348
69252
  sessionGrantExpiresAt
69349
69253
  };
@@ -71361,51 +71265,83 @@ function isCmmdUserAccessJwt(token) {
71361
71265
  }
71362
71266
  //#endregion
71363
71267
  //#region src/command/cmmdJwt.ts
71364
- function parseCmmdOAuthRefreshResponse(value) {
71365
- if (!value || typeof value !== "object") return null;
71366
- const record = value;
71367
- const accessToken = record["access_token"];
71368
- if (typeof accessToken !== "string" || accessToken.trim().length === 0) return null;
71369
- const refreshToken = record["refresh_token"];
71370
- return {
71371
- accessToken: accessToken.trim(),
71372
- refreshToken: typeof refreshToken === "string" && refreshToken.trim().length > 0 ? refreshToken.trim() : null
71373
- };
71374
- }
71375
71268
  /**
71376
- * Read the one field the mint lane consumes from CMMD's mint response.
71377
- * Returns null unless `access_token` is present and non-empty.
71378
- */
71269
+ * Read a minted JWT with finite issuance and expiry times and a lifetime of
71270
+ * at most one hour. Reject expired tokens and tokens issued in the future.
71271
+ * This checks the mint contract only; CMMD verifies signatures on use.
71272
+ */
71273
+ /** Machine-client tokens live at most one hour; anything longer is a CMMD
71274
+ * contract violation that must not silently extend forge sessions. */
71275
+ const MAX_MACHINE_TOKEN_LIFETIME_SECONDS = 3600;
71276
+ /**
71277
+ * The minted access token's lifetime in seconds, read from its JWT `exp` and
71278
+ * `iat` claims without verifying the signature (the signature is checked on
71279
+ * every use at the guard; here the claims only measure the lifetime). `iat`
71280
+ * falls back to the current time when absent. Null when the payload cannot be
71281
+ * read, which is itself a contract violation.
71282
+ */
71283
+ function readJwtLifetimeSeconds(accessToken) {
71284
+ const claims = decodeJwtPayload(accessToken);
71285
+ const expiresAt = claims?.["exp"];
71286
+ const issuedAtRaw = claims?.["iat"];
71287
+ if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt)) return null;
71288
+ const issuedAt = typeof issuedAtRaw === "number" && Number.isFinite(issuedAtRaw) ? issuedAtRaw : Math.floor(Date.now() / 1e3);
71289
+ const now = Date.now() / 1e3;
71290
+ if (expiresAt <= now || issuedAt > now) return null;
71291
+ return expiresAt - issuedAt;
71292
+ }
71293
+ /** True when the minted token's lifetime honors the one-hour contract. A
71294
+ * refusal is logged here because the reason is mint-contract specific and the
71295
+ * caller only wants the token-or-null answer. */
71296
+ function mintLifetimeWithinContract(accessToken) {
71297
+ const lifetime = readJwtLifetimeSeconds(accessToken);
71298
+ if (lifetime !== null && lifetime <= MAX_MACHINE_TOKEN_LIFETIME_SECONDS) return true;
71299
+ console.warn(`[cmmd-auth] CMMD minted a forge token outside the one-hour contract (lifetime=${lifetime === null ? "unreadable" : `${lifetime}s`}); refusing to persist it`);
71300
+ return false;
71301
+ }
71379
71302
  function parseCmmdTokenMintResponse(value) {
71380
71303
  if (!value || typeof value !== "object") return null;
71381
71304
  const accessToken = value["access_token"];
71382
71305
  if (typeof accessToken !== "string" || accessToken.trim().length === 0) return null;
71383
- return { accessToken: accessToken.trim() };
71306
+ const trimmed = accessToken.trim();
71307
+ if (!mintLifetimeWithinContract(trimmed)) return null;
71308
+ return { accessToken: trimmed };
71384
71309
  }
71385
71310
  /**
71386
- * How long the mint may take before the lane gives up on it. Minting is one
71311
+ * How long the mint may take before the caller gives up on it. Minting is one
71387
71312
  * grant lookup and one signature on CMMD's side; anything slower is treated
71388
- * like a failure and the resolver falls through to the legacy ladder.
71313
+ * like a failure and the resolution returns null.
71389
71314
  */
71390
71315
  const MINT_TIMEOUT_MS = 1e4;
71391
71316
  /**
71317
+ * The OAuth error codes that are evidence the grant itself is dead, and the
71318
+ * only mint refusals allowed to feed the credential-rejection ledger.
71319
+ *
71320
+ * This set is grant-specific on purpose. Per RFC 6749, `invalid_grant` names
71321
+ * the grant. `invalid_client` names the client (CMMD did not accept
71322
+ * `cmmd-forge-web`) and `invalid_request` names the request shape (a missing or
71323
+ * unsupported parameter, for example after a CMMD contract change); both would
71324
+ * fire for every session at once, so a single misconfiguration or
71325
+ * deploy-ordering mistake could delete every stored credential. Those codes
71326
+ * stay logged refusals that resolve null and leave the row in place. If CMMD's
71327
+ * contract ever makes one of them grant-specific, document that here before
71328
+ * widening this set.
71329
+ */
71330
+ const REJECTED_OAUTH_ERROR_CODES = new Set(["invalid_grant"]);
71331
+ /**
71392
71332
  * Mint a short-lived forge-scoped access token from a durable session grant
71393
- * (forge#3518 expand phase).
71333
+ * (forge#3518 expand; the only lane since the forge#3521 contract phase).
71394
71334
  *
71395
- * The grant is non-rotating and redeemable on demand, so this is the mint
71396
- * lane's entire refresh story: call it again when the short-lived token
71397
- * expires. CMMD answers with `access_token` only (no refresh token, no MCP
71398
- * PAT, nothing durable), so a session that lives entirely on this lane never
71399
- * accumulates long-lived credential state on Forge's side.
71335
+ * The grant is non-rotating and redeemable on demand, so this is the entire
71336
+ * refresh story: call it again when the short-lived token expires. CMMD answers
71337
+ * with `access_token` only (no refresh token, no MCP PAT, nothing durable), so
71338
+ * a session living on this lane never accumulates long-lived credential state
71339
+ * beyond the grant it already holds.
71400
71340
  *
71401
71341
  * Returns null for every failure because the resolver wants a value, not a
71402
- * thrown error; the failure modes are logged by status and OAuth error code
71403
- * the same way `refreshCmmdJwt` logs them. A rejection here is never fed to
71404
- * the credential-rejection ledger: the ledger exists to prove a session's
71405
- * *rotation* credential is dead, and the grant dying says nothing about the
71406
- * refresh token the legacy fallback still owns.
71342
+ * thrown error; the failure modes are logged by status and OAuth error code.
71407
71343
  */
71408
- function mintCmmdShortLivedToken(grantToken) {
71344
+ function mintCmmdShortLivedToken(grantToken, observer) {
71409
71345
  return fetch(new URL("/api/mcp-server/oauth/token", resolveCommandAppUrl()).toString(), {
71410
71346
  method: "POST",
71411
71347
  headers: { "Content-Type": "application/json" },
@@ -71418,6 +71354,7 @@ function mintCmmdShortLivedToken(grantToken) {
71418
71354
  }).then(async (response) => {
71419
71355
  if (!response.ok) {
71420
71356
  const errorCode = await readOAuthErrorCode(response);
71357
+ if (errorCode !== null && REJECTED_OAUTH_ERROR_CODES.has(errorCode)) observer?.onCredentialRejected?.();
71421
71358
  console.warn(`[cmmd-auth] CMMD refused a forge token mint with ${response.status}${errorCode === null ? "" : ` (${errorCode})`}`);
71422
71359
  return null;
71423
71360
  }
@@ -71430,103 +71367,149 @@ function mintCmmdShortLivedToken(grantToken) {
71430
71367
  });
71431
71368
  }
71432
71369
  /**
71433
- * The redemption request body. `popPublicKeyPem` is the server's PoP identity
71434
- * (forge#3534): presented when bound sessions are enabled so CMMD stamps the
71435
- * returned JWT with this server's key (`cnf.jkt`), omitted otherwise.
71370
+ * The OAuth `error` code from a refusal, lowercased, or null when there is none.
71371
+ *
71372
+ * Best effort by design: a body that is not JSON, or is already consumed, must
71373
+ * not turn a logged failure into an unlogged one. Null also reads correctly as
71374
+ * "no evidence the grant is dead", which is the safe answer for a caller
71375
+ * deciding whether to keep the session's stored credential.
71436
71376
  */
71437
- function buildSessionGrantRedemptionBody(grantToken, popPublicKeyPem) {
71438
- return {
71439
- grant_type: "forge_session_grant",
71440
- grant_token: grantToken,
71441
- client_id: "cmmd-forge-web",
71442
- ...popPublicKeyPem ? { pop_public_key: popPublicKeyPem } : {}
71443
- };
71377
+ async function readOAuthErrorCode(response) {
71378
+ try {
71379
+ const code = (await response.json())["error"];
71380
+ return typeof code === "string" && code.trim().length > 0 ? code.trim().toLowerCase() : null;
71381
+ } catch {
71382
+ return null;
71383
+ }
71444
71384
  }
71385
+ //#endregion
71386
+ //#region src/cmmdConnection/cmmdCredentialRejection.ts
71445
71387
  /**
71446
- * Redeem a durable CMMD session grant for a fresh credential set.
71447
- *
71448
- * The grant is CMMD's designed recovery path for exactly the case a rotating
71449
- * refresh token cannot cover: the refresh token is single-use, so one lost or
71450
- * raced rotation strands the session with no way back. The grant is
71451
- * NON-rotating — the same token redeems repeatedly until CMMD expires or
71452
- * revokes it — so redemption is safe to retry and cannot race itself.
71388
+ * How long a recorded rejection stays usable.
71453
71389
  *
71454
- * Returns the new MCP PAT alongside the JWT/refresh pair, because CMMD mints a
71455
- * fresh PAT on redemption and the old one is not carried over.
71390
+ * Long enough to span the resolver ladder and the response that follows it,
71391
+ * short enough that it cannot outlive the credential it describes.
71456
71392
  */
71457
- function redeemCmmdSessionGrant(grantToken, popPublicKeyPem) {
71458
- return fetch(new URL("/api/mcp-server/oauth/token", resolveCommandAppUrl()).toString(), {
71459
- method: "POST",
71460
- headers: { "Content-Type": "application/json" },
71461
- body: JSON.stringify(buildSessionGrantRedemptionBody(grantToken, popPublicKeyPem))
71462
- }).then(async (response) => {
71463
- if (!response.ok) return null;
71464
- const payload = await response.json();
71465
- const parsed = parseCmmdOAuthRefreshResponse(payload);
71466
- if (!parsed) return null;
71467
- const mcpToken = payload["mcp_token"];
71468
- return {
71469
- ...parsed,
71470
- mcpToken: typeof mcpToken === "string" && mcpToken.trim().length > 0 ? mcpToken.trim() : null
71471
- };
71472
- }).catch(() => null);
71393
+ const REJECTION_TTL_MS = 6e4;
71394
+ const rejectedAt = /* @__PURE__ */ new Map();
71395
+ /** Record that CMMD refused this session's credential outright. */
71396
+ function noteCmmdCredentialRejected(sessionId) {
71397
+ rejectedAt.set(sessionId, Date.now());
71398
+ }
71399
+ /** Whether CMMD refused this session's credential recently enough to act on. */
71400
+ function hasCmmdCredentialRejection(sessionId) {
71401
+ const observedAt = rejectedAt.get(sessionId);
71402
+ if (observedAt === void 0) return false;
71403
+ if (Date.now() - observedAt <= REJECTION_TTL_MS) return true;
71404
+ rejectedAt.delete(sessionId);
71405
+ return false;
71406
+ }
71407
+ /** Drop a recorded rejection, once it has been acted on or superseded. */
71408
+ function forgetCmmdCredentialRejection(sessionId) {
71409
+ rejectedAt.delete(sessionId);
71473
71410
  }
71474
- const REJECTED_OAUTH_ERROR_CODES = new Set([
71475
- "invalid_grant",
71476
- "invalid_client",
71477
- "invalid_request"
71478
- ]);
71479
71411
  /**
71480
- * Rotate a CMMD refresh token into a fresh access JWT.
71412
+ * Clear a session's CMMD credentials, but only if CMMD rejected them.
71481
71413
  *
71482
- * Returns null for every failure, because the caller's recovery ladder wants a
71483
- * value rather than a thrown error. Null is nearly the whole diagnostic value
71484
- * of this call though, so each way of reaching it says so first. Chasing a
71485
- * phone that had silently stopped accepting its session cost an afternoon
71486
- * partly because a failed rotation here was indistinguishable from one that
71487
- * never ran. The token itself is never logged; the status and the OAuth error
71488
- * code are what identify the failure, and `invalid_grant` in particular means
71489
- * the token was already spent, which is a different fix from a 500.
71414
+ * This is the only door the 401 path may use. A resolver that returned null
71415
+ * without a recorded rejection is a transient failure and leaves the stored
71416
+ * credential exactly where it is, so the next request can mint from it again.
71490
71417
  */
71491
- function refreshCmmdJwt(refreshToken, observer) {
71492
- return fetch(new URL("/api/mcp-server/oauth/token", resolveCommandAppUrl()).toString(), {
71493
- method: "POST",
71494
- headers: { "Content-Type": "application/json" },
71495
- body: JSON.stringify({
71496
- grant_type: "refresh_token",
71497
- refresh_token: refreshToken,
71498
- client_id: "cmmd-forge-web"
71499
- })
71500
- }).then(async (response) => {
71501
- if (!response.ok) {
71502
- const errorCode = await readOAuthErrorCode(response);
71503
- if (errorCode !== null && REJECTED_OAUTH_ERROR_CODES.has(errorCode)) observer?.onCredentialRejected?.();
71504
- console.warn(`[cmmd-auth] CMMD refused a JWT rotation with ${response.status}${errorCode === null ? "" : ` (${errorCode})`}`);
71505
- return null;
71506
- }
71507
- const parsed = parseCmmdOAuthRefreshResponse(await response.json());
71508
- if (!parsed) console.warn("[cmmd-auth] CMMD accepted a JWT rotation but returned no usable access token");
71509
- return parsed;
71510
- }).catch((cause) => {
71511
- console.warn(`[cmmd-auth] CMMD JWT rotation could not be completed: ${String(cause)}`);
71512
- return null;
71418
+ const clearCmmdAuthAfterCmmdRejection = (sessionId) => require_Schema$1.gen(function* () {
71419
+ if (!hasCmmdCredentialRejection(sessionId)) return;
71420
+ yield* clearUnrecoverableCmmdAuthForSession(sessionId, "cmmd-rejected-credential");
71421
+ forgetCmmdCredentialRejection(sessionId);
71422
+ });
71423
+ //#endregion
71424
+ //#region src/command/cmmdJwtResolver.ts
71425
+ function usableCmmdUserJwt(token) {
71426
+ return isCmmdUserAccessJwt(token) ? token : null;
71427
+ }
71428
+ /** Log a resolver decision with session context and a fixed reason, excluding credential values. */
71429
+ function logCmmdJwtRung(context, rung, outcome, reason) {
71430
+ return (outcome === "failed" ? require_Schema$1.logWarning : require_Schema$1.logInfo)("CMMD JWT resolver rung.", {
71431
+ sessionId: context.sessionId,
71432
+ subject: context.subject,
71433
+ deviceType: context.deviceType,
71434
+ caller: context.caller,
71435
+ rung,
71436
+ outcome,
71437
+ reason
71513
71438
  });
71514
71439
  }
71440
+ /** In-flight mints, keyed by session. */
71441
+ const inFlightMints = /* @__PURE__ */ new Map();
71515
71442
  /**
71516
- * The OAuth `error` code from a refusal, lowercased, or null when there is none.
71443
+ * Collapse concurrent mints for one session into a single upstream call. A
71444
+ * mint cannot race itself into `invalid_grant` (the grant is non-rotating),
71445
+ * so this is call-rate hygiene, not correctness: two tabs observing the same
71446
+ * expiry must not both hit CMMD when one mint would do.
71447
+ */
71448
+ function mintOnceForSession(sessionId, grantToken) {
71449
+ return require_Schema$1.promise(() => {
71450
+ const existing = inFlightMints.get(sessionId);
71451
+ if (existing) return existing;
71452
+ const pending = mintCmmdShortLivedToken(grantToken, { onCredentialRejected: () => noteCmmdCredentialRejected(sessionId) }).catch(() => null).finally(() => {
71453
+ inFlightMints.delete(sessionId);
71454
+ });
71455
+ inFlightMints.set(sessionId, pending);
71456
+ return pending;
71457
+ });
71458
+ }
71459
+ /**
71460
+ * Resolve the CMMD REST JWT for a Forge session.
71517
71461
  *
71518
- * Best effort by design: a body that is not JSON, or is already consumed, must
71519
- * not turn a logged failure into an unlogged one. Null also reads correctly as
71520
- * "no evidence the credential is dead", which is the safe answer for a caller
71521
- * deciding whether to keep a refresh token.
71462
+ * Forge session auth is long-lived compared with the short-lived CMMD JWT. When
71463
+ * the session JWT expires, mint a fresh short-lived token from the session's
71464
+ * durable, non-rotating grant: CMMD answers with one forge-scoped access token
71465
+ * (TTL at most one hour) and nothing else, and minting is the entire refresh
71466
+ * story (forge#3521 contract phase). There is no rotation to fence, no
71467
+ * single-use token to single-flight, and no sibling credential to borrow.
71468
+ *
71469
+ * Fail-closed: never returns a non-user / malformed Bearer. Callers must map
71470
+ * null to CMMD_AUTH_REQUIRED and must not call CMMD upstream with a bad token.
71522
71471
  */
71523
- async function readOAuthErrorCode(response) {
71524
- try {
71525
- const code = (await response.json())["error"];
71526
- return typeof code === "string" && code.trim().length > 0 ? code.trim().toLowerCase() : null;
71527
- } catch {
71528
- return null;
71472
+ function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth", forceRefresh = false, staleAccessToken = null }) {
71473
+ const cached = usableCmmdUserJwt(readCommandJwtForSession(sessionId));
71474
+ if (cached && !isJwtExpiredForSession(sessionId)) {
71475
+ if (!forceRefresh) return require_Schema$1.succeed(cached);
71476
+ if (staleAccessToken && cached !== staleAccessToken) return require_Schema$1.succeed(cached).pipe(require_Schema$1.tap(() => require_Schema$1.gen(function* () {
71477
+ const deviceType = yield* readClientDeviceTypeForSession(sessionId);
71478
+ yield* logCmmdJwtRung({
71479
+ sessionId,
71480
+ subject: subject?.trim() || null,
71481
+ deviceType,
71482
+ caller: logPrefix
71483
+ }, "cache", "skipped", "forced refresh skipped: cache already holds a token another caller minted");
71484
+ })));
71529
71485
  }
71486
+ const normalizedSubject = subject?.trim() || null;
71487
+ if (!normalizedSubject && !forceRefresh && readCommandSessionGrantForSession(sessionId) === null) return require_Schema$1.succeed(null);
71488
+ return require_Schema$1.gen(function* () {
71489
+ const context = {
71490
+ sessionId,
71491
+ subject: normalizedSubject,
71492
+ deviceType: yield* readClientDeviceTypeForSession(sessionId),
71493
+ caller: logPrefix
71494
+ };
71495
+ const grantToken = readCommandSessionGrantForSession(sessionId);
71496
+ if (!grantToken) {
71497
+ yield* logCmmdJwtRung(context, "mint", "skipped", "no session grant stored for this session");
71498
+ return null;
71499
+ }
71500
+ yield* logCmmdJwtRung(context, "mint", "attempted", "minting a short-lived forge-scoped token from this session's grant");
71501
+ yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "attempted" })), 1);
71502
+ const accessToken = usableCmmdUserJwt((yield* mintOnceForSession(sessionId, grantToken))?.accessToken);
71503
+ if (!accessToken) {
71504
+ yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "failed" })), 1);
71505
+ yield* logCmmdJwtRung(context, "mint", "failed", "CMMD token mint did not return a usable user access JWT");
71506
+ return null;
71507
+ }
71508
+ yield* setCommandJwtForSession(sessionId, accessToken).pipe(require_Schema$1.ignore({ log: true }));
71509
+ yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "succeeded" })), 1);
71510
+ yield* logCmmdJwtRung(context, "mint", "succeeded", "CMMD minted a short-lived forge-scoped token");
71511
+ return accessToken;
71512
+ });
71530
71513
  }
71531
71514
  //#endregion
71532
71515
  //#region ../../packages/shared/src/cmmdSessionPop.ts
@@ -71644,10 +71627,6 @@ function resolveCmmdSessionPopSigner() {
71644
71627
  cachedSigner ??= createServerSigner();
71645
71628
  return cachedSigner;
71646
71629
  }
71647
- /** The `pop_public_key` value to present at issuance, or null while disabled. */
71648
- function cmmdSessionPopPublicKey() {
71649
- return resolveCmmdSessionPopSigner()?.publicKeyPem ?? null;
71650
- }
71651
71630
  /**
71652
71631
  * The three proof headers for one CMMD-origin request, or no headers while
71653
71632
  * disabled. Each call carries a fresh nonce; never reuse the result across
@@ -71663,407 +71642,6 @@ async function cmmdSessionPopRequestHeaders(input) {
71663
71642
  });
71664
71643
  }
71665
71644
  //#endregion
71666
- //#region src/command/cmmdTokenMintLane.ts
71667
- /**
71668
- * The CMMD token-mint lane (forge#3518, expand phase).
71669
- *
71670
- * A new FIRST rung beside the legacy credential ladder: instead of rotating
71671
- * CMMD's single-use refresh tokens, the resolver mints a short-lived
71672
- * forge-scoped access token from the session's durable, non-rotating session
71673
- * grant (`grant_type: forge_token_mint` on CMMD's token endpoint). The mint
71674
- * spends nothing that cannot be spent again, so it cannot race itself into
71675
- * `invalid_grant` the way a rotation can.
71676
- *
71677
- * Behind a config flag that is OFF unless explicitly enabled: production
71678
- * keeps the legacy ladder as the only lane until this is switched on
71679
- * deliberately. When enabled, the lane is tried before the legacy ladder;
71680
- * every legacy rung (rotation fence, self-refresh, grant redemption,
71681
- * bootstrap promotion, subject fallback) still exists below it unchanged and
71682
- * still guards itself with the rejection ledger.
71683
- */
71684
- /** Env var that enables the mint lane. Unset and anything but an affirmative value is off. */
71685
- const CMMD_TOKEN_MINT_LANE_ENV = "FORGE_CMMD_TOKEN_MINT_LANE";
71686
- const AFFIRMATIVE_VALUES = new Set([
71687
- "1",
71688
- "on",
71689
- "true",
71690
- "yes"
71691
- ]);
71692
- /**
71693
- * Whether the mint lane may run. Default off everywhere: a missing env var is
71694
- * the production posture, and an opt-in flag must not half-default to on in
71695
- * any environment a misconfigured deploy might land in.
71696
- */
71697
- function isCmmdTokenMintLaneEnabled(env = process.env) {
71698
- const raw = env[CMMD_TOKEN_MINT_LANE_ENV]?.trim().toLowerCase();
71699
- return raw !== void 0 && AFFIRMATIVE_VALUES.has(raw);
71700
- }
71701
- //#endregion
71702
- //#region src/command/cmmdRotationFence.ts
71703
- /**
71704
- * Log and count a fence application, without changing the verdict it reports.
71705
- *
71706
- * The two facts named in the log line are exactly the ones the predicate
71707
- * above the caller already computed — whether this session ever deposited a
71708
- * refresh token of its own, and (only when that was false) whether the
71709
- * session row reads as a desktop bearer session. Neither fact nor the log
71710
- * line changes what the caller returns.
71711
- */
71712
- function logRotationFenceApplied(input) {
71713
- return require_Schema$1.logInfo("CMMD rotation fence applied: session may not rotate its own credential.", {
71714
- sessionId: input.sessionId,
71715
- hasStoredRefreshToken: input.hasStoredRefreshToken,
71716
- isDesktopBearerSession: input.isDesktopBearerSession
71717
- }).pipe(require_Schema$1.andThen(() => require_Schema$1.update(require_Schema$1.withAttributes(cmmdRotationFenceAppliedTotal, metricAttributes({})), 1)));
71718
- }
71719
- /**
71720
- * True when Forge must not rotate this session's CMMD credentials.
71721
- *
71722
- * Fails closed: if the session row cannot be read, assume the fence applies.
71723
- * Spending a token Forge had no right to spend is worse than one extra
71724
- * reconnect.
71725
- */
71726
- function isRotationFencedSession(sessionId) {
71727
- const hasStoredRefreshToken = readCommandRefreshTokenForSession(sessionId) !== null;
71728
- if (hasStoredRefreshToken) return require_Schema$1.succeed(false);
71729
- return isDesktopBearerAuthSession(sessionId).pipe(require_Schema$1.match({
71730
- onFailure: () => ({
71731
- fenced: true,
71732
- isDesktopBearerSession: null
71733
- }),
71734
- onSuccess: (isDesktopBearerSession) => ({
71735
- fenced: isDesktopBearerSession,
71736
- isDesktopBearerSession
71737
- })
71738
- }), require_Schema$1.tap(({ fenced, isDesktopBearerSession }) => fenced ? logRotationFenceApplied({
71739
- sessionId,
71740
- hasStoredRefreshToken,
71741
- isDesktopBearerSession
71742
- }) : require_Schema$1.void_), require_Schema$1.map(({ fenced }) => fenced));
71743
- }
71744
- //#endregion
71745
- //#region src/cmmdConnection/cmmdCredentialRejection.ts
71746
- /**
71747
- * How long a recorded rejection stays usable.
71748
- *
71749
- * Long enough to span the resolver ladder and the response that follows it,
71750
- * short enough that it cannot outlive the credential it describes.
71751
- */
71752
- const REJECTION_TTL_MS = 6e4;
71753
- const rejectedAt = /* @__PURE__ */ new Map();
71754
- /** Record that CMMD refused this session's credential outright. */
71755
- function noteCmmdCredentialRejected(sessionId) {
71756
- rejectedAt.set(sessionId, Date.now());
71757
- }
71758
- /** Whether CMMD refused this session's credential recently enough to act on. */
71759
- function hasCmmdCredentialRejection(sessionId) {
71760
- const observedAt = rejectedAt.get(sessionId);
71761
- if (observedAt === void 0) return false;
71762
- if (Date.now() - observedAt <= REJECTION_TTL_MS) return true;
71763
- rejectedAt.delete(sessionId);
71764
- return false;
71765
- }
71766
- /** Drop a recorded rejection, once it has been acted on or superseded. */
71767
- function forgetCmmdCredentialRejection(sessionId) {
71768
- rejectedAt.delete(sessionId);
71769
- }
71770
- /**
71771
- * Clear a session's CMMD credentials, but only if CMMD rejected them.
71772
- *
71773
- * This is the only door the 401 path may use. A resolver that returned null
71774
- * because it was not allowed to try is a policy refusal and leaves the stored
71775
- * credential exactly where it is, so the next request can rotate it.
71776
- */
71777
- const clearCmmdAuthAfterCmmdRejection = (sessionId) => require_Schema$1.gen(function* () {
71778
- if (!hasCmmdCredentialRejection(sessionId)) return;
71779
- yield* clearUnrecoverableCmmdAuthForSession(sessionId, "cmmd-rejected-credential");
71780
- forgetCmmdCredentialRejection(sessionId);
71781
- });
71782
- //#endregion
71783
- //#region src/command/cmmdJwtResolver.ts
71784
- function usableCmmdUserJwt(token) {
71785
- return isCmmdUserAccessJwt(token) ? token : null;
71786
- }
71787
- function storeResolvedJwt(sessionId, accessToken, refreshToken) {
71788
- return setCommandJwtForSession(sessionId, accessToken, refreshToken ? { refreshToken } : {}).pipe(require_Schema$1.ignore({ log: true }));
71789
- }
71790
- function logCmmdJwtRung(context, rung, outcome, reason, lane = "legacy") {
71791
- return (outcome === "failed" ? require_Schema$1.logWarning : require_Schema$1.logInfo)("CMMD JWT resolver rung.", {
71792
- sessionId: context.sessionId,
71793
- subject: context.subject,
71794
- deviceType: context.deviceType,
71795
- caller: context.caller,
71796
- lane,
71797
- rung,
71798
- outcome,
71799
- reason
71800
- });
71801
- }
71802
- function recordCmmdCredentialRefreshMetric(rung, outcome) {
71803
- return require_Schema$1.update(require_Schema$1.withAttributes(cmmdCredentialRefreshTotal, metricAttributes({
71804
- rung,
71805
- outcome
71806
- })), 1);
71807
- }
71808
- const inFlightRefreshes = /* @__PURE__ */ new Map();
71809
- /** In-flight mints, keyed by session, same shape as `inFlightRefreshes`. */
71810
- const inFlightMints = /* @__PURE__ */ new Map();
71811
- /**
71812
- * Collapse concurrent mints for one session into a single upstream call. A
71813
- * mint cannot race itself into `invalid_grant` (the grant is non-rotating),
71814
- * so this is call-rate hygiene, not correctness: two tabs observing the same
71815
- * expiry must not both hit CMMD when one mint would do.
71816
- */
71817
- function mintOnceForSession(sessionId, grantToken) {
71818
- return require_Schema$1.promise(() => {
71819
- const existing = inFlightMints.get(sessionId);
71820
- if (existing) return existing;
71821
- const pending = mintCmmdShortLivedToken(grantToken).catch(() => null).finally(() => {
71822
- inFlightMints.delete(sessionId);
71823
- });
71824
- inFlightMints.set(sessionId, pending);
71825
- return pending;
71826
- });
71827
- }
71828
- function refreshOnceForSession(sessionId, refreshToken) {
71829
- return require_Schema$1.promise(() => {
71830
- const existing = inFlightRefreshes.get(sessionId);
71831
- if (existing) return existing;
71832
- const pending = refreshCmmdJwt(refreshToken, { onCredentialRejected: () => noteCmmdCredentialRejected(sessionId) }).catch(() => null).finally(() => {
71833
- inFlightRefreshes.delete(sessionId);
71834
- });
71835
- inFlightRefreshes.set(sessionId, pending);
71836
- return pending;
71837
- });
71838
- }
71839
- /**
71840
- * Rotate this session's **own** CMMD refresh token.
71841
- *
71842
- * CMMD rotates refresh tokens single-use — the presented hash is revoked inside
71843
- * the rotation transaction (CMMD `server/jwt-auth.ts`). A session must
71844
- * therefore only ever present the refresh token it was issued: rotating a
71845
- * sibling session's token silently invalidates that sibling's stored copy and
71846
- * signs it out.
71847
- */
71848
- function trySelfRefresh(sessionId, context) {
71849
- const currentRefreshToken = readCommandRefreshTokenForSession(sessionId);
71850
- if (!currentRefreshToken) return logCmmdJwtRung(context, "self-refresh", "skipped", "no refresh token stored for this session").pipe(require_Schema$1.as(null));
71851
- return require_Schema$1.gen(function* () {
71852
- yield* logCmmdJwtRung(context, "self-refresh", "attempted", "rotating this session's own stored refresh token");
71853
- yield* recordCmmdCredentialRefreshMetric("self-refresh", "attempted");
71854
- const refreshed = yield* refreshOnceForSession(sessionId, currentRefreshToken);
71855
- const accessToken = usableCmmdUserJwt(refreshed?.accessToken);
71856
- if (!accessToken) {
71857
- yield* logCmmdJwtRung(context, "self-refresh", "failed", "CMMD refresh did not return a usable user access JWT");
71858
- return null;
71859
- }
71860
- yield* storeResolvedJwt(sessionId, accessToken, refreshed?.refreshToken);
71861
- yield* recordCmmdCredentialRefreshMetric("self-refresh", "succeeded");
71862
- yield* logCmmdJwtRung(context, "self-refresh", "succeeded", "CMMD rotated the stored refresh token");
71863
- return accessToken;
71864
- });
71865
- }
71866
- /**
71867
- * The mint lane's rung (forge#3518, expand phase): mint a short-lived
71868
- * forge-scoped access token from this session's durable session grant.
71869
- *
71870
- * Tried FIRST, before the rotation fence and the whole legacy ladder, when
71871
- * `FORGE_CMMD_TOKEN_MINT_LANE` is on. Minting spends nothing that cannot be
71872
- * spent again (the grant is non-rotating), so unlike the legacy rungs it is
71873
- * safe for a session the rotation fence fences off, and it never feeds the
71874
- * credential-rejection ledger: this rung failing says nothing about whether
71875
- * the legacy rungs below it can still succeed.
71876
- *
71877
- * Only the JWT is persisted (`setCommandJwtForSession`, no refresh token):
71878
- * the short lifetime is the expiry story, and re-minting is the refresh
71879
- * story. A session that lives entirely on this lane accumulates no new
71880
- * long-lived credential state beyond the grant it already had.
71881
- */
71882
- function tryMintShortLivedToken(sessionId, context) {
71883
- const grantToken = readCommandSessionGrantForSession(sessionId);
71884
- if (!grantToken) return logCmmdJwtRung(context, "mint", "skipped", "no session grant stored for this session", "mint").pipe(require_Schema$1.as(null));
71885
- return require_Schema$1.gen(function* () {
71886
- yield* logCmmdJwtRung(context, "mint", "attempted", "minting a short-lived forge-scoped token from this session's grant", "mint");
71887
- yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "attempted" })), 1);
71888
- const accessToken = usableCmmdUserJwt((yield* mintOnceForSession(sessionId, grantToken))?.accessToken);
71889
- if (!accessToken) {
71890
- yield* logCmmdJwtRung(context, "mint", "failed", "CMMD token mint did not return a usable user access JWT", "mint");
71891
- return null;
71892
- }
71893
- yield* setCommandJwtForSession(sessionId, accessToken, {}).pipe(require_Schema$1.ignore({ log: true }));
71894
- yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "succeeded" })), 1);
71895
- yield* logCmmdJwtRung(context, "mint", "succeeded", "CMMD minted a short-lived forge-scoped token", "mint");
71896
- return accessToken;
71897
- });
71898
- }
71899
- /**
71900
- * Last-resort recovery: redeem this session's durable CMMD session grant.
71901
- *
71902
- * A refresh token is single-use, so a session that lost a rotation — raced, or
71903
- * stranded by a redeploy mid-flight — has no way back and the user is bounced
71904
- * to /login. CMMD mints a long-lived, non-rotating grant at login for exactly
71905
- * this, and redemption returns a fresh JWT, refresh token AND MCP PAT.
71906
- *
71907
- * Only reached after the session's own refresh has already failed, so this
71908
- * never competes with a live rotation. The grant is not cleared on failure:
71909
- * CMMD owns its lifetime, and a transient network failure must not throw away
71910
- * the one credential that can still recover the session.
71911
- */
71912
- function tryGrantRedemption(sessionId, context) {
71913
- const grantToken = readCommandSessionGrantForSession(sessionId);
71914
- if (!grantToken) return logCmmdJwtRung(context, "grant-redemption", "skipped", "no session grant stored for this session").pipe(require_Schema$1.as(null));
71915
- return require_Schema$1.gen(function* () {
71916
- yield* logCmmdJwtRung(context, "grant-redemption", "attempted", "redeeming this session's durable CMMD session grant");
71917
- yield* recordCmmdCredentialRefreshMetric("grant-redemption", "attempted");
71918
- const redeemed = yield* require_Schema$1.promise(() => redeemCmmdSessionGrant(grantToken, cmmdSessionPopPublicKey())).pipe(require_Schema$1.orElseSucceed(() => null));
71919
- const accessToken = usableCmmdUserJwt(redeemed?.accessToken);
71920
- if (!accessToken) {
71921
- yield* logCmmdJwtRung(context, "grant-redemption", "failed", "session grant redemption did not return a usable user access JWT");
71922
- return null;
71923
- }
71924
- if (redeemed?.mcpToken) yield* setCommandAccessTokenForSession(sessionId, redeemed.mcpToken).pipe(require_Schema$1.ignore({ log: true }));
71925
- yield* storeResolvedJwt(sessionId, accessToken, redeemed?.refreshToken);
71926
- yield* recordCmmdCredentialRefreshMetric("grant-redemption", "succeeded");
71927
- yield* logCmmdJwtRung(context, "grant-redemption", "succeeded", "session grant redeemed");
71928
- return accessToken;
71929
- });
71930
- }
71931
- /**
71932
- * Recover using only credentials this session owns, strongest first.
71933
- *
71934
- * Rotating its own refresh token is preferred; the durable grant is the
71935
- * fallback for when that rotation is already gone. Neither touches another
71936
- * session's credentials, so this ladder is safe to run from any path.
71937
- */
71938
- function tryOwnCredentialRecovery(sessionId, context) {
71939
- return require_Schema$1.gen(function* () {
71940
- const refreshed = yield* trySelfRefresh(sessionId, context);
71941
- if (refreshed) return refreshed;
71942
- return yield* tryGrantRedemption(sessionId, context);
71943
- });
71944
- }
71945
- function tryPromoteBootstrap(sessionId, subject, forceRefresh, context) {
71946
- if (subject !== "owner-bootstrap" && subject !== "desktop-bootstrap") return logCmmdJwtRung(context, "promote-bootstrap", "skipped", "subject is not a bootstrap subject").pipe(require_Schema$1.as(null));
71947
- return require_Schema$1.gen(function* () {
71948
- yield* logCmmdJwtRung(context, "promote-bootstrap", "attempted", "promoting the most recent owner CMMD auth to this bootstrap session");
71949
- yield* promoteMostRecentOwnerCommandAuthToSession(sessionId).pipe(require_Schema$1.catch_((error) => require_Schema$1.logWarning("Failed to promote owner CMMD auth for REST JWT resolution.", {
71950
- cause: error,
71951
- sessionId,
71952
- subject
71953
- })));
71954
- const promotedJwt = usableCmmdUserJwt(readCommandJwtForSession(sessionId));
71955
- if (!forceRefresh && promotedJwt && !isJwtExpiredForSession(sessionId)) {
71956
- yield* logCmmdJwtRung(context, "promote-bootstrap", "succeeded", "promoted owner JWT is usable");
71957
- return promotedJwt;
71958
- }
71959
- yield* logCmmdJwtRung(context, "promote-bootstrap", "failed", forceRefresh ? "forced refresh: a promoted JWT is never reused as-is" : "no promoted JWT available, or it is already expired");
71960
- return null;
71961
- });
71962
- }
71963
- /**
71964
- * Borrow a sibling session's still-valid CMMD access JWT.
71965
- *
71966
- * `readMostRecentJwtForSubject` only returns a JWT that has not expired, so
71967
- * this is a cheap read-only recovery that avoids an upstream rotation. The
71968
- * sibling's *refresh* token is deliberately NOT copied: it is single-use and
71969
- * must stay private to the session it was issued to (see `trySelfRefresh`).
71970
- * Copying it is what let one expired tab sign the user out everywhere.
71971
- */
71972
- function trySubjectFallback(sessionId, subject, context) {
71973
- return require_Schema$1.gen(function* () {
71974
- yield* logCmmdJwtRung(context, "subject-fallback", "attempted", "looking for a sibling session's still-valid JWT for this subject");
71975
- const fallbackJwt = usableCmmdUserJwt((yield* readMostRecentJwtForSubject(subject).pipe(require_Schema$1.orElseSucceed(() => null)))?.jwt);
71976
- if (!fallbackJwt) {
71977
- yield* logCmmdJwtRung(context, "subject-fallback", "failed", "no sibling session holds a still-valid JWT for this subject");
71978
- return null;
71979
- }
71980
- yield* storeResolvedJwt(sessionId, fallbackJwt, null);
71981
- yield* logCmmdJwtRung(context, "subject-fallback", "succeeded", "borrowed a sibling session's still-valid JWT");
71982
- return fallbackJwt;
71983
- });
71984
- }
71985
- /**
71986
- * The rungs below the rotation fence: forced own-credential recovery,
71987
- * bootstrap promotion, subject fallback, then own-credential recovery again
71988
- * as the last resort. Split out of `resolveCmmdJwtForSession` so each half of
71989
- * the ladder stays independently readable (and independently testable) —
71990
- * the fence check above is the one decision the split does not touch.
71991
- */
71992
- function runRecoveryLadder(sessionId, normalizedSubject, forceRefresh, context) {
71993
- return require_Schema$1.gen(function* () {
71994
- if (forceRefresh) {
71995
- const forced = yield* tryOwnCredentialRecovery(sessionId, context);
71996
- if (forced) return forced;
71997
- }
71998
- if (normalizedSubject) {
71999
- const promoted = yield* tryPromoteBootstrap(sessionId, normalizedSubject, forceRefresh, context);
72000
- if (promoted) return promoted;
72001
- }
72002
- if (!normalizedSubject) {
72003
- yield* logCmmdJwtRung(context, "cache", "failed", "no subject to resolve and forced recovery already exhausted");
72004
- return null;
72005
- }
72006
- if (!forceRefresh) {
72007
- const fallback = yield* trySubjectFallback(sessionId, normalizedSubject, context);
72008
- if (fallback) return fallback;
72009
- }
72010
- const recovered = yield* tryOwnCredentialRecovery(sessionId, context);
72011
- if (!recovered) yield* logCmmdJwtRung(context, "cache", "failed", "recovery ladder exhausted: no rung produced a usable JWT");
72012
- return recovered;
72013
- });
72014
- }
72015
- /**
72016
- * Resolve the CMMD REST JWT for a Forge session.
72017
- *
72018
- * Forge session auth is long-lived compared with the short-lived CMMD JWT.
72019
- * When the session JWT expires, recover by minting a fresh short-lived token
72020
- * from the session grant (the mint lane; default-off, forge#3518), or, on
72021
- * the legacy lane, by promoting a valid subject-level JWT or rotating the
72022
- * stored CMMD refresh token.
72023
- *
72024
- * Fail-closed: never returns a non-user / malformed Bearer. Callers must map
72025
- * null to CMMD_AUTH_REQUIRED and must not call CMMD upstream with a bad token.
72026
- */
72027
- function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth", forceRefresh = false, staleAccessToken = null }) {
72028
- const cached = usableCmmdUserJwt(readCommandJwtForSession(sessionId));
72029
- if (cached && !isJwtExpiredForSession(sessionId)) {
72030
- if (!forceRefresh) return require_Schema$1.succeed(cached);
72031
- if (staleAccessToken && cached !== staleAccessToken) return require_Schema$1.succeed(cached).pipe(require_Schema$1.tap(() => require_Schema$1.gen(function* () {
72032
- const deviceType = yield* readClientDeviceTypeForSession(sessionId);
72033
- yield* logCmmdJwtRung({
72034
- sessionId,
72035
- subject: subject?.trim() || null,
72036
- deviceType,
72037
- caller: logPrefix
72038
- }, "cache", "skipped", "forced refresh skipped: cache already holds a token another caller rotated");
72039
- })));
72040
- }
72041
- const normalizedSubject = subject?.trim() || null;
72042
- const mintLaneEnabled = isCmmdTokenMintLaneEnabled();
72043
- if (!normalizedSubject && !forceRefresh && !mintLaneEnabled) return require_Schema$1.succeed(null);
72044
- return require_Schema$1.gen(function* () {
72045
- const context = {
72046
- sessionId,
72047
- subject: normalizedSubject,
72048
- deviceType: yield* readClientDeviceTypeForSession(sessionId),
72049
- caller: logPrefix
72050
- };
72051
- if (mintLaneEnabled) {
72052
- const minted = yield* tryMintShortLivedToken(sessionId, context);
72053
- if (minted) return minted;
72054
- if (!normalizedSubject && !forceRefresh) {
72055
- yield* logCmmdJwtRung(context, "mint", "failed", "no subject to resolve and the mint lane produced nothing", "mint");
72056
- return null;
72057
- }
72058
- }
72059
- if (yield* isRotationFencedSession(sessionId)) {
72060
- yield* logCmmdJwtRung(context, "rotation-fence", "failed", "session is fenced from rotating CMMD credentials");
72061
- return null;
72062
- }
72063
- return yield* runRecoveryLadder(sessionId, normalizedSubject, forceRefresh, context);
72064
- });
72065
- }
72066
- //#endregion
72067
71645
  //#region src/cmmdData/cmmdRestProxyDeadline.ts
72068
71646
  var CmmdProxyTimeoutError = class extends Error {
72069
71647
  status;
@@ -102706,6 +102284,22 @@ var WorkspaceFileSystem = class extends require_Schema$1.Service()("forge/worksp
102706
102284
  //#region src/workspace/Services/WorkspaceBulkTransfer.ts
102707
102285
  var WorkspaceBulkTransfer = class extends require_Schema$1.Service()("forge/workspace/Services/WorkspaceBulkTransfer") {};
102708
102286
  //#endregion
102287
+ //#region src/workspace/Services/WorkspaceDirectoryListing.ts
102288
+ /**
102289
+ * WorkspaceDirectoryListing - Effect service contract for one directory's
102290
+ * immediate children.
102291
+ *
102292
+ * Owns workspace-root-confined directory listings for the Files tree: the
102293
+ * caller names a workspace root (the thread's checkout) and a root-relative
102294
+ * directory, and gets that directory's children back, one readdir deep.
102295
+ *
102296
+ * @module WorkspaceDirectoryListing
102297
+ */
102298
+ /**
102299
+ * WorkspaceDirectoryListing - Service tag for workspace directory listings.
102300
+ */
102301
+ var WorkspaceDirectoryListing = class extends require_Schema$1.Service()("forge/workspace/Services/WorkspaceDirectoryListing") {};
102302
+ //#endregion
102709
102303
  //#region src/workspace/Services/WorkspaceReadinessState.ts
102710
102304
  var WorkspaceReadinessState = class extends require_Schema$1.Service()("forge/workspace/Services/WorkspaceReadinessState") {};
102711
102305
  //#endregion
@@ -108119,6 +107713,7 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(r
108119
107713
  const discoverSourceControl = yield* makeCachedSourceControlDiscovery;
108120
107714
  const workspaceFileSystem = yield* WorkspaceFileSystem;
108121
107715
  const workspaceBulkTransfer = yield* WorkspaceBulkTransfer;
107716
+ const workspaceDirectoryListing = yield* WorkspaceDirectoryListing;
108122
107717
  const projectCheckoutDeletion = yield* require_Schema$1.serviceOption(ProjectCheckoutDeletion);
108123
107718
  const workspaceReadinessState = yield* WorkspaceReadinessState;
108124
107719
  const projectSetupScriptRunner = yield* ProjectSetupScriptRunner;
@@ -108468,6 +108063,7 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(r
108468
108063
  blockers: ["deletion_authority_unavailable"]
108469
108064
  }), { "rpc.aggregate": "workspace" }),
108470
108065
  [WS_METHODS.workspaceReadFile]: (input) => observeRpcStream(WS_METHODS.workspaceReadFile, workspaceBulkTransfer.readFile(input), { "rpc.aggregate": "workspace" }),
108066
+ [WS_METHODS.workspaceListDirectory]: (input) => observeRpcEffect(WS_METHODS.workspaceListDirectory, workspaceDirectoryListing.list(input), { "rpc.aggregate": "workspace" }),
108471
108067
  [WS_METHODS.workspaceBeginWriteFile]: (input) => observeRpcEffect(WS_METHODS.workspaceBeginWriteFile, workspaceBulkTransfer.beginWrite(input), { "rpc.aggregate": "workspace" }),
108472
108068
  [WS_METHODS.workspaceWriteFileChunk]: (input) => observeRpcEffect(WS_METHODS.workspaceWriteFileChunk, workspaceBulkTransfer.writeChunk(input), { "rpc.aggregate": "workspace" }),
108473
108069
  [WS_METHODS.workspaceCommitWriteFile]: (input) => observeRpcEffect(WS_METHODS.workspaceCommitWriteFile, workspaceBulkTransfer.commitWrite(input), { "rpc.aggregate": "workspace" }),
@@ -116285,7 +115881,7 @@ const postgresSchemaStatements = [
116285
115881
  "CREATE TABLE IF NOT EXISTS projection_thread_proposed_plans (\n plan_id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n turn_id TEXT,\n plan_markdown TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n , implemented_at TEXT, implementation_thread_id TEXT)",
116286
115882
  "CREATE TABLE IF NOT EXISTS auth_pairing_links (\n id TEXT PRIMARY KEY,\n credential TEXT NOT NULL UNIQUE,\n method TEXT NOT NULL,\n role TEXT NOT NULL,\n subject TEXT NOT NULL,\n created_at TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n consumed_at TEXT,\n revoked_at TEXT\n , label TEXT, credential_lookup TEXT)",
116287
115883
  "CREATE TABLE IF NOT EXISTS auth_sessions (\n session_id TEXT PRIMARY KEY,\n subject TEXT NOT NULL,\n role TEXT NOT NULL,\n method TEXT NOT NULL,\n issued_at TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n revoked_at TEXT\n , client_label TEXT, client_ip_address TEXT, client_user_agent TEXT, client_device_type TEXT NOT NULL DEFAULT 'unknown', client_os TEXT, client_browser TEXT, last_connected_at TEXT)",
116288
- "CREATE TABLE IF NOT EXISTS command_access_tokens (\n session_id TEXT PRIMARY KEY,\n access_token TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n , jwt_token TEXT, jwt_expires_at TEXT, refresh_token TEXT, cmmd_user_identity TEXT, session_grant TEXT, session_grant_expires_at TEXT)",
115884
+ "CREATE TABLE IF NOT EXISTS command_access_tokens (\n session_id TEXT PRIMARY KEY,\n access_token TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n , jwt_token TEXT, jwt_expires_at TEXT, cmmd_user_identity TEXT, session_grant TEXT, session_grant_expires_at TEXT)",
116289
115885
  "CREATE TABLE IF NOT EXISTS comms_accounts (\n id TEXT PRIMARY KEY,\n provider TEXT NOT NULL,\n email TEXT NOT NULL,\n display_name TEXT,\n composio_entity_id TEXT NOT NULL,\n composio_connected_account_id TEXT NOT NULL,\n is_active INTEGER NOT NULL DEFAULT 1,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )",
116290
115886
  "CREATE TABLE IF NOT EXISTS comms_messages (\n id TEXT PRIMARY KEY,\n provider TEXT NOT NULL,\n provider_message_id TEXT NOT NULL,\n provider_thread_id TEXT,\n account_id TEXT NOT NULL REFERENCES comms_accounts(id),\n direction TEXT NOT NULL DEFAULT 'inbound',\n from_address TEXT NOT NULL,\n from_name TEXT,\n to_addresses TEXT NOT NULL DEFAULT '[]',\n cc_addresses TEXT,\n subject TEXT,\n body_text TEXT,\n body_html TEXT,\n labels TEXT,\n sent_at TEXT NOT NULL,\n received_at TEXT,\n is_read INTEGER NOT NULL DEFAULT 0,\n is_starred INTEGER NOT NULL DEFAULT 0,\n has_attachments INTEGER NOT NULL DEFAULT 0,\n raw_payload TEXT,\n synced_at TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )",
116291
115887
  "CREATE TABLE IF NOT EXISTS comms_sync_cursors (\n id TEXT PRIMARY KEY,\n provider TEXT NOT NULL,\n account_id TEXT NOT NULL REFERENCES comms_accounts(id),\n cursor_type TEXT NOT NULL,\n cursor_value TEXT NOT NULL,\n last_sync_at TEXT NOT NULL,\n last_sync_status TEXT NOT NULL DEFAULT 'success',\n last_error TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )",
@@ -116598,7 +116194,8 @@ const postgresUpgradeMigrations = new Map([
116598
116194
  WHERE model_selection_json IS NULL`
116599
116195
  ]],
116600
116196
  [77, [`ALTER TABLE projection_threads ADD COLUMN IF NOT EXISTS work_session_id TEXT`]],
116601
- [78, [`ALTER TABLE projection_threads ADD COLUMN IF NOT EXISTS work_session_bound_at TEXT`]]
116197
+ [78, [`ALTER TABLE projection_threads ADD COLUMN IF NOT EXISTS work_session_bound_at TEXT`]],
116198
+ [79, [`ALTER TABLE command_access_tokens DROP COLUMN IF EXISTS refresh_token`]]
116602
116199
  ]);
116603
116200
  function requirePostgresUpgradeStatements(version) {
116604
116201
  const statements = postgresUpgradeMigrations.get(version);
@@ -116624,15 +116221,15 @@ const runPostgresMigrations = require_Schema$1.fn("runPostgresMigrations")(funct
116624
116221
  ORDER BY version DESC
116625
116222
  LIMIT 1
116626
116223
  `)[0]?.version ?? 0;
116627
- if (currentVersion > 78) return yield* require_Schema$1.die(/* @__PURE__ */ new Error(`Forge Postgres schema ${currentVersion} is newer than server schema 78.`));
116224
+ if (currentVersion > 79) return yield* require_Schema$1.die(/* @__PURE__ */ new Error(`Forge Postgres schema ${currentVersion} is newer than server schema 79.`));
116628
116225
  if (currentVersion === 0) {
116629
116226
  for (const statement of postgresSchemaStatements) yield* sql.unsafe(statement);
116630
116227
  yield* sql`
116631
116228
  INSERT INTO forge_schema_migrations (version)
116632
- VALUES (${78})
116229
+ VALUES (${79})
116633
116230
  ON CONFLICT (version) DO NOTHING
116634
116231
  `;
116635
- } else for (let version = currentVersion + 1; version <= 78; version += 1) {
116232
+ } else for (let version = currentVersion + 1; version <= 79; version += 1) {
116636
116233
  const statements = yield* require_Schema$1.sync(() => requirePostgresUpgradeStatements(version));
116637
116234
  for (const statement of statements) yield* sql.unsafe(statement);
116638
116235
  yield* sql`
@@ -119441,6 +119038,26 @@ var _078_ProjectionThreadWorkSessionBoundAt_default = require_Schema$1.gen(funct
119441
119038
  `;
119442
119039
  });
119443
119040
  //#endregion
119041
+ //#region src/persistence/Migrations/079_DropCommandRefreshToken.ts
119042
+ /**
119043
+ * Drop the single-use CMMD refresh token from the session credential row.
119044
+ *
119045
+ * The rotation it powered is gone. forge#3518 added the CMMD mint lane, and
119046
+ * forge#3521 deleted the legacy ladder that sat beside it: the rotation fence,
119047
+ * the refresh single-flight, self-refresh, grant redemption, the subject
119048
+ * fallback, and the desktop refresh sweep. The resolver now mints a short-lived
119049
+ * JWT from the row's durable, non-rotating session grant, so a credential whose
119050
+ * every use invalidates it has nothing left to do on this row.
119051
+ *
119052
+ * Nothing is copied out first. Every surviving read of the column was deleted
119053
+ * in the same change; the value is a dead secret, and the kind of secret best
119054
+ * kept nowhere. Historical installs that ran migration 034 need this to run
119055
+ * after it, not instead of it.
119056
+ */
119057
+ var _079_DropCommandRefreshToken_default = require_Schema$1.gen(function* () {
119058
+ yield* (yield* require_SqlError.SqlClient)`ALTER TABLE command_access_tokens DROP COLUMN refresh_token`;
119059
+ });
119060
+ //#endregion
119444
119061
  //#region src/persistence/Migrations.ts
119445
119062
  /**
119446
119063
  * MigrationsLive - Migration runner with inline loader
@@ -119851,6 +119468,11 @@ const migrationEntries = [
119851
119468
  78,
119852
119469
  "ProjectionThreadWorkSessionBoundAt",
119853
119470
  _078_ProjectionThreadWorkSessionBoundAt_default
119471
+ ],
119472
+ [
119473
+ 79,
119474
+ "DropCommandRefreshToken",
119475
+ _079_DropCommandRefreshToken_default
119854
119476
  ]
119855
119477
  ];
119856
119478
  const makeMigrationLoader = (throughId) => fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -298999,6 +298621,106 @@ const makeWorkspaceBulkTransfer = (options = {}) => require_Schema$1.gen(functio
298999
298621
  });
299000
298622
  const WorkspaceBulkTransferLive = require_Schema$1.effect(WorkspaceBulkTransfer, makeWorkspaceBulkTransfer());
299001
298623
  //#endregion
298624
+ //#region src/workspace/Layers/WorkspaceDirectoryListing.ts
298625
+ function isPathWithin(path, root, candidate) {
298626
+ const relative = path.relative(root, candidate);
298627
+ return relative.length === 0 || !relative.startsWith("..") && !path.isAbsolute(relative);
298628
+ }
298629
+ /**
298630
+ * Directories sort before files so the tree reads like every other file tree;
298631
+ * names compare case-insensitively, then by raw name so stable order survives
298632
+ * a platform that folds case differently.
298633
+ */
298634
+ function compareEntries(left, right) {
298635
+ if (left.kind !== right.kind) return left.kind === "directory" ? -1 : 1;
298636
+ const byName = left.name.localeCompare(right.name, void 0, { sensitivity: "accent" });
298637
+ return byName !== 0 ? byName : left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
298638
+ }
298639
+ const makeWorkspaceDirectoryListing = require_Schema$1.gen(function* () {
298640
+ const fileSystem = yield* require_Path.FileSystem;
298641
+ const path = yield* require_Path.Path;
298642
+ const workspacePaths = yield* WorkspacePaths;
298643
+ return { list: require_Schema$1.fn("WorkspaceDirectoryListing.list")(function* (input) {
298644
+ const workspaceRoot = yield* workspacePaths.normalizeWorkspaceRoot(input.cwd).pipe(require_Schema$1.mapError(() => new WorkspaceListDirectoryError({
298645
+ cwd: input.cwd,
298646
+ relativePath: input.relativePath,
298647
+ failure: "workspace_root_invalid",
298648
+ detail: `Workspace root is not a usable directory: ${input.cwd}`
298649
+ })));
298650
+ const trimmed = input.relativePath.trim();
298651
+ if (path.isAbsolute(trimmed)) return yield* new WorkspaceListDirectoryError({
298652
+ cwd: input.cwd,
298653
+ relativePath: input.relativePath,
298654
+ failure: "path_outside_workspace",
298655
+ detail: "Directory listings must use workspace-relative paths."
298656
+ });
298657
+ const resolved = trimmed.length === 0 ? {
298658
+ absolutePath: workspaceRoot,
298659
+ relativePath: ""
298660
+ } : yield* workspacePaths.resolveRelativePathWithinRoot({
298661
+ workspaceRoot,
298662
+ relativePath: trimmed
298663
+ }).pipe(require_Schema$1.mapError(() => new WorkspaceListDirectoryError({
298664
+ cwd: input.cwd,
298665
+ relativePath: input.relativePath,
298666
+ failure: "path_outside_workspace",
298667
+ detail: `Directory is outside the workspace: ${input.relativePath}`
298668
+ })));
298669
+ const canonicalRoot = yield* fileSystem.realPath(workspaceRoot).pipe(require_Schema$1.orDie);
298670
+ const targetRealPath = yield* fileSystem.realPath(resolved.absolutePath).pipe(require_Schema$1.catch_(() => require_Schema$1.succeed(null)));
298671
+ if (targetRealPath === null) return yield* new WorkspaceListDirectoryError({
298672
+ cwd: input.cwd,
298673
+ relativePath: input.relativePath,
298674
+ failure: "read_directory_failed",
298675
+ detail: `Directory does not exist: ${input.relativePath || "."}`
298676
+ });
298677
+ if (!isPathWithin(path, canonicalRoot, targetRealPath)) return yield* new WorkspaceListDirectoryError({
298678
+ cwd: input.cwd,
298679
+ relativePath: input.relativePath,
298680
+ failure: "path_outside_workspace",
298681
+ detail: `Directory does not resolve inside the workspace: ${input.relativePath}`
298682
+ });
298683
+ const targetStat = yield* fileSystem.stat(targetRealPath).pipe(require_Schema$1.option);
298684
+ if (targetStat._tag === "None" || targetStat.value.type !== "Directory") return yield* new WorkspaceListDirectoryError({
298685
+ cwd: input.cwd,
298686
+ relativePath: input.relativePath,
298687
+ failure: "read_directory_failed",
298688
+ detail: `Not a directory: ${input.relativePath}`
298689
+ });
298690
+ const names = yield* fileSystem.readDirectory(targetRealPath).pipe(require_Schema$1.mapError((cause) => new WorkspaceListDirectoryError({
298691
+ cwd: input.cwd,
298692
+ relativePath: input.relativePath,
298693
+ failure: "read_directory_failed",
298694
+ detail: `Could not read directory: ${input.relativePath}`,
298695
+ cause
298696
+ })));
298697
+ return { entries: (yield* require_Schema$1.forEach(names, (name) => require_Schema$1.gen(function* () {
298698
+ const entryPath = path.join(targetRealPath, name);
298699
+ if ((yield* fileSystem.readLink(entryPath).pipe(require_Schema$1.option))._tag === "Some") return {
298700
+ name,
298701
+ kind: "file",
298702
+ hasChildren: false
298703
+ };
298704
+ const stat = yield* fileSystem.stat(entryPath).pipe(require_Schema$1.option);
298705
+ if (stat._tag === "None") return void 0;
298706
+ if (stat.value.type === "Directory") {
298707
+ const children = yield* fileSystem.readDirectory(entryPath).pipe(require_Schema$1.option);
298708
+ return {
298709
+ name,
298710
+ kind: "directory",
298711
+ hasChildren: children._tag === "Some" && children.value.length > 0
298712
+ };
298713
+ }
298714
+ return {
298715
+ name,
298716
+ kind: "file",
298717
+ hasChildren: false
298718
+ };
298719
+ }), { concurrency: 16 })).filter((entry) => entry !== void 0).toSorted(compareEntries) };
298720
+ }) };
298721
+ });
298722
+ const WorkspaceDirectoryListingLive = require_Schema$1.effect(WorkspaceDirectoryListing, makeWorkspaceDirectoryListing);
298723
+ //#endregion
299002
298724
  //#region src/workspace/Layers/WorkspacePaths.ts
299003
298725
  function toPosixRelativePath(input) {
299004
298726
  return input.replaceAll("\\", "/");
@@ -300611,7 +300333,7 @@ function resolveBuildCommitFromEnv(env) {
300611
300333
  * environment descriptor down instead of reporting an honest "unknown".
300612
300334
  */
300613
300335
  function readBakedBuildCommit() {
300614
- return "84e1e008ce3e3d30e5e80361598aa54ed7bda92d";
300336
+ return "15aa06fedcdcf9e165622cf018e31ce724b98ef7";
300615
300337
  }
300616
300338
  async function resolveServerBuildCommit(input) {
300617
300339
  if (isFullCommitSha(input.baked)) return input.baked;
@@ -300859,14 +300581,13 @@ function identityFromRedeemed(redeemed) {
300859
300581
  username: redeemed.displayName?.trim() || `user-${redeemed.userId}`
300860
300582
  };
300861
300583
  }
300584
+ /** Read optional display name, owner assertion, session grant, and expiry from a redeemed handoff. */
300862
300585
  function optionalRedeemedFields(body) {
300863
- const refreshToken = readTrimmedString(body.refreshToken);
300864
300586
  const displayName = typeof body.displayName === "string" ? body.displayName : void 0;
300865
300587
  const forgeOwnerAssertion = readTrimmedString(body.forgeOwnerAssertion);
300866
300588
  const sessionGrant = readTrimmedString(body.sessionGrant);
300867
300589
  const sessionGrantExpiresAt = typeof body.sessionGrantExpiresAt === "string" ? body.sessionGrantExpiresAt : void 0;
300868
300590
  return {
300869
- ...refreshToken !== void 0 ? { refreshToken } : {},
300870
300591
  ...displayName !== void 0 ? { displayName } : {},
300871
300592
  ...forgeOwnerAssertion !== void 0 ? { forgeOwnerAssertion } : {},
300872
300593
  ...sessionGrant !== void 0 ? { sessionGrant } : {},
@@ -300934,6 +300655,7 @@ function redeemEmbedCodeWithCmmd(code) {
300934
300655
  return require_Schema$1.succeed(redeemed);
300935
300656
  }));
300936
300657
  }
300658
+ /** Store the redeemed PAT, JWT, and identity for a local embed session, failing on write errors. */
300937
300659
  function persistLocalEmbedSession(input) {
300938
300660
  return require_Schema$1.gen(function* () {
300939
300661
  yield* setCommandAccessTokenForSession(input.sessionId, input.redeemed.mcpToken).pipe(require_Schema$1.mapError((cause) => new AuthError({
@@ -300941,7 +300663,7 @@ function persistLocalEmbedSession(input) {
300941
300663
  status: 500,
300942
300664
  cause
300943
300665
  })));
300944
- yield* setCommandJwtForSession(input.sessionId, input.redeemed.accessToken, input.redeemed.refreshToken ? { refreshToken: input.redeemed.refreshToken } : {}).pipe(require_Schema$1.mapError((cause) => new AuthError({
300666
+ yield* setCommandJwtForSession(input.sessionId, input.redeemed.accessToken).pipe(require_Schema$1.mapError((cause) => new AuthError({
300945
300667
  message: "Failed to persist CMMD JWT.",
300946
300668
  status: 500,
300947
300669
  cause
@@ -300996,7 +300718,6 @@ const authCmmdEmbedExchangeRouteLayer = add$1("POST", "/api/auth/cmmd-embed/exch
300996
300718
  accessToken: redeemed.mcpToken,
300997
300719
  identity,
300998
300720
  jwtToken: redeemed.accessToken,
300999
- refreshToken: redeemed.refreshToken,
301000
300721
  sessionGrant,
301001
300722
  sessionGrantExpiresAt: redeemed.sessionGrantExpiresAt
301002
300723
  });
@@ -301102,7 +300823,7 @@ const cloudOwnerSessionRedeemRouteLayer = add$1("POST", "/api/auth/cloud/session
301102
300823
  status: 403
301103
300824
  });
301104
300825
  const tokenResult = yield* redeemSessionGrantWithCmmd(payload);
301105
- const { tokenObj, cmmdIdentity, userId, accessToken, mcpToken, refreshToken, forgeOwnerAssertion, sessionGrantExpiresAt, isJwt, patToStore, jwtToStore } = yield* resolveCmmdTokenBundleCredentials(tokenResult);
300826
+ const { tokenObj, cmmdIdentity, userId, accessToken, mcpToken, forgeOwnerAssertion, sessionGrantExpiresAt, isJwt, patToStore, jwtToStore } = yield* resolveCmmdTokenBundleCredentials(tokenResult);
301106
300827
  if (!cmmdIdentity || !userId || !patToStore) {
301107
300828
  yield* require_Schema$1.logError("Forge Cloud session redeem: CMMD token response incomplete.", {
301108
300829
  clientId: payload.clientId,
@@ -301111,7 +300832,6 @@ const cloudOwnerSessionRedeemRouteLayer = add$1("POST", "/api/auth/cloud/session
301111
300832
  tokenResult,
301112
300833
  accessToken,
301113
300834
  mcpToken,
301114
- refreshToken,
301115
300835
  isJwt,
301116
300836
  userId,
301117
300837
  patToStore,
@@ -301134,7 +300854,6 @@ const cloudOwnerSessionRedeemRouteLayer = add$1("POST", "/api/auth/cloud/session
301134
300854
  accessToken: patToStore,
301135
300855
  identity: cmmdIdentity,
301136
300856
  jwtToken: jwtToStore,
301137
- refreshToken,
301138
300857
  sessionGrant: payload.sessionGrant,
301139
300858
  sessionGrantExpiresAt
301140
300859
  });
@@ -325264,7 +324983,7 @@ const ProjectLeadSchedulerRuntimeLayerLive = makeProjectLeadSchedulerRuntimeLive
325264
324983
  const GitManagerLayerLive = GitManagerLive.pipe(require_Schema$1.provideMerge(ProjectSetupScriptRunnerLive), require_Schema$1.provideMerge(GitCoreLive), require_Schema$1.provideMerge(GitHubCliLive), require_Schema$1.provideMerge(RoutingTextGenerationLive.pipe(require_Schema$1.provide$1(TextGenerationUsageLive.pipe(require_Schema$1.provide$1(RuntimeReplicationOutboxLive), require_Schema$1.provide$1(TextGenerationSessionRepositoryLive))))));
325265
324984
  const GitLayerLive = require_Schema$1.empty$1.pipe(require_Schema$1.provideMerge(GitManagerLayerLive), require_Schema$1.provideMerge(GitStatusBroadcasterLive.pipe(require_Schema$1.provide$1(GitManagerLayerLive))), require_Schema$1.provideMerge(GitCoreLive));
325266
324985
  const TerminalLayerLive = TerminalManagerLive.pipe(require_Schema$1.provide$1(PtyAdapterLive));
325267
- const WorkspaceLayerLive = require_Schema$1.mergeAll(WorkspacePathsLive, ProjectCheckoutDeletionLive.pipe(require_Schema$1.provide$1(GitCoreLive), require_Schema$1.provide$1(ProjectionProjectRepositoryLive)), WorkspaceEntriesLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)), WorkspaceFileSystemLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive), require_Schema$1.provide$1(WorkspaceEntriesLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)))), WorkspaceBulkTransferLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive), require_Schema$1.provide$1(WorkspaceEntriesLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)))));
324986
+ const WorkspaceLayerLive = require_Schema$1.mergeAll(WorkspacePathsLive, ProjectCheckoutDeletionLive.pipe(require_Schema$1.provide$1(GitCoreLive), require_Schema$1.provide$1(ProjectionProjectRepositoryLive)), WorkspaceEntriesLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)), WorkspaceFileSystemLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive), require_Schema$1.provide$1(WorkspaceEntriesLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)))), WorkspaceBulkTransferLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive), require_Schema$1.provide$1(WorkspaceEntriesLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)))), WorkspaceDirectoryListingLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)));
325268
324987
  const AuthLayerLive = ServerAuthLive.pipe(require_Schema$1.provideMerge(PersistenceLayerLive), require_Schema$1.provide$1(ServerSecretStoreLayerLive));
325269
324988
  const MiscRuntimeLayerLive = require_Schema$1.mergeAll(AnalyticsServiceLayerLive, OpenLive, ServerLifecycleEventsLive, layer$4);
325270
324989
  const RuntimeDependenciesWithoutPullRequestsLive = ReactorLayerLive.pipe(require_Schema$1.provideMerge(CheckpointingLayerLive), require_Schema$1.provideMerge(GitLayerLive), require_Schema$1.provideMerge(OrchestrationLayerLive), require_Schema$1.provideMerge(ProviderLayerLive), require_Schema$1.provideMerge(TerminalLayerLive), require_Schema$1.provideMerge(AgentBrowserManagerLive), require_Schema$1.provideMerge(require_Schema$1.mergeAll(ThreadDevRuntimeLive, DevTunnelLive.pipe(require_Schema$1.provide$1(ThreadDevRuntimeLive)))), require_Schema$1.provideMerge(PersistenceLayerLive), require_Schema$1.provideMerge(KeybindingsLive), require_Schema$1.provideMerge(ProviderRegistryLive), require_Schema$1.provideMerge(require_Schema$1.mergeAll(ServerSettingsLive, UsageServiceLive.pipe(require_Schema$1.provide$1(ServerSettingsLive), require_Schema$1.provide$1(UsageSessionLookupLive), require_Schema$1.provide$1(UsageRecordedSourceLive.pipe(require_Schema$1.provide$1(OrchestrationLayerLive), require_Schema$1.provide$1(RuntimeReplicationOutboxLive), require_Schema$1.provide$1(ProviderSessionRuntimeRepositoryLive), require_Schema$1.provide$1(TextGenerationSessionRepositoryLive)))))), require_Schema$1.provideMerge(WorkspaceLayerLive), require_Schema$1.provideMerge(WorkspaceReadinessStateLive), require_Schema$1.provideMerge(ProjectFaviconResolverLive), require_Schema$1.provideMerge(RepositoryIdentityResolverLive), require_Schema$1.provideMerge(ServerEnvironmentLive), require_Schema$1.provideMerge(AuthLayerLive), require_Schema$1.provideMerge(ServerSecretStoreLayerLive), require_Schema$1.provideMerge(require_Schema$1.mergeAll(CloudRedisCoordinationLive, CloudStateSyncLive.pipe(require_Schema$1.provide$1(PersistenceLayerLive)))), require_Schema$1.provideMerge(MiscRuntimeLayerLive));