@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.mjs CHANGED
@@ -20558,6 +20558,42 @@ var FilesystemBrowseError = class extends TaggedErrorClass()("FilesystemBrowseEr
20558
20558
  }
20559
20559
  };
20560
20560
  //#endregion
20561
+ //#region ../../packages/contracts/src/workspaceDirectory.ts
20562
+ const WORKSPACE_DIRECTORY_PATH_MAX_LENGTH = 512;
20563
+ /**
20564
+ * One immediate child of a listed workspace directory.
20565
+ *
20566
+ * `hasChildren` is what lets the Files tree draw an expander only on
20567
+ * directories that open onto something, without listing the whole workspace
20568
+ * up front: a directory entry is one stat-sized readdir, not a recursive walk.
20569
+ */
20570
+ const WorkspaceDirectoryEntry = Struct({
20571
+ name: TrimmedNonEmptyString,
20572
+ kind: Literals(["file", "directory"]),
20573
+ hasChildren: Boolean$1
20574
+ });
20575
+ const WorkspaceListDirectoryInput = Struct({
20576
+ cwd: TrimmedNonEmptyString,
20577
+ relativePath: TrimmedString.check(isMaxLength(WORKSPACE_DIRECTORY_PATH_MAX_LENGTH))
20578
+ });
20579
+ const WorkspaceListDirectoryResult = Struct({ entries: ArraySchema(WorkspaceDirectoryEntry) });
20580
+ const WorkspaceListDirectoryFailure = Literals([
20581
+ "workspace_root_invalid",
20582
+ "path_outside_workspace",
20583
+ "read_directory_failed"
20584
+ ]);
20585
+ var WorkspaceListDirectoryError = class extends TaggedErrorClass()("WorkspaceListDirectoryError", {
20586
+ cwd: String$1,
20587
+ relativePath: String$1,
20588
+ failure: WorkspaceListDirectoryFailure,
20589
+ detail: String$1,
20590
+ cause: optional$4(Defect)
20591
+ }) {
20592
+ get message() {
20593
+ return this.detail;
20594
+ }
20595
+ };
20596
+ //#endregion
20561
20597
  //#region ../../packages/contracts/src/terminal.ts
20562
20598
  const DEFAULT_TERMINAL_ID = "default";
20563
20599
  const TrimmedNonEmptyStringSchema$3 = TrimmedNonEmptyString;
@@ -26009,6 +26045,7 @@ const WS_METHODS = {
26009
26045
  projectsWriteFile: "projects.writeFile",
26010
26046
  projectCheckoutDeletionPreview: "projectCheckoutDeletion.preview",
26011
26047
  projectCheckoutDeletionExecute: "projectCheckoutDeletion.execute",
26048
+ workspaceListDirectory: "workspace.listDirectory",
26012
26049
  workspaceReadFile: "workspace.readFile",
26013
26050
  workspaceBeginWriteFile: "workspace.beginWriteFile",
26014
26051
  workspaceWriteFileChunk: "workspace.writeFileChunk",
@@ -26320,6 +26357,17 @@ const WsWorkspaceCancelWriteFileRpc = make$29(WS_METHODS.workspaceCancelWriteFil
26320
26357
  success: BulkTransferCancelResult,
26321
26358
  error: BulkTransferError
26322
26359
  });
26360
+ /**
26361
+ * One workspace directory's immediate children, for the right panel's Files
26362
+ * tree. Read-scoped and confined to the workspace root on the server; the tree
26363
+ * expands one directory at a time through this method rather than listing the
26364
+ * whole workspace up front.
26365
+ */
26366
+ const WsWorkspaceListDirectoryRpc = make$29(WS_METHODS.workspaceListDirectory, {
26367
+ payload: WorkspaceListDirectoryInput,
26368
+ success: WorkspaceListDirectoryResult,
26369
+ error: WorkspaceListDirectoryError
26370
+ });
26323
26371
  const WsShellOpenInEditorRpc = make$29(WS_METHODS.shellOpenInEditor, {
26324
26372
  payload: OpenInEditorInput,
26325
26373
  error: OpenError$1
@@ -26743,7 +26791,7 @@ const WsRpcGroup = make$28(WsServerGetConfigRpc, WsServerRefreshProvidersRpc, Ws
26743
26791
  payload: UsageSummaryInput,
26744
26792
  success: UsageSummary,
26745
26793
  error: UsageReadError
26746
- }), 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, {
26794
+ }), 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, {
26747
26795
  payload: AssetsRpcSchemas.createUrl.input,
26748
26796
  success: AssetsRpcSchemas.createUrl.output,
26749
26797
  error: AssetAccessError
@@ -31485,6 +31533,11 @@ const RELAY_METHOD_POLICY_BASES = [
31485
31533
  requiredScope: "workspace.read",
31486
31534
  deliveryClass: "read"
31487
31535
  },
31536
+ {
31537
+ method: WS_METHODS.workspaceListDirectory,
31538
+ requiredScope: "workspace.read",
31539
+ deliveryClass: "read"
31540
+ },
31488
31541
  {
31489
31542
  method: WS_METHODS.workspaceReadFile,
31490
31543
  requiredScope: "workspace.read",
@@ -66062,7 +66115,7 @@ function normalizeNumberish(value) {
66062
66115
  }
66063
66116
  //#endregion
66064
66117
  //#region package.json
66065
- var version$1 = "0.13.131";
66118
+ var version$1 = "0.13.133";
66066
66119
  //#endregion
66067
66120
  //#region src/sentry.ts
66068
66121
  const SERVER_APP_NAME = "forge-server";
@@ -67021,9 +67074,7 @@ const runtimeReplicationOutboxOldestEventAge = gauge("t3_runtime_replication_out
67021
67074
  const runtimeReplicationPublicationAttemptsTotal = counter("t3_runtime_replication_publication_attempts_total", { description: "Total canonical runtime replication publication attempts." });
67022
67075
  const runtimeReplicationPublicationRetriesTotal = counter("t3_runtime_replication_publication_retries_total", { description: "Total runtime replication retries scheduled after recoverable failures." });
67023
67076
  const runtimeReplicationPublicationLatency = timer("t3_runtime_replication_publication_latency", { description: "Time from durable enqueue to canonical CMMD acknowledgement." });
67024
- const cmmdCredentialRefreshTotal = counter("t3_cmmd_credential_refresh_total", { description: "CMMD JWT refresh attempts for a Forge session, by rung (self-refresh | grant-redemption) and outcome (attempted | succeeded)." });
67025
- const cmmdTokenMintTotal = counter("t3_cmmd_token_mint_total", { description: "CMMD short-lived forge-token mints through the mint lane (forge#3518), by outcome (attempted | succeeded). The legacy rotation ladder keeps reporting t3_cmmd_credential_refresh_total, so the two lanes stay separable in every dashboard." });
67026
- const cmmdRotationFenceAppliedTotal = counter("t3_cmmd_rotation_fence_applied_total", { description: "Sessions Forge refused to rotate CMMD credentials for, because the rotation fence applied." });
67077
+ const cmmdTokenMintTotal = 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." });
67027
67078
  const cmmdCredentialRowDeletedTotal = counter("t3_cmmd_credential_row_deleted_total", { description: "command_access_tokens rows actually deleted by clearUnrecoverableCmmdAuthForSession, by reason." });
67028
67079
  const metricAttributes = (attributes) => Object.entries(compactMetricAttributes(attributes));
67029
67080
  const increment = (metric, attributes, amount = 1) => update$3(withAttributes(metric, metricAttributes(attributes)), amount);
@@ -67104,12 +67155,10 @@ const isPersistenceError = (u) => is$1(PersistenceSqlError)(u) || is$1(Persisten
67104
67155
  const cache = /* @__PURE__ */ new Map();
67105
67156
  const jwtCache = /* @__PURE__ */ new Map();
67106
67157
  const jwtExpiryCache = /* @__PURE__ */ new Map();
67107
- const refreshTokenCache = /* @__PURE__ */ new Map();
67108
67158
  const identityCache = /* @__PURE__ */ new Map();
67109
67159
  const sessionGrantCache = /* @__PURE__ */ new Map();
67110
67160
  const ACCESS_TOKEN_SCOPE = "cmmd-access-token";
67111
67161
  const JWT_TOKEN_SCOPE = "cmmd-jwt-token";
67112
- const REFRESH_TOKEN_SCOPE = "cmmd-refresh-token";
67113
67162
  const IDENTITY_SCOPE = "cmmd-user-identity";
67114
67163
  const SESSION_GRANT_SCOPE = "cmmd-session-grant";
67115
67164
  const CommandAccessTokenRow = Struct({
@@ -67117,27 +67166,15 @@ const CommandAccessTokenRow = Struct({
67117
67166
  accessToken: String$1,
67118
67167
  jwtToken: NullOr(String$1),
67119
67168
  jwtExpiresAt: NullOr(String$1),
67120
- refreshToken: NullOr(String$1),
67121
67169
  cmmdUserIdentity: NullOr(String$1),
67122
67170
  sessionGrant: NullOr(String$1)
67123
67171
  });
67124
67172
  const SubjectCommandAccessTokenRow = Struct({ accessToken: String$1 });
67125
- const SubjectJwtRow = Struct({
67126
- accessToken: String$1,
67127
- jwtToken: NullOr(String$1),
67128
- jwtExpiresAt: NullOr(String$1),
67129
- refreshToken: NullOr(String$1),
67130
- sessionId: String$1
67131
- });
67132
- const SubjectRefreshTokenRow = Struct({
67133
- refreshToken: String$1,
67134
- sessionId: String$1
67135
- });
67136
67173
  const OwnerCommandAuthBundleRow = Struct({
67137
67174
  accessToken: String$1,
67138
67175
  jwtToken: NullOr(String$1),
67139
67176
  jwtExpiresAt: NullOr(String$1),
67140
- refreshToken: NullOr(String$1),
67177
+ sessionGrant: NullOr(String$1),
67141
67178
  sessionId: String$1,
67142
67179
  cmmdUserIdentity: String$1
67143
67180
  });
@@ -67182,8 +67219,6 @@ function decryptNullable(scope, value) {
67182
67219
  }
67183
67220
  const decodeTokenRows = decodeUnknownEffect(ArraySchema(CommandAccessTokenRow));
67184
67221
  const decodeSubjectTokenRows = decodeUnknownEffect(ArraySchema(SubjectCommandAccessTokenRow));
67185
- const decodeSubjectJwtRows = decodeUnknownEffect(ArraySchema(SubjectJwtRow));
67186
- decodeUnknownEffect(ArraySchema(SubjectRefreshTokenRow));
67187
67222
  const decodeOwnerCommandAuthBundleRows = decodeUnknownEffect(ArraySchema(OwnerCommandAuthBundleRow));
67188
67223
  const decodeOwnerCommandAuthSessionRows = decodeUnknownEffect(ArraySchema(OwnerCommandAuthSessionRow));
67189
67224
  const decodeCloudOwnerSessionRows = decodeUnknownEffect(ArraySchema(CloudOwnerSessionRow));
@@ -67209,9 +67244,9 @@ function extractJwtExpiresAt(jwtToken) {
67209
67244
  * Comparing against `exp` exactly leaves a window where Forge sends a token it
67210
67245
  * believes is good and CMMD has already stopped accepting it: the clocks differ by a
67211
67246
  * little, the request spends time in flight, and the answer comes back 401 on a
67212
- * session that had a perfectly good refresh token sitting next to it. Two
67213
- * minutes is far inside the JWT's own lifetime, so this costs one early
67214
- * rotation and removes the race.
67247
+ * session whose grant could have minted a good replacement. Two minutes is far
67248
+ * inside the JWT's own lifetime, so this costs one early mint and removes the
67249
+ * race.
67215
67250
  */
67216
67251
  const JWT_EXPIRY_SKEW_MS = 2 * 6e4;
67217
67252
  /**
@@ -67237,7 +67272,6 @@ const hydrateCommandAccessTokenStore = gen(function* () {
67237
67272
  access_token AS "accessToken",
67238
67273
  jwt_token AS "jwtToken",
67239
67274
  jwt_expires_at AS "jwtExpiresAt",
67240
- refresh_token AS "refreshToken",
67241
67275
  cmmd_user_identity AS "cmmdUserIdentity",
67242
67276
  session_grant AS "sessionGrant"
67243
67277
  FROM command_access_tokens
@@ -67245,7 +67279,6 @@ const hydrateCommandAccessTokenStore = gen(function* () {
67245
67279
  cache.clear();
67246
67280
  jwtCache.clear();
67247
67281
  jwtExpiryCache.clear();
67248
- refreshTokenCache.clear();
67249
67282
  identityCache.clear();
67250
67283
  sessionGrantCache.clear();
67251
67284
  yield* try_({
@@ -67256,8 +67289,6 @@ const hydrateCommandAccessTokenStore = gen(function* () {
67256
67289
  const jwtToken = decryptNullable(JWT_TOKEN_SCOPE, row.jwtToken);
67257
67290
  if (jwtToken) jwtCache.set(sessionId, jwtToken);
67258
67291
  if (row.jwtExpiresAt) jwtExpiryCache.set(sessionId, row.jwtExpiresAt);
67259
- const refreshToken = decryptNullable(REFRESH_TOKEN_SCOPE, row.refreshToken);
67260
- if (refreshToken) refreshTokenCache.set(sessionId, refreshToken);
67261
67292
  const identity = decodeCmmdUserIdentity(decryptNullable(IDENTITY_SCOPE, row.cmmdUserIdentity));
67262
67293
  if (identity) identityCache.set(sessionId, identity);
67263
67294
  const sessionGrant = decryptNullable(SESSION_GRANT_SCOPE, row.sessionGrant);
@@ -67364,19 +67395,11 @@ function readCommandJwtForSession(sessionId) {
67364
67395
  return jwtCache.get(sessionId) ?? null;
67365
67396
  }
67366
67397
  /**
67367
- * Read the CMMD OAuth refresh token for a session. Sync; hits the in-memory
67368
- * cache only. Used to rotate a REST JWT after CMMD rejects a locally cached JWT
67369
- * that still appears unexpired.
67370
- */
67371
- function readCommandRefreshTokenForSession(sessionId) {
67372
- return refreshTokenCache.get(sessionId) ?? null;
67373
- }
67374
- /**
67375
67398
  * The durable CMMD session grant for a session, if one was issued.
67376
67399
  *
67377
- * Unlike the refresh token this is NON-rotating: redeeming it does not
67378
- * invalidate it, so it stays valid until CMMD expires or revokes it. That is
67379
- * what makes it a safe recovery credential after a rotation is lost.
67400
+ * The grant is NON-rotating: minting from it does not invalidate it, so it
67401
+ * stays valid until CMMD expires or revokes it. That is what makes it a safe
67402
+ * long-lived credential: the short-lived JWT it mints can always be replaced.
67380
67403
  */
67381
67404
  function readCommandSessionGrantForSession(sessionId) {
67382
67405
  return sessionGrantCache.get(sessionId) ?? null;
@@ -67426,77 +67449,26 @@ const setCmmdUserIdentityForSession = (sessionId, identity) => gen(function* ()
67426
67449
  * `jwt_expires_at` so callers can detect when the token has expired.
67427
67450
  * Updates only the JWT columns; the PAT row must already exist
67428
67451
  * (call `setCommandAccessTokenForSession` first in the OAuth callback).
67452
+ *
67453
+ * The JWT is short-lived by contract (CMMD clamps the mint TTL to one hour);
67454
+ * replacing one is the resolver's mint, so no sibling credential rides along.
67429
67455
  */
67430
- const setCommandJwtForSession = (sessionId, jwtToken, opts = {}) => gen(function* () {
67456
+ const setCommandJwtForSession = (sessionId, jwtToken) => gen(function* () {
67431
67457
  const token = jwtToken.trim();
67432
67458
  if (token.length === 0) return;
67433
67459
  const expiresAt = extractJwtExpiresAt(token);
67434
- const refreshToken = opts.refreshToken?.trim();
67435
67460
  const storedToken = encodeSensitiveValue(JWT_TOKEN_SCOPE, token);
67436
- const storedRefreshToken = refreshToken ? encodeSensitiveValue(REFRESH_TOKEN_SCOPE, refreshToken) : null;
67437
- const sql = yield* SqlClient;
67438
- const now = (/* @__PURE__ */ new Date()).toISOString();
67439
- if (refreshToken) yield* sql`
67440
- UPDATE command_access_tokens
67441
- SET
67442
- jwt_token = ${storedToken},
67443
- jwt_expires_at = ${expiresAt},
67444
- refresh_token = ${storedRefreshToken},
67445
- updated_at = ${now}
67446
- WHERE session_id = ${sessionId}
67447
- `.pipe(mapError(toPersistenceSqlError("CommandAccessTokenStore.setJwt:query")));
67448
- else yield* sql`
67449
- UPDATE command_access_tokens
67450
- SET
67451
- jwt_token = ${storedToken},
67452
- jwt_expires_at = ${expiresAt},
67453
- updated_at = ${now}
67454
- WHERE session_id = ${sessionId}
67455
- `.pipe(mapError(toPersistenceSqlError("CommandAccessTokenStore.setJwt:query")));
67461
+ yield* (yield* SqlClient)`
67462
+ UPDATE command_access_tokens
67463
+ SET
67464
+ jwt_token = ${storedToken},
67465
+ jwt_expires_at = ${expiresAt},
67466
+ updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
67467
+ WHERE session_id = ${sessionId}
67468
+ `.pipe(mapError(toPersistenceSqlError("CommandAccessTokenStore.setJwt:query")));
67456
67469
  jwtCache.set(sessionId, token);
67457
67470
  if (expiresAt) jwtExpiryCache.set(sessionId, expiresAt);
67458
67471
  else jwtExpiryCache.delete(sessionId);
67459
- if (refreshToken) refreshTokenCache.set(sessionId, refreshToken);
67460
- });
67461
- /**
67462
- * Look up the most recent non-expired JWT for an auth subject across active
67463
- * owner sessions. Used as a continuity fallback when the current session ID
67464
- * changed (e.g. after re-authentication) but the user's CMMD JWT should still
67465
- * be usable.
67466
- *
67467
- * Also returns the sessionId that owns the JWT so callers can promote it to
67468
- * the current session, avoiding repeated cross-session lookups.
67469
- */
67470
- const readMostRecentJwtForSubject = (subject) => gen(function* () {
67471
- const row = (yield* decodeSubjectJwtRows(yield* (yield* SqlClient)`
67472
- SELECT
67473
- cat.access_token AS "accessToken",
67474
- cat.jwt_token AS "jwtToken",
67475
- cat.jwt_expires_at AS "jwtExpiresAt",
67476
- cat.refresh_token AS "refreshToken",
67477
- cat.session_id AS "sessionId"
67478
- FROM command_access_tokens AS cat
67479
- JOIN auth_sessions AS s ON s.session_id = cat.session_id
67480
- WHERE s.subject = ${subject}
67481
- AND s.role = 'owner'
67482
- AND s.revoked_at IS NULL
67483
- AND cat.jwt_token IS NOT NULL
67484
- AND cat.jwt_expires_at IS NOT NULL
67485
- AND cat.jwt_expires_at > ${(/* @__PURE__ */ new Date()).toISOString()}
67486
- ORDER BY s.issued_at DESC
67487
- LIMIT 1
67488
- `.pipe(mapError(toPersistenceSqlError("CommandAccessTokenStore.readMostRecentJwtBySubject:query")))).pipe(mapError(toPersistenceDecodeError("CommandAccessTokenStore.readMostRecentJwtBySubject:decodeRows"))))[0];
67489
- if (!row?.jwtToken) return null;
67490
- const jwtToken = decodeSensitiveValue(JWT_TOKEN_SCOPE, row.jwtToken);
67491
- const refreshToken = decryptNullable(REFRESH_TOKEN_SCOPE, row.refreshToken);
67492
- const accessToken = decodeSensitiveValue(ACCESS_TOKEN_SCOPE, row.accessToken);
67493
- return {
67494
- jwt: jwtToken,
67495
- sessionId: row.sessionId,
67496
- expiresAt: row.jwtExpiresAt ?? null,
67497
- refreshToken,
67498
- accessToken
67499
- };
67500
67472
  });
67501
67473
  /**
67502
67474
  * Look up the latest CMMD PAT for an auth subject across active owner sessions.
@@ -67535,7 +67507,7 @@ const readMostRecentCommandAccessTokenForSubject = (subject) => gen(function* ()
67535
67507
  * `desktop-bootstrap` session that minted them, and nothing later rewrites that
67536
67508
  * subject. So the clause matched no row on any machine, promotion always
67537
67509
  * returned null, and a phone whose short-lived JWT had expired could not reach
67538
- * the refresh token sitting on its own previous session. It surfaced as "This
67510
+ * the credentials sitting on its own previous session. It surfaced as "This
67539
67511
  * session is no longer accepted. Sign in again." about a day after signing in,
67540
67512
  * with nothing in the log, because the ladder never got as far as a call that
67541
67513
  * could fail out loud.
@@ -67596,6 +67568,9 @@ const readMostRecentOwnerCommandAuthSession = gen(function* () {
67596
67568
  * a CMMD user subject. The CMMD token is still account-owned, so the trusted
67597
67569
  * local owner may inherit the most recent active owner token with an identity.
67598
67570
  * Client pairings must not call this.
67571
+ *
67572
+ * The copy includes the session grant: the promoted session can then mint its
67573
+ * own short-lived JWT, which since forge#3521 is the only refresh story.
67599
67574
  */
67600
67575
  const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => gen(function* () {
67601
67576
  const sql = yield* SqlClient;
@@ -67604,7 +67579,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => gen(function*
67604
67579
  cat.access_token AS "accessToken",
67605
67580
  cat.jwt_token AS "jwtToken",
67606
67581
  cat.jwt_expires_at AS "jwtExpiresAt",
67607
- cat.refresh_token AS "refreshToken",
67582
+ cat.session_grant AS "sessionGrant",
67608
67583
  cat.session_id AS "sessionId",
67609
67584
  cat.cmmd_user_identity AS "cmmdUserIdentity"
67610
67585
  FROM command_access_tokens AS cat
@@ -67616,7 +67591,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => gen(function*
67616
67591
  if (!row) return null;
67617
67592
  const accessToken = decodeSensitiveValue(ACCESS_TOKEN_SCOPE, row.accessToken);
67618
67593
  const jwtToken = decryptNullable(JWT_TOKEN_SCOPE, row.jwtToken);
67619
- const refreshToken = decryptNullable(REFRESH_TOKEN_SCOPE, row.refreshToken);
67594
+ const sessionGrant = decryptNullable(SESSION_GRANT_SCOPE, row.sessionGrant);
67620
67595
  const identity = decodeCmmdUserIdentity(decodeSensitiveValue(IDENTITY_SCOPE, row.cmmdUserIdentity));
67621
67596
  if (!identity) return null;
67622
67597
  const updateNow = (/* @__PURE__ */ new Date()).toISOString();
@@ -67626,7 +67601,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => gen(function*
67626
67601
  access_token,
67627
67602
  jwt_token,
67628
67603
  jwt_expires_at,
67629
- refresh_token,
67604
+ session_grant,
67630
67605
  cmmd_user_identity,
67631
67606
  created_at,
67632
67607
  updated_at
@@ -67636,7 +67611,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => gen(function*
67636
67611
  ${row.accessToken},
67637
67612
  ${row.jwtToken},
67638
67613
  ${row.jwtExpiresAt},
67639
- ${row.refreshToken},
67614
+ ${row.sessionGrant},
67640
67615
  ${row.cmmdUserIdentity},
67641
67616
  ${updateNow},
67642
67617
  ${updateNow}
@@ -67645,7 +67620,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => gen(function*
67645
67620
  access_token = excluded.access_token,
67646
67621
  jwt_token = excluded.jwt_token,
67647
67622
  jwt_expires_at = excluded.jwt_expires_at,
67648
- refresh_token = excluded.refresh_token,
67623
+ session_grant = excluded.session_grant,
67649
67624
  cmmd_user_identity = excluded.cmmd_user_identity,
67650
67625
  updated_at = excluded.updated_at
67651
67626
  `.pipe(mapError(toPersistenceSqlError("CommandAccessTokenStore.promoteLatestOwnerAuth:upsert")));
@@ -67654,14 +67629,14 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => gen(function*
67654
67629
  else jwtCache.delete(sessionId);
67655
67630
  if (row.jwtExpiresAt) jwtExpiryCache.set(sessionId, row.jwtExpiresAt);
67656
67631
  else jwtExpiryCache.delete(sessionId);
67657
- if (refreshToken) refreshTokenCache.set(sessionId, refreshToken);
67658
- else refreshTokenCache.delete(sessionId);
67632
+ if (sessionGrant) sessionGrantCache.set(sessionId, sessionGrant);
67633
+ else sessionGrantCache.delete(sessionId);
67659
67634
  identityCache.set(sessionId, identity);
67660
67635
  return {
67661
67636
  accessToken,
67662
67637
  jwtToken,
67663
67638
  jwtExpiresAt: row.jwtExpiresAt ?? null,
67664
- refreshToken,
67639
+ sessionGrant,
67665
67640
  sourceSessionId: row.sessionId,
67666
67641
  cmmdUserIdentity: identity
67667
67642
  };
@@ -67706,11 +67681,11 @@ const clearCommandAccessTokenForSession = (sessionId) => gen(function* () {
67706
67681
  `.pipe(mapError(toPersistenceSqlError("CommandAccessTokenStore.clear:query")));
67707
67682
  forgetCommandAccessTokenCachesForSession(sessionId);
67708
67683
  });
67684
+ /** Evict a session's credentials, JWT expiry, identity, and grant from memory without changing storage. */
67709
67685
  function forgetCommandAccessTokenCachesForSession(sessionId) {
67710
67686
  cache.delete(sessionId);
67711
67687
  jwtCache.delete(sessionId);
67712
67688
  jwtExpiryCache.delete(sessionId);
67713
- refreshTokenCache.delete(sessionId);
67714
67689
  identityCache.delete(sessionId);
67715
67690
  sessionGrantCache.delete(sessionId);
67716
67691
  }
@@ -67742,14 +67717,14 @@ const scheduleExpiredForgeCloudOwnerCredentialMaintenance = sleep$4(minutes(5)).
67742
67717
  * is dead too, so the promotion sources are cleared with it — otherwise the
67743
67718
  * next promotion resurrects the same dead bundle in a loop.
67744
67719
  * - **Every other session** owns its credentials outright: each OAuth exchange
67745
- * mints a distinct PAT and refresh token (CMMD `createAccountScopedToken` /
67746
- * `issueTokenPair`). One session's dead credential says nothing about a
67720
+ * mints its own PAT, JWT and session grant. One session's dead credential
67721
+ * says nothing about a
67747
67722
  * sibling browser or device, so the clear stays local to that session.
67748
67723
  *
67749
67724
  * Clearing every active owner row unconditionally (the pre-CMMD-1871 behaviour)
67750
67725
  * turned a single expired tab into a sign-out on every browser and device.
67751
67726
  *
67752
- * Destroys a refresh token good for months, so every call is logged with a
67727
+ * Destroys durable credentials (the PAT, the grant), so every call is logged with a
67753
67728
  * `reason` naming the caller's own decision (never the credential itself) and
67754
67729
  * the actual row count deleted, at info level so it shows up without raising
67755
67730
  * the server's log level. `reason` is optional only so a caller that has not
@@ -67790,65 +67765,10 @@ const clearUnrecoverableCmmdAuthForSession = (sessionId, reason) => gen(function
67790
67765
  }
67791
67766
  });
67792
67767
  /**
67793
- * Retire refresh credentials copied into historical desktop bearer sessions.
67794
- * Electron owns rotation for native desktop OAuth; Forge Local keeps only the
67795
- * derived access authority needed by the current bearer session.
67796
- *
67797
- * Scoped to the bootstrapping client, not to the whole subject. The phone and
67798
- * Electron sign in as the same `user:<id>`, so a subject-wide sweep let one
67799
- * desktop launch null out a phone's refresh token and end its session a day
67800
- * later. Two clauses keep that from happening:
67801
- *
67802
- * - `client_device_type = 'desktop'`, which now excludes phones outright,
67803
- * because the mobile app declares itself at bootstrap.
67804
- * - the same recorded `client_user_agent` as the bootstrap doing the
67805
- * retirement, which is the only thing that separates a phone row written
67806
- * before that stamp existed from a real Electron row. Both are stamped
67807
- * `desktop`; neither carries any other mark. An empty or unmatched user agent
67808
- * retires nothing, which is the direction that keeps a live credential.
67809
- *
67810
- * Electron re-bootstraps under a stable user agent, so its own historical rows
67811
- * still match and still get swept.
67812
- */
67813
- const clearDesktopBearerRefreshTokensForSubject = (input) => gen(function* () {
67814
- const normalizedSubject = input.subject.trim();
67815
- const normalizedUserAgent = input.clientUserAgent?.trim() ?? "";
67816
- if (!normalizedSubject || !normalizedUserAgent) return;
67817
- const rows = yield* (yield* SqlClient)`
67818
- UPDATE command_access_tokens
67819
- SET
67820
- refresh_token = NULL,
67821
- updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
67822
- WHERE session_id IN (
67823
- SELECT session_id
67824
- FROM auth_sessions
67825
- WHERE subject = ${normalizedSubject}
67826
- AND role = 'owner'
67827
- AND method = 'bearer-session-token'
67828
- AND client_device_type = 'desktop'
67829
- AND client_user_agent = ${normalizedUserAgent}
67830
- )
67831
- RETURNING session_id AS "sessionId"
67832
- `.pipe(mapError(toPersistenceSqlError("CommandAccessTokenStore.clearDesktopBearerRefresh:query")));
67833
- for (const row of rows) refreshTokenCache.delete(row.sessionId);
67834
- });
67835
- const isDesktopBearerAuthSession = (sessionId) => gen(function* () {
67836
- const rows = yield* (yield* SqlClient)`
67837
- SELECT
67838
- method,
67839
- client_device_type AS "clientDeviceType"
67840
- FROM auth_sessions
67841
- WHERE session_id = ${sessionId}
67842
- LIMIT 1
67843
- `.pipe(mapError(toPersistenceSqlError("CommandAccessTokenStore.isDesktopBearerSession:query")));
67844
- return rows[0]?.method === "bearer-session-token" && rows[0].clientDeviceType === "desktop";
67845
- });
67846
- /**
67847
67768
  * The device type a session's bearer bootstrap declared, for log lines only.
67848
67769
  *
67849
- * Mirrors `isDesktopBearerAuthSession`'s read but returns the raw column
67850
- * instead of collapsing it into a rotation verdict — a log line wants to say
67851
- * what made the request, not whether Forge may rotate for it. Never fails:
67770
+ * Returns the raw column: a log line wants to say what made the request.
67771
+ * Never fails:
67852
67772
  * an unreadable or missing row logs as `"unknown"` rather than losing the
67853
67773
  * decision-point log line it is attached to.
67854
67774
  */
@@ -67901,6 +67821,7 @@ function artifactError(message) {
67901
67821
  cause
67902
67822
  });
67903
67823
  }
67824
+ /** Store credentials under the asserted Cloud owner session and retain its durable mint grant. */
67904
67825
  function persistCloudOwnerArtifacts(input) {
67905
67826
  return gen(function* () {
67906
67827
  const sql = yield* SqlClient;
@@ -67908,7 +67829,7 @@ function persistCloudOwnerArtifacts(input) {
67908
67829
  yield* setCommandAccessTokenForSession(input.assertion.sessionId, input.accessToken).pipe(mapError(artifactError("Failed to persist CMMD access token.")));
67909
67830
  yield* setCmmdUserIdentityForSession(input.assertion.sessionId, input.identity).pipe(mapError(artifactError("Failed to persist CMMD user identity.")));
67910
67831
  if (input.sessionGrant) yield* setCommandSessionGrantForSession(input.assertion.sessionId, input.sessionGrant, { expiresAt: input.sessionGrantExpiresAt ?? null }).pipe(mapError(artifactError("Failed to persist CMMD session grant.")));
67911
- if (input.jwtToken) yield* setCommandJwtForSession(input.assertion.sessionId, input.jwtToken, input.refreshToken ? { refreshToken: input.refreshToken } : {}).pipe(mapError(artifactError("Failed to persist CMMD JWT.")));
67832
+ if (input.jwtToken) yield* setCommandJwtForSession(input.assertion.sessionId, input.jwtToken).pipe(mapError(artifactError("Failed to persist CMMD JWT.")));
67912
67833
  });
67913
67834
  yield* sql.withTransaction(transaction).pipe(mapError((cause) => cause instanceof AuthError ? cause : new AuthError({
67914
67835
  message: "Failed to persist Forge Cloud owner credentials.",
@@ -68046,7 +67967,6 @@ const authSessionRenewRouteLayer = add$1("POST", "/api/auth/session/renew", gen(
68046
67967
  accessToken: patToStore,
68047
67968
  identity: cmmdIdentity,
68048
67969
  jwtToken: credentials.jwtToStore,
68049
- refreshToken: credentials.refreshToken,
68050
67970
  sessionGrant,
68051
67971
  sessionGrantExpiresAt: void 0
68052
67972
  });
@@ -68122,7 +68042,6 @@ const PairingCredentialRequestHeaders = Struct({
68122
68042
  const CommandBootstrapInput = Struct({
68123
68043
  accessToken: String$1,
68124
68044
  mcpToken: optionalKey(String$1),
68125
- refreshToken: optionalKey(String$1),
68126
68045
  userId: optionalKey(Number$1),
68127
68046
  displayName: optionalKey(String$1),
68128
68047
  user: optionalKey(Unknown),
@@ -68356,6 +68275,7 @@ function validateCommandBootstrapPayload(payload) {
68356
68275
  };
68357
68276
  });
68358
68277
  }
68278
+ /** Store the bootstrap PAT, optional JWT, and identity; report persistence failures as auth errors. */
68359
68279
  function persistCommandBootstrapSessionArtifacts(input) {
68360
68280
  return gen(function* () {
68361
68281
  const token = input.payload.accessToken.trim();
@@ -68370,14 +68290,11 @@ function persistCommandBootstrapSessionArtifacts(input) {
68370
68290
  status: 500,
68371
68291
  cause
68372
68292
  })));
68373
- if (isCmmdJwtToken(token)) {
68374
- const refreshToken = input.payload.refreshToken?.trim();
68375
- yield* setCommandJwtForSession(input.sessionId, token, refreshToken ? { refreshToken } : {}).pipe(mapError((cause) => new AuthError({
68376
- message: "Failed to persist CMMD JWT.",
68377
- status: 500,
68378
- cause
68379
- })));
68380
- }
68293
+ if (isCmmdJwtToken(token)) yield* setCommandJwtForSession(input.sessionId, token).pipe(mapError((cause) => new AuthError({
68294
+ message: "Failed to persist CMMD JWT.",
68295
+ status: 500,
68296
+ cause
68297
+ })));
68381
68298
  const cmmdIdentity = input.cmmdIdentity ?? normalizeCmmdUserIdentity(input.payload.user);
68382
68299
  if (cmmdIdentity) yield* setCmmdUserIdentityForSession(input.sessionId, cmmdIdentity).pipe(mapError((cause) => new AuthError({
68383
68300
  message: "Failed to persist CMMD user identity.",
@@ -68479,7 +68396,6 @@ function resolveCmmdTokenBundleCredentials(tokenResult) {
68479
68396
  userId: cmmdIdentity?.id,
68480
68397
  accessToken,
68481
68398
  mcpToken,
68482
- refreshToken: readString("refresh_token"),
68483
68399
  sessionGrant: readString("session_grant"),
68484
68400
  sessionGrantExpiresAt: readString("session_grant_expires_at"),
68485
68401
  forgeOwnerAssertion: readString("forge_owner_assertion"),
@@ -68501,7 +68417,6 @@ function describeCmmdTokenResponseForLog(input) {
68501
68417
  accessTokenType: input.tokenObj ? typeof input.tokenObj["access_token"] : "n/a",
68502
68418
  hasAccessToken: input.accessToken !== void 0,
68503
68419
  hasMcpToken: input.mcpToken !== void 0,
68504
- hasRefreshToken: input.refreshToken !== void 0,
68505
68420
  expiresInType: input.tokenObj ? typeof input.tokenObj["expires_in"] : "n/a",
68506
68421
  isJwt: input.isJwt,
68507
68422
  userKeys: userObj ? Object.keys(userObj) : [],
@@ -68729,19 +68644,6 @@ const authCommandBearerBootstrapRouteLayer = add$1("POST", "/api/auth/bootstrap/
68729
68644
  payload,
68730
68645
  cmmdIdentity: validated.cmmdIdentity
68731
68646
  });
68732
- if (deviceType === "desktop" && !payload.refreshToken?.trim()) yield* clearDesktopBearerRefreshTokensForSubject({
68733
- subject: sessionSubject,
68734
- clientUserAgent: request.headers["user-agent"]
68735
- }).pipe(mapError((cause) => new AuthError({
68736
- message: "Failed to retire legacy desktop refresh credentials.",
68737
- status: 500,
68738
- cause
68739
- })), tapError(() => logWarning$1("CMMD bearer bootstrap failed.", {
68740
- sessionId: issued.sessionId,
68741
- deviceType,
68742
- outcome: "failed",
68743
- reason: "legacy-desktop-refresh-retirement-failed"
68744
- })));
68745
68647
  yield* logInfo("CMMD bearer bootstrap succeeded.", {
68746
68648
  sessionId: issued.sessionId,
68747
68649
  deviceType,
@@ -68839,7 +68741,6 @@ function completeCloudOwnerPasswordLogin(input) {
68839
68741
  accessToken: patToStore,
68840
68742
  identity: cmmdIdentity,
68841
68743
  jwtToken: credentials.jwtToStore,
68842
- refreshToken: credentials.refreshToken,
68843
68744
  sessionGrant: credentials.sessionGrant,
68844
68745
  sessionGrantExpiresAt: credentials.sessionGrantExpiresAt
68845
68746
  });
@@ -68922,6 +68823,10 @@ const authWebLoginRouteLayer = add$1("GET", "/api/auth/login", gen(function* ()
68922
68823
  transientCookie
68923
68824
  ]]));
68924
68825
  }));
68826
+ /**
68827
+ * Persist the local OAuth PAT and identity, failing if either write fails.
68828
+ * Capture and ignore failures while storing the optional mint grant and JWT.
68829
+ */
68925
68830
  function persistLocalOAuthArtifacts(input) {
68926
68831
  return gen(function* () {
68927
68832
  yield* setCommandAccessTokenForSession(input.sessionId, input.accessToken).pipe(mapError((cause) => new AuthError({
@@ -68938,7 +68843,7 @@ function persistLocalOAuthArtifacts(input) {
68938
68843
  route: "oauth-callback",
68939
68844
  step: "grant-store"
68940
68845
  }))), ignore({ log: true }));
68941
- if (input.jwtToken) yield* setCommandJwtForSession(input.sessionId, input.jwtToken, input.refreshToken ? { refreshToken: input.refreshToken } : {}).pipe(tapError((error) => sync$1(() => captureServerError(error, {
68846
+ if (input.jwtToken) yield* setCommandJwtForSession(input.sessionId, input.jwtToken).pipe(tapError((error) => sync$1(() => captureServerError(error, {
68942
68847
  route: "oauth-callback",
68943
68848
  step: "jwt-store"
68944
68849
  }))), ignore({ log: true }));
@@ -68992,13 +68897,12 @@ const authWebCallbackRouteLayer = add$1("GET", "/api/auth/callback", gen(functio
68992
68897
  cause
68993
68898
  })
68994
68899
  }), "Failed to exchange authorization code with CMMD.");
68995
- const { tokenObj, cmmdIdentity, userId, accessToken, mcpToken, refreshToken, sessionGrant, sessionGrantExpiresAt, forgeOwnerAssertion, isJwt, patToStore, jwtToStore } = yield* resolveCmmdTokenBundleCredentials(tokenResult);
68900
+ const { tokenObj, cmmdIdentity, userId, accessToken, mcpToken, sessionGrant, sessionGrantExpiresAt, forgeOwnerAssertion, isJwt, patToStore, jwtToStore } = yield* resolveCmmdTokenBundleCredentials(tokenResult);
68996
68901
  const tokenResponseLogDetails = describeCmmdTokenResponseForLog({
68997
68902
  tokenObj,
68998
68903
  tokenResult,
68999
68904
  accessToken,
69000
68905
  mcpToken,
69001
- refreshToken,
69002
68906
  isJwt,
69003
68907
  userId,
69004
68908
  patToStore,
@@ -69051,7 +68955,6 @@ const authWebCallbackRouteLayer = add$1("GET", "/api/auth/callback", gen(functio
69051
68955
  accessToken: patToStore,
69052
68956
  identity: cmmdIdentity,
69053
68957
  jwtToken: jwtToStore,
69054
- refreshToken,
69055
68958
  sessionGrant,
69056
68959
  sessionGrantExpiresAt
69057
68960
  };
@@ -71045,51 +70948,83 @@ function isCmmdUserAccessJwt(token) {
71045
70948
  }
71046
70949
  //#endregion
71047
70950
  //#region src/command/cmmdJwt.ts
71048
- function parseCmmdOAuthRefreshResponse(value) {
71049
- if (!value || typeof value !== "object") return null;
71050
- const record = value;
71051
- const accessToken = record["access_token"];
71052
- if (typeof accessToken !== "string" || accessToken.trim().length === 0) return null;
71053
- const refreshToken = record["refresh_token"];
71054
- return {
71055
- accessToken: accessToken.trim(),
71056
- refreshToken: typeof refreshToken === "string" && refreshToken.trim().length > 0 ? refreshToken.trim() : null
71057
- };
71058
- }
71059
70951
  /**
71060
- * Read the one field the mint lane consumes from CMMD's mint response.
71061
- * Returns null unless `access_token` is present and non-empty.
71062
- */
70952
+ * Read a minted JWT with finite issuance and expiry times and a lifetime of
70953
+ * at most one hour. Reject expired tokens and tokens issued in the future.
70954
+ * This checks the mint contract only; CMMD verifies signatures on use.
70955
+ */
70956
+ /** Machine-client tokens live at most one hour; anything longer is a CMMD
70957
+ * contract violation that must not silently extend forge sessions. */
70958
+ const MAX_MACHINE_TOKEN_LIFETIME_SECONDS = 3600;
70959
+ /**
70960
+ * The minted access token's lifetime in seconds, read from its JWT `exp` and
70961
+ * `iat` claims without verifying the signature (the signature is checked on
70962
+ * every use at the guard; here the claims only measure the lifetime). `iat`
70963
+ * falls back to the current time when absent. Null when the payload cannot be
70964
+ * read, which is itself a contract violation.
70965
+ */
70966
+ function readJwtLifetimeSeconds(accessToken) {
70967
+ const claims = decodeJwtPayload(accessToken);
70968
+ const expiresAt = claims?.["exp"];
70969
+ const issuedAtRaw = claims?.["iat"];
70970
+ if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt)) return null;
70971
+ const issuedAt = typeof issuedAtRaw === "number" && Number.isFinite(issuedAtRaw) ? issuedAtRaw : Math.floor(Date.now() / 1e3);
70972
+ const now = Date.now() / 1e3;
70973
+ if (expiresAt <= now || issuedAt > now) return null;
70974
+ return expiresAt - issuedAt;
70975
+ }
70976
+ /** True when the minted token's lifetime honors the one-hour contract. A
70977
+ * refusal is logged here because the reason is mint-contract specific and the
70978
+ * caller only wants the token-or-null answer. */
70979
+ function mintLifetimeWithinContract(accessToken) {
70980
+ const lifetime = readJwtLifetimeSeconds(accessToken);
70981
+ if (lifetime !== null && lifetime <= MAX_MACHINE_TOKEN_LIFETIME_SECONDS) return true;
70982
+ console.warn(`[cmmd-auth] CMMD minted a forge token outside the one-hour contract (lifetime=${lifetime === null ? "unreadable" : `${lifetime}s`}); refusing to persist it`);
70983
+ return false;
70984
+ }
71063
70985
  function parseCmmdTokenMintResponse(value) {
71064
70986
  if (!value || typeof value !== "object") return null;
71065
70987
  const accessToken = value["access_token"];
71066
70988
  if (typeof accessToken !== "string" || accessToken.trim().length === 0) return null;
71067
- return { accessToken: accessToken.trim() };
70989
+ const trimmed = accessToken.trim();
70990
+ if (!mintLifetimeWithinContract(trimmed)) return null;
70991
+ return { accessToken: trimmed };
71068
70992
  }
71069
70993
  /**
71070
- * How long the mint may take before the lane gives up on it. Minting is one
70994
+ * How long the mint may take before the caller gives up on it. Minting is one
71071
70995
  * grant lookup and one signature on CMMD's side; anything slower is treated
71072
- * like a failure and the resolver falls through to the legacy ladder.
70996
+ * like a failure and the resolution returns null.
71073
70997
  */
71074
70998
  const MINT_TIMEOUT_MS = 1e4;
71075
70999
  /**
71000
+ * The OAuth error codes that are evidence the grant itself is dead, and the
71001
+ * only mint refusals allowed to feed the credential-rejection ledger.
71002
+ *
71003
+ * This set is grant-specific on purpose. Per RFC 6749, `invalid_grant` names
71004
+ * the grant. `invalid_client` names the client (CMMD did not accept
71005
+ * `cmmd-forge-web`) and `invalid_request` names the request shape (a missing or
71006
+ * unsupported parameter, for example after a CMMD contract change); both would
71007
+ * fire for every session at once, so a single misconfiguration or
71008
+ * deploy-ordering mistake could delete every stored credential. Those codes
71009
+ * stay logged refusals that resolve null and leave the row in place. If CMMD's
71010
+ * contract ever makes one of them grant-specific, document that here before
71011
+ * widening this set.
71012
+ */
71013
+ const REJECTED_OAUTH_ERROR_CODES = new Set(["invalid_grant"]);
71014
+ /**
71076
71015
  * Mint a short-lived forge-scoped access token from a durable session grant
71077
- * (forge#3518 expand phase).
71016
+ * (forge#3518 expand; the only lane since the forge#3521 contract phase).
71078
71017
  *
71079
- * The grant is non-rotating and redeemable on demand, so this is the mint
71080
- * lane's entire refresh story: call it again when the short-lived token
71081
- * expires. CMMD answers with `access_token` only (no refresh token, no MCP
71082
- * PAT, nothing durable), so a session that lives entirely on this lane never
71083
- * accumulates long-lived credential state on Forge's side.
71018
+ * The grant is non-rotating and redeemable on demand, so this is the entire
71019
+ * refresh story: call it again when the short-lived token expires. CMMD answers
71020
+ * with `access_token` only (no refresh token, no MCP PAT, nothing durable), so
71021
+ * a session living on this lane never accumulates long-lived credential state
71022
+ * beyond the grant it already holds.
71084
71023
  *
71085
71024
  * Returns null for every failure because the resolver wants a value, not a
71086
- * thrown error; the failure modes are logged by status and OAuth error code
71087
- * the same way `refreshCmmdJwt` logs them. A rejection here is never fed to
71088
- * the credential-rejection ledger: the ledger exists to prove a session's
71089
- * *rotation* credential is dead, and the grant dying says nothing about the
71090
- * refresh token the legacy fallback still owns.
71025
+ * thrown error; the failure modes are logged by status and OAuth error code.
71091
71026
  */
71092
- function mintCmmdShortLivedToken(grantToken) {
71027
+ function mintCmmdShortLivedToken(grantToken, observer) {
71093
71028
  return fetch(new URL("/api/mcp-server/oauth/token", resolveCommandAppUrl()).toString(), {
71094
71029
  method: "POST",
71095
71030
  headers: { "Content-Type": "application/json" },
@@ -71102,6 +71037,7 @@ function mintCmmdShortLivedToken(grantToken) {
71102
71037
  }).then(async (response) => {
71103
71038
  if (!response.ok) {
71104
71039
  const errorCode = await readOAuthErrorCode(response);
71040
+ if (errorCode !== null && REJECTED_OAUTH_ERROR_CODES.has(errorCode)) observer?.onCredentialRejected?.();
71105
71041
  console.warn(`[cmmd-auth] CMMD refused a forge token mint with ${response.status}${errorCode === null ? "" : ` (${errorCode})`}`);
71106
71042
  return null;
71107
71043
  }
@@ -71114,103 +71050,149 @@ function mintCmmdShortLivedToken(grantToken) {
71114
71050
  });
71115
71051
  }
71116
71052
  /**
71117
- * The redemption request body. `popPublicKeyPem` is the server's PoP identity
71118
- * (forge#3534): presented when bound sessions are enabled so CMMD stamps the
71119
- * returned JWT with this server's key (`cnf.jkt`), omitted otherwise.
71053
+ * The OAuth `error` code from a refusal, lowercased, or null when there is none.
71054
+ *
71055
+ * Best effort by design: a body that is not JSON, or is already consumed, must
71056
+ * not turn a logged failure into an unlogged one. Null also reads correctly as
71057
+ * "no evidence the grant is dead", which is the safe answer for a caller
71058
+ * deciding whether to keep the session's stored credential.
71120
71059
  */
71121
- function buildSessionGrantRedemptionBody(grantToken, popPublicKeyPem) {
71122
- return {
71123
- grant_type: "forge_session_grant",
71124
- grant_token: grantToken,
71125
- client_id: "cmmd-forge-web",
71126
- ...popPublicKeyPem ? { pop_public_key: popPublicKeyPem } : {}
71127
- };
71060
+ async function readOAuthErrorCode(response) {
71061
+ try {
71062
+ const code = (await response.json())["error"];
71063
+ return typeof code === "string" && code.trim().length > 0 ? code.trim().toLowerCase() : null;
71064
+ } catch {
71065
+ return null;
71066
+ }
71128
71067
  }
71068
+ //#endregion
71069
+ //#region src/cmmdConnection/cmmdCredentialRejection.ts
71129
71070
  /**
71130
- * Redeem a durable CMMD session grant for a fresh credential set.
71131
- *
71132
- * The grant is CMMD's designed recovery path for exactly the case a rotating
71133
- * refresh token cannot cover: the refresh token is single-use, so one lost or
71134
- * raced rotation strands the session with no way back. The grant is
71135
- * NON-rotating — the same token redeems repeatedly until CMMD expires or
71136
- * revokes it — so redemption is safe to retry and cannot race itself.
71071
+ * How long a recorded rejection stays usable.
71137
71072
  *
71138
- * Returns the new MCP PAT alongside the JWT/refresh pair, because CMMD mints a
71139
- * fresh PAT on redemption and the old one is not carried over.
71073
+ * Long enough to span the resolver ladder and the response that follows it,
71074
+ * short enough that it cannot outlive the credential it describes.
71140
71075
  */
71141
- function redeemCmmdSessionGrant(grantToken, popPublicKeyPem) {
71142
- return fetch(new URL("/api/mcp-server/oauth/token", resolveCommandAppUrl()).toString(), {
71143
- method: "POST",
71144
- headers: { "Content-Type": "application/json" },
71145
- body: JSON.stringify(buildSessionGrantRedemptionBody(grantToken, popPublicKeyPem))
71146
- }).then(async (response) => {
71147
- if (!response.ok) return null;
71148
- const payload = await response.json();
71149
- const parsed = parseCmmdOAuthRefreshResponse(payload);
71150
- if (!parsed) return null;
71151
- const mcpToken = payload["mcp_token"];
71152
- return {
71153
- ...parsed,
71154
- mcpToken: typeof mcpToken === "string" && mcpToken.trim().length > 0 ? mcpToken.trim() : null
71155
- };
71156
- }).catch(() => null);
71076
+ const REJECTION_TTL_MS = 6e4;
71077
+ const rejectedAt = /* @__PURE__ */ new Map();
71078
+ /** Record that CMMD refused this session's credential outright. */
71079
+ function noteCmmdCredentialRejected(sessionId) {
71080
+ rejectedAt.set(sessionId, Date.now());
71081
+ }
71082
+ /** Whether CMMD refused this session's credential recently enough to act on. */
71083
+ function hasCmmdCredentialRejection(sessionId) {
71084
+ const observedAt = rejectedAt.get(sessionId);
71085
+ if (observedAt === void 0) return false;
71086
+ if (Date.now() - observedAt <= REJECTION_TTL_MS) return true;
71087
+ rejectedAt.delete(sessionId);
71088
+ return false;
71089
+ }
71090
+ /** Drop a recorded rejection, once it has been acted on or superseded. */
71091
+ function forgetCmmdCredentialRejection(sessionId) {
71092
+ rejectedAt.delete(sessionId);
71157
71093
  }
71158
- const REJECTED_OAUTH_ERROR_CODES = new Set([
71159
- "invalid_grant",
71160
- "invalid_client",
71161
- "invalid_request"
71162
- ]);
71163
71094
  /**
71164
- * Rotate a CMMD refresh token into a fresh access JWT.
71095
+ * Clear a session's CMMD credentials, but only if CMMD rejected them.
71165
71096
  *
71166
- * Returns null for every failure, because the caller's recovery ladder wants a
71167
- * value rather than a thrown error. Null is nearly the whole diagnostic value
71168
- * of this call though, so each way of reaching it says so first. Chasing a
71169
- * phone that had silently stopped accepting its session cost an afternoon
71170
- * partly because a failed rotation here was indistinguishable from one that
71171
- * never ran. The token itself is never logged; the status and the OAuth error
71172
- * code are what identify the failure, and `invalid_grant` in particular means
71173
- * the token was already spent, which is a different fix from a 500.
71097
+ * This is the only door the 401 path may use. A resolver that returned null
71098
+ * without a recorded rejection is a transient failure and leaves the stored
71099
+ * credential exactly where it is, so the next request can mint from it again.
71174
71100
  */
71175
- function refreshCmmdJwt(refreshToken, observer) {
71176
- return fetch(new URL("/api/mcp-server/oauth/token", resolveCommandAppUrl()).toString(), {
71177
- method: "POST",
71178
- headers: { "Content-Type": "application/json" },
71179
- body: JSON.stringify({
71180
- grant_type: "refresh_token",
71181
- refresh_token: refreshToken,
71182
- client_id: "cmmd-forge-web"
71183
- })
71184
- }).then(async (response) => {
71185
- if (!response.ok) {
71186
- const errorCode = await readOAuthErrorCode(response);
71187
- if (errorCode !== null && REJECTED_OAUTH_ERROR_CODES.has(errorCode)) observer?.onCredentialRejected?.();
71188
- console.warn(`[cmmd-auth] CMMD refused a JWT rotation with ${response.status}${errorCode === null ? "" : ` (${errorCode})`}`);
71189
- return null;
71190
- }
71191
- const parsed = parseCmmdOAuthRefreshResponse(await response.json());
71192
- if (!parsed) console.warn("[cmmd-auth] CMMD accepted a JWT rotation but returned no usable access token");
71193
- return parsed;
71194
- }).catch((cause) => {
71195
- console.warn(`[cmmd-auth] CMMD JWT rotation could not be completed: ${String(cause)}`);
71196
- return null;
71101
+ const clearCmmdAuthAfterCmmdRejection = (sessionId) => gen(function* () {
71102
+ if (!hasCmmdCredentialRejection(sessionId)) return;
71103
+ yield* clearUnrecoverableCmmdAuthForSession(sessionId, "cmmd-rejected-credential");
71104
+ forgetCmmdCredentialRejection(sessionId);
71105
+ });
71106
+ //#endregion
71107
+ //#region src/command/cmmdJwtResolver.ts
71108
+ function usableCmmdUserJwt(token) {
71109
+ return isCmmdUserAccessJwt(token) ? token : null;
71110
+ }
71111
+ /** Log a resolver decision with session context and a fixed reason, excluding credential values. */
71112
+ function logCmmdJwtRung(context, rung, outcome, reason) {
71113
+ return (outcome === "failed" ? logWarning$1 : logInfo)("CMMD JWT resolver rung.", {
71114
+ sessionId: context.sessionId,
71115
+ subject: context.subject,
71116
+ deviceType: context.deviceType,
71117
+ caller: context.caller,
71118
+ rung,
71119
+ outcome,
71120
+ reason
71197
71121
  });
71198
71122
  }
71123
+ /** In-flight mints, keyed by session. */
71124
+ const inFlightMints = /* @__PURE__ */ new Map();
71199
71125
  /**
71200
- * The OAuth `error` code from a refusal, lowercased, or null when there is none.
71126
+ * Collapse concurrent mints for one session into a single upstream call. A
71127
+ * mint cannot race itself into `invalid_grant` (the grant is non-rotating),
71128
+ * so this is call-rate hygiene, not correctness: two tabs observing the same
71129
+ * expiry must not both hit CMMD when one mint would do.
71130
+ */
71131
+ function mintOnceForSession(sessionId, grantToken) {
71132
+ return promise(() => {
71133
+ const existing = inFlightMints.get(sessionId);
71134
+ if (existing) return existing;
71135
+ const pending = mintCmmdShortLivedToken(grantToken, { onCredentialRejected: () => noteCmmdCredentialRejected(sessionId) }).catch(() => null).finally(() => {
71136
+ inFlightMints.delete(sessionId);
71137
+ });
71138
+ inFlightMints.set(sessionId, pending);
71139
+ return pending;
71140
+ });
71141
+ }
71142
+ /**
71143
+ * Resolve the CMMD REST JWT for a Forge session.
71201
71144
  *
71202
- * Best effort by design: a body that is not JSON, or is already consumed, must
71203
- * not turn a logged failure into an unlogged one. Null also reads correctly as
71204
- * "no evidence the credential is dead", which is the safe answer for a caller
71205
- * deciding whether to keep a refresh token.
71145
+ * Forge session auth is long-lived compared with the short-lived CMMD JWT. When
71146
+ * the session JWT expires, mint a fresh short-lived token from the session's
71147
+ * durable, non-rotating grant: CMMD answers with one forge-scoped access token
71148
+ * (TTL at most one hour) and nothing else, and minting is the entire refresh
71149
+ * story (forge#3521 contract phase). There is no rotation to fence, no
71150
+ * single-use token to single-flight, and no sibling credential to borrow.
71151
+ *
71152
+ * Fail-closed: never returns a non-user / malformed Bearer. Callers must map
71153
+ * null to CMMD_AUTH_REQUIRED and must not call CMMD upstream with a bad token.
71206
71154
  */
71207
- async function readOAuthErrorCode(response) {
71208
- try {
71209
- const code = (await response.json())["error"];
71210
- return typeof code === "string" && code.trim().length > 0 ? code.trim().toLowerCase() : null;
71211
- } catch {
71212
- return null;
71155
+ function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth", forceRefresh = false, staleAccessToken = null }) {
71156
+ const cached = usableCmmdUserJwt(readCommandJwtForSession(sessionId));
71157
+ if (cached && !isJwtExpiredForSession(sessionId)) {
71158
+ if (!forceRefresh) return succeed(cached);
71159
+ if (staleAccessToken && cached !== staleAccessToken) return succeed(cached).pipe(tap(() => gen(function* () {
71160
+ const deviceType = yield* readClientDeviceTypeForSession(sessionId);
71161
+ yield* logCmmdJwtRung({
71162
+ sessionId,
71163
+ subject: subject?.trim() || null,
71164
+ deviceType,
71165
+ caller: logPrefix
71166
+ }, "cache", "skipped", "forced refresh skipped: cache already holds a token another caller minted");
71167
+ })));
71213
71168
  }
71169
+ const normalizedSubject = subject?.trim() || null;
71170
+ if (!normalizedSubject && !forceRefresh && readCommandSessionGrantForSession(sessionId) === null) return succeed(null);
71171
+ return gen(function* () {
71172
+ const context = {
71173
+ sessionId,
71174
+ subject: normalizedSubject,
71175
+ deviceType: yield* readClientDeviceTypeForSession(sessionId),
71176
+ caller: logPrefix
71177
+ };
71178
+ const grantToken = readCommandSessionGrantForSession(sessionId);
71179
+ if (!grantToken) {
71180
+ yield* logCmmdJwtRung(context, "mint", "skipped", "no session grant stored for this session");
71181
+ return null;
71182
+ }
71183
+ yield* logCmmdJwtRung(context, "mint", "attempted", "minting a short-lived forge-scoped token from this session's grant");
71184
+ yield* update$3(withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "attempted" })), 1);
71185
+ const accessToken = usableCmmdUserJwt((yield* mintOnceForSession(sessionId, grantToken))?.accessToken);
71186
+ if (!accessToken) {
71187
+ yield* update$3(withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "failed" })), 1);
71188
+ yield* logCmmdJwtRung(context, "mint", "failed", "CMMD token mint did not return a usable user access JWT");
71189
+ return null;
71190
+ }
71191
+ yield* setCommandJwtForSession(sessionId, accessToken).pipe(ignore({ log: true }));
71192
+ yield* update$3(withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "succeeded" })), 1);
71193
+ yield* logCmmdJwtRung(context, "mint", "succeeded", "CMMD minted a short-lived forge-scoped token");
71194
+ return accessToken;
71195
+ });
71214
71196
  }
71215
71197
  //#endregion
71216
71198
  //#region ../../packages/shared/src/cmmdSessionPop.ts
@@ -71328,10 +71310,6 @@ function resolveCmmdSessionPopSigner() {
71328
71310
  cachedSigner ??= createServerSigner();
71329
71311
  return cachedSigner;
71330
71312
  }
71331
- /** The `pop_public_key` value to present at issuance, or null while disabled. */
71332
- function cmmdSessionPopPublicKey() {
71333
- return resolveCmmdSessionPopSigner()?.publicKeyPem ?? null;
71334
- }
71335
71313
  /**
71336
71314
  * The three proof headers for one CMMD-origin request, or no headers while
71337
71315
  * disabled. Each call carries a fresh nonce; never reuse the result across
@@ -71347,407 +71325,6 @@ async function cmmdSessionPopRequestHeaders(input) {
71347
71325
  });
71348
71326
  }
71349
71327
  //#endregion
71350
- //#region src/command/cmmdTokenMintLane.ts
71351
- /**
71352
- * The CMMD token-mint lane (forge#3518, expand phase).
71353
- *
71354
- * A new FIRST rung beside the legacy credential ladder: instead of rotating
71355
- * CMMD's single-use refresh tokens, the resolver mints a short-lived
71356
- * forge-scoped access token from the session's durable, non-rotating session
71357
- * grant (`grant_type: forge_token_mint` on CMMD's token endpoint). The mint
71358
- * spends nothing that cannot be spent again, so it cannot race itself into
71359
- * `invalid_grant` the way a rotation can.
71360
- *
71361
- * Behind a config flag that is OFF unless explicitly enabled: production
71362
- * keeps the legacy ladder as the only lane until this is switched on
71363
- * deliberately. When enabled, the lane is tried before the legacy ladder;
71364
- * every legacy rung (rotation fence, self-refresh, grant redemption,
71365
- * bootstrap promotion, subject fallback) still exists below it unchanged and
71366
- * still guards itself with the rejection ledger.
71367
- */
71368
- /** Env var that enables the mint lane. Unset and anything but an affirmative value is off. */
71369
- const CMMD_TOKEN_MINT_LANE_ENV = "FORGE_CMMD_TOKEN_MINT_LANE";
71370
- const AFFIRMATIVE_VALUES = new Set([
71371
- "1",
71372
- "on",
71373
- "true",
71374
- "yes"
71375
- ]);
71376
- /**
71377
- * Whether the mint lane may run. Default off everywhere: a missing env var is
71378
- * the production posture, and an opt-in flag must not half-default to on in
71379
- * any environment a misconfigured deploy might land in.
71380
- */
71381
- function isCmmdTokenMintLaneEnabled(env = process.env) {
71382
- const raw = env[CMMD_TOKEN_MINT_LANE_ENV]?.trim().toLowerCase();
71383
- return raw !== void 0 && AFFIRMATIVE_VALUES.has(raw);
71384
- }
71385
- //#endregion
71386
- //#region src/command/cmmdRotationFence.ts
71387
- /**
71388
- * Log and count a fence application, without changing the verdict it reports.
71389
- *
71390
- * The two facts named in the log line are exactly the ones the predicate
71391
- * above the caller already computed — whether this session ever deposited a
71392
- * refresh token of its own, and (only when that was false) whether the
71393
- * session row reads as a desktop bearer session. Neither fact nor the log
71394
- * line changes what the caller returns.
71395
- */
71396
- function logRotationFenceApplied(input) {
71397
- return logInfo("CMMD rotation fence applied: session may not rotate its own credential.", {
71398
- sessionId: input.sessionId,
71399
- hasStoredRefreshToken: input.hasStoredRefreshToken,
71400
- isDesktopBearerSession: input.isDesktopBearerSession
71401
- }).pipe(andThen(() => update$3(withAttributes(cmmdRotationFenceAppliedTotal, metricAttributes({})), 1)));
71402
- }
71403
- /**
71404
- * True when Forge must not rotate this session's CMMD credentials.
71405
- *
71406
- * Fails closed: if the session row cannot be read, assume the fence applies.
71407
- * Spending a token Forge had no right to spend is worse than one extra
71408
- * reconnect.
71409
- */
71410
- function isRotationFencedSession(sessionId) {
71411
- const hasStoredRefreshToken = readCommandRefreshTokenForSession(sessionId) !== null;
71412
- if (hasStoredRefreshToken) return succeed(false);
71413
- return isDesktopBearerAuthSession(sessionId).pipe(match$1({
71414
- onFailure: () => ({
71415
- fenced: true,
71416
- isDesktopBearerSession: null
71417
- }),
71418
- onSuccess: (isDesktopBearerSession) => ({
71419
- fenced: isDesktopBearerSession,
71420
- isDesktopBearerSession
71421
- })
71422
- }), tap(({ fenced, isDesktopBearerSession }) => fenced ? logRotationFenceApplied({
71423
- sessionId,
71424
- hasStoredRefreshToken,
71425
- isDesktopBearerSession
71426
- }) : void_$1), map$3(({ fenced }) => fenced));
71427
- }
71428
- //#endregion
71429
- //#region src/cmmdConnection/cmmdCredentialRejection.ts
71430
- /**
71431
- * How long a recorded rejection stays usable.
71432
- *
71433
- * Long enough to span the resolver ladder and the response that follows it,
71434
- * short enough that it cannot outlive the credential it describes.
71435
- */
71436
- const REJECTION_TTL_MS = 6e4;
71437
- const rejectedAt = /* @__PURE__ */ new Map();
71438
- /** Record that CMMD refused this session's credential outright. */
71439
- function noteCmmdCredentialRejected(sessionId) {
71440
- rejectedAt.set(sessionId, Date.now());
71441
- }
71442
- /** Whether CMMD refused this session's credential recently enough to act on. */
71443
- function hasCmmdCredentialRejection(sessionId) {
71444
- const observedAt = rejectedAt.get(sessionId);
71445
- if (observedAt === void 0) return false;
71446
- if (Date.now() - observedAt <= REJECTION_TTL_MS) return true;
71447
- rejectedAt.delete(sessionId);
71448
- return false;
71449
- }
71450
- /** Drop a recorded rejection, once it has been acted on or superseded. */
71451
- function forgetCmmdCredentialRejection(sessionId) {
71452
- rejectedAt.delete(sessionId);
71453
- }
71454
- /**
71455
- * Clear a session's CMMD credentials, but only if CMMD rejected them.
71456
- *
71457
- * This is the only door the 401 path may use. A resolver that returned null
71458
- * because it was not allowed to try is a policy refusal and leaves the stored
71459
- * credential exactly where it is, so the next request can rotate it.
71460
- */
71461
- const clearCmmdAuthAfterCmmdRejection = (sessionId) => gen(function* () {
71462
- if (!hasCmmdCredentialRejection(sessionId)) return;
71463
- yield* clearUnrecoverableCmmdAuthForSession(sessionId, "cmmd-rejected-credential");
71464
- forgetCmmdCredentialRejection(sessionId);
71465
- });
71466
- //#endregion
71467
- //#region src/command/cmmdJwtResolver.ts
71468
- function usableCmmdUserJwt(token) {
71469
- return isCmmdUserAccessJwt(token) ? token : null;
71470
- }
71471
- function storeResolvedJwt(sessionId, accessToken, refreshToken) {
71472
- return setCommandJwtForSession(sessionId, accessToken, refreshToken ? { refreshToken } : {}).pipe(ignore({ log: true }));
71473
- }
71474
- function logCmmdJwtRung(context, rung, outcome, reason, lane = "legacy") {
71475
- return (outcome === "failed" ? logWarning$1 : logInfo)("CMMD JWT resolver rung.", {
71476
- sessionId: context.sessionId,
71477
- subject: context.subject,
71478
- deviceType: context.deviceType,
71479
- caller: context.caller,
71480
- lane,
71481
- rung,
71482
- outcome,
71483
- reason
71484
- });
71485
- }
71486
- function recordCmmdCredentialRefreshMetric(rung, outcome) {
71487
- return update$3(withAttributes(cmmdCredentialRefreshTotal, metricAttributes({
71488
- rung,
71489
- outcome
71490
- })), 1);
71491
- }
71492
- const inFlightRefreshes = /* @__PURE__ */ new Map();
71493
- /** In-flight mints, keyed by session, same shape as `inFlightRefreshes`. */
71494
- const inFlightMints = /* @__PURE__ */ new Map();
71495
- /**
71496
- * Collapse concurrent mints for one session into a single upstream call. A
71497
- * mint cannot race itself into `invalid_grant` (the grant is non-rotating),
71498
- * so this is call-rate hygiene, not correctness: two tabs observing the same
71499
- * expiry must not both hit CMMD when one mint would do.
71500
- */
71501
- function mintOnceForSession(sessionId, grantToken) {
71502
- return promise(() => {
71503
- const existing = inFlightMints.get(sessionId);
71504
- if (existing) return existing;
71505
- const pending = mintCmmdShortLivedToken(grantToken).catch(() => null).finally(() => {
71506
- inFlightMints.delete(sessionId);
71507
- });
71508
- inFlightMints.set(sessionId, pending);
71509
- return pending;
71510
- });
71511
- }
71512
- function refreshOnceForSession(sessionId, refreshToken) {
71513
- return promise(() => {
71514
- const existing = inFlightRefreshes.get(sessionId);
71515
- if (existing) return existing;
71516
- const pending = refreshCmmdJwt(refreshToken, { onCredentialRejected: () => noteCmmdCredentialRejected(sessionId) }).catch(() => null).finally(() => {
71517
- inFlightRefreshes.delete(sessionId);
71518
- });
71519
- inFlightRefreshes.set(sessionId, pending);
71520
- return pending;
71521
- });
71522
- }
71523
- /**
71524
- * Rotate this session's **own** CMMD refresh token.
71525
- *
71526
- * CMMD rotates refresh tokens single-use — the presented hash is revoked inside
71527
- * the rotation transaction (CMMD `server/jwt-auth.ts`). A session must
71528
- * therefore only ever present the refresh token it was issued: rotating a
71529
- * sibling session's token silently invalidates that sibling's stored copy and
71530
- * signs it out.
71531
- */
71532
- function trySelfRefresh(sessionId, context) {
71533
- const currentRefreshToken = readCommandRefreshTokenForSession(sessionId);
71534
- if (!currentRefreshToken) return logCmmdJwtRung(context, "self-refresh", "skipped", "no refresh token stored for this session").pipe(as$3(null));
71535
- return gen(function* () {
71536
- yield* logCmmdJwtRung(context, "self-refresh", "attempted", "rotating this session's own stored refresh token");
71537
- yield* recordCmmdCredentialRefreshMetric("self-refresh", "attempted");
71538
- const refreshed = yield* refreshOnceForSession(sessionId, currentRefreshToken);
71539
- const accessToken = usableCmmdUserJwt(refreshed?.accessToken);
71540
- if (!accessToken) {
71541
- yield* logCmmdJwtRung(context, "self-refresh", "failed", "CMMD refresh did not return a usable user access JWT");
71542
- return null;
71543
- }
71544
- yield* storeResolvedJwt(sessionId, accessToken, refreshed?.refreshToken);
71545
- yield* recordCmmdCredentialRefreshMetric("self-refresh", "succeeded");
71546
- yield* logCmmdJwtRung(context, "self-refresh", "succeeded", "CMMD rotated the stored refresh token");
71547
- return accessToken;
71548
- });
71549
- }
71550
- /**
71551
- * The mint lane's rung (forge#3518, expand phase): mint a short-lived
71552
- * forge-scoped access token from this session's durable session grant.
71553
- *
71554
- * Tried FIRST, before the rotation fence and the whole legacy ladder, when
71555
- * `FORGE_CMMD_TOKEN_MINT_LANE` is on. Minting spends nothing that cannot be
71556
- * spent again (the grant is non-rotating), so unlike the legacy rungs it is
71557
- * safe for a session the rotation fence fences off, and it never feeds the
71558
- * credential-rejection ledger: this rung failing says nothing about whether
71559
- * the legacy rungs below it can still succeed.
71560
- *
71561
- * Only the JWT is persisted (`setCommandJwtForSession`, no refresh token):
71562
- * the short lifetime is the expiry story, and re-minting is the refresh
71563
- * story. A session that lives entirely on this lane accumulates no new
71564
- * long-lived credential state beyond the grant it already had.
71565
- */
71566
- function tryMintShortLivedToken(sessionId, context) {
71567
- const grantToken = readCommandSessionGrantForSession(sessionId);
71568
- if (!grantToken) return logCmmdJwtRung(context, "mint", "skipped", "no session grant stored for this session", "mint").pipe(as$3(null));
71569
- return gen(function* () {
71570
- yield* logCmmdJwtRung(context, "mint", "attempted", "minting a short-lived forge-scoped token from this session's grant", "mint");
71571
- yield* update$3(withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "attempted" })), 1);
71572
- const accessToken = usableCmmdUserJwt((yield* mintOnceForSession(sessionId, grantToken))?.accessToken);
71573
- if (!accessToken) {
71574
- yield* logCmmdJwtRung(context, "mint", "failed", "CMMD token mint did not return a usable user access JWT", "mint");
71575
- return null;
71576
- }
71577
- yield* setCommandJwtForSession(sessionId, accessToken, {}).pipe(ignore({ log: true }));
71578
- yield* update$3(withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "succeeded" })), 1);
71579
- yield* logCmmdJwtRung(context, "mint", "succeeded", "CMMD minted a short-lived forge-scoped token", "mint");
71580
- return accessToken;
71581
- });
71582
- }
71583
- /**
71584
- * Last-resort recovery: redeem this session's durable CMMD session grant.
71585
- *
71586
- * A refresh token is single-use, so a session that lost a rotation — raced, or
71587
- * stranded by a redeploy mid-flight — has no way back and the user is bounced
71588
- * to /login. CMMD mints a long-lived, non-rotating grant at login for exactly
71589
- * this, and redemption returns a fresh JWT, refresh token AND MCP PAT.
71590
- *
71591
- * Only reached after the session's own refresh has already failed, so this
71592
- * never competes with a live rotation. The grant is not cleared on failure:
71593
- * CMMD owns its lifetime, and a transient network failure must not throw away
71594
- * the one credential that can still recover the session.
71595
- */
71596
- function tryGrantRedemption(sessionId, context) {
71597
- const grantToken = readCommandSessionGrantForSession(sessionId);
71598
- if (!grantToken) return logCmmdJwtRung(context, "grant-redemption", "skipped", "no session grant stored for this session").pipe(as$3(null));
71599
- return gen(function* () {
71600
- yield* logCmmdJwtRung(context, "grant-redemption", "attempted", "redeeming this session's durable CMMD session grant");
71601
- yield* recordCmmdCredentialRefreshMetric("grant-redemption", "attempted");
71602
- const redeemed = yield* promise(() => redeemCmmdSessionGrant(grantToken, cmmdSessionPopPublicKey())).pipe(orElseSucceed(() => null));
71603
- const accessToken = usableCmmdUserJwt(redeemed?.accessToken);
71604
- if (!accessToken) {
71605
- yield* logCmmdJwtRung(context, "grant-redemption", "failed", "session grant redemption did not return a usable user access JWT");
71606
- return null;
71607
- }
71608
- if (redeemed?.mcpToken) yield* setCommandAccessTokenForSession(sessionId, redeemed.mcpToken).pipe(ignore({ log: true }));
71609
- yield* storeResolvedJwt(sessionId, accessToken, redeemed?.refreshToken);
71610
- yield* recordCmmdCredentialRefreshMetric("grant-redemption", "succeeded");
71611
- yield* logCmmdJwtRung(context, "grant-redemption", "succeeded", "session grant redeemed");
71612
- return accessToken;
71613
- });
71614
- }
71615
- /**
71616
- * Recover using only credentials this session owns, strongest first.
71617
- *
71618
- * Rotating its own refresh token is preferred; the durable grant is the
71619
- * fallback for when that rotation is already gone. Neither touches another
71620
- * session's credentials, so this ladder is safe to run from any path.
71621
- */
71622
- function tryOwnCredentialRecovery(sessionId, context) {
71623
- return gen(function* () {
71624
- const refreshed = yield* trySelfRefresh(sessionId, context);
71625
- if (refreshed) return refreshed;
71626
- return yield* tryGrantRedemption(sessionId, context);
71627
- });
71628
- }
71629
- function tryPromoteBootstrap(sessionId, subject, forceRefresh, context) {
71630
- if (subject !== "owner-bootstrap" && subject !== "desktop-bootstrap") return logCmmdJwtRung(context, "promote-bootstrap", "skipped", "subject is not a bootstrap subject").pipe(as$3(null));
71631
- return gen(function* () {
71632
- yield* logCmmdJwtRung(context, "promote-bootstrap", "attempted", "promoting the most recent owner CMMD auth to this bootstrap session");
71633
- yield* promoteMostRecentOwnerCommandAuthToSession(sessionId).pipe(catch_((error) => logWarning$1("Failed to promote owner CMMD auth for REST JWT resolution.", {
71634
- cause: error,
71635
- sessionId,
71636
- subject
71637
- })));
71638
- const promotedJwt = usableCmmdUserJwt(readCommandJwtForSession(sessionId));
71639
- if (!forceRefresh && promotedJwt && !isJwtExpiredForSession(sessionId)) {
71640
- yield* logCmmdJwtRung(context, "promote-bootstrap", "succeeded", "promoted owner JWT is usable");
71641
- return promotedJwt;
71642
- }
71643
- 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");
71644
- return null;
71645
- });
71646
- }
71647
- /**
71648
- * Borrow a sibling session's still-valid CMMD access JWT.
71649
- *
71650
- * `readMostRecentJwtForSubject` only returns a JWT that has not expired, so
71651
- * this is a cheap read-only recovery that avoids an upstream rotation. The
71652
- * sibling's *refresh* token is deliberately NOT copied: it is single-use and
71653
- * must stay private to the session it was issued to (see `trySelfRefresh`).
71654
- * Copying it is what let one expired tab sign the user out everywhere.
71655
- */
71656
- function trySubjectFallback(sessionId, subject, context) {
71657
- return gen(function* () {
71658
- yield* logCmmdJwtRung(context, "subject-fallback", "attempted", "looking for a sibling session's still-valid JWT for this subject");
71659
- const fallbackJwt = usableCmmdUserJwt((yield* readMostRecentJwtForSubject(subject).pipe(orElseSucceed(() => null)))?.jwt);
71660
- if (!fallbackJwt) {
71661
- yield* logCmmdJwtRung(context, "subject-fallback", "failed", "no sibling session holds a still-valid JWT for this subject");
71662
- return null;
71663
- }
71664
- yield* storeResolvedJwt(sessionId, fallbackJwt, null);
71665
- yield* logCmmdJwtRung(context, "subject-fallback", "succeeded", "borrowed a sibling session's still-valid JWT");
71666
- return fallbackJwt;
71667
- });
71668
- }
71669
- /**
71670
- * The rungs below the rotation fence: forced own-credential recovery,
71671
- * bootstrap promotion, subject fallback, then own-credential recovery again
71672
- * as the last resort. Split out of `resolveCmmdJwtForSession` so each half of
71673
- * the ladder stays independently readable (and independently testable) —
71674
- * the fence check above is the one decision the split does not touch.
71675
- */
71676
- function runRecoveryLadder(sessionId, normalizedSubject, forceRefresh, context) {
71677
- return gen(function* () {
71678
- if (forceRefresh) {
71679
- const forced = yield* tryOwnCredentialRecovery(sessionId, context);
71680
- if (forced) return forced;
71681
- }
71682
- if (normalizedSubject) {
71683
- const promoted = yield* tryPromoteBootstrap(sessionId, normalizedSubject, forceRefresh, context);
71684
- if (promoted) return promoted;
71685
- }
71686
- if (!normalizedSubject) {
71687
- yield* logCmmdJwtRung(context, "cache", "failed", "no subject to resolve and forced recovery already exhausted");
71688
- return null;
71689
- }
71690
- if (!forceRefresh) {
71691
- const fallback = yield* trySubjectFallback(sessionId, normalizedSubject, context);
71692
- if (fallback) return fallback;
71693
- }
71694
- const recovered = yield* tryOwnCredentialRecovery(sessionId, context);
71695
- if (!recovered) yield* logCmmdJwtRung(context, "cache", "failed", "recovery ladder exhausted: no rung produced a usable JWT");
71696
- return recovered;
71697
- });
71698
- }
71699
- /**
71700
- * Resolve the CMMD REST JWT for a Forge session.
71701
- *
71702
- * Forge session auth is long-lived compared with the short-lived CMMD JWT.
71703
- * When the session JWT expires, recover by minting a fresh short-lived token
71704
- * from the session grant (the mint lane; default-off, forge#3518), or, on
71705
- * the legacy lane, by promoting a valid subject-level JWT or rotating the
71706
- * stored CMMD refresh token.
71707
- *
71708
- * Fail-closed: never returns a non-user / malformed Bearer. Callers must map
71709
- * null to CMMD_AUTH_REQUIRED and must not call CMMD upstream with a bad token.
71710
- */
71711
- function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth", forceRefresh = false, staleAccessToken = null }) {
71712
- const cached = usableCmmdUserJwt(readCommandJwtForSession(sessionId));
71713
- if (cached && !isJwtExpiredForSession(sessionId)) {
71714
- if (!forceRefresh) return succeed(cached);
71715
- if (staleAccessToken && cached !== staleAccessToken) return succeed(cached).pipe(tap(() => gen(function* () {
71716
- const deviceType = yield* readClientDeviceTypeForSession(sessionId);
71717
- yield* logCmmdJwtRung({
71718
- sessionId,
71719
- subject: subject?.trim() || null,
71720
- deviceType,
71721
- caller: logPrefix
71722
- }, "cache", "skipped", "forced refresh skipped: cache already holds a token another caller rotated");
71723
- })));
71724
- }
71725
- const normalizedSubject = subject?.trim() || null;
71726
- const mintLaneEnabled = isCmmdTokenMintLaneEnabled();
71727
- if (!normalizedSubject && !forceRefresh && !mintLaneEnabled) return succeed(null);
71728
- return gen(function* () {
71729
- const context = {
71730
- sessionId,
71731
- subject: normalizedSubject,
71732
- deviceType: yield* readClientDeviceTypeForSession(sessionId),
71733
- caller: logPrefix
71734
- };
71735
- if (mintLaneEnabled) {
71736
- const minted = yield* tryMintShortLivedToken(sessionId, context);
71737
- if (minted) return minted;
71738
- if (!normalizedSubject && !forceRefresh) {
71739
- yield* logCmmdJwtRung(context, "mint", "failed", "no subject to resolve and the mint lane produced nothing", "mint");
71740
- return null;
71741
- }
71742
- }
71743
- if (yield* isRotationFencedSession(sessionId)) {
71744
- yield* logCmmdJwtRung(context, "rotation-fence", "failed", "session is fenced from rotating CMMD credentials");
71745
- return null;
71746
- }
71747
- return yield* runRecoveryLadder(sessionId, normalizedSubject, forceRefresh, context);
71748
- });
71749
- }
71750
- //#endregion
71751
71328
  //#region src/cmmdData/cmmdRestProxyDeadline.ts
71752
71329
  var CmmdProxyTimeoutError = class extends Error {
71753
71330
  status;
@@ -102310,6 +101887,22 @@ var WorkspaceFileSystem = class extends Service$1()("forge/workspace/Services/Wo
102310
101887
  //#region src/workspace/Services/WorkspaceBulkTransfer.ts
102311
101888
  var WorkspaceBulkTransfer = class extends Service$1()("forge/workspace/Services/WorkspaceBulkTransfer") {};
102312
101889
  //#endregion
101890
+ //#region src/workspace/Services/WorkspaceDirectoryListing.ts
101891
+ /**
101892
+ * WorkspaceDirectoryListing - Effect service contract for one directory's
101893
+ * immediate children.
101894
+ *
101895
+ * Owns workspace-root-confined directory listings for the Files tree: the
101896
+ * caller names a workspace root (the thread's checkout) and a root-relative
101897
+ * directory, and gets that directory's children back, one readdir deep.
101898
+ *
101899
+ * @module WorkspaceDirectoryListing
101900
+ */
101901
+ /**
101902
+ * WorkspaceDirectoryListing - Service tag for workspace directory listings.
101903
+ */
101904
+ var WorkspaceDirectoryListing = class extends Service$1()("forge/workspace/Services/WorkspaceDirectoryListing") {};
101905
+ //#endregion
102313
101906
  //#region src/workspace/Services/WorkspaceReadinessState.ts
102314
101907
  var WorkspaceReadinessState = class extends Service$1()("forge/workspace/Services/WorkspaceReadinessState") {};
102315
101908
  //#endregion
@@ -107719,6 +107312,7 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(g
107719
107312
  const discoverSourceControl = yield* makeCachedSourceControlDiscovery;
107720
107313
  const workspaceFileSystem = yield* WorkspaceFileSystem;
107721
107314
  const workspaceBulkTransfer = yield* WorkspaceBulkTransfer;
107315
+ const workspaceDirectoryListing = yield* WorkspaceDirectoryListing;
107722
107316
  const projectCheckoutDeletion = yield* serviceOption(ProjectCheckoutDeletion);
107723
107317
  const workspaceReadinessState = yield* WorkspaceReadinessState;
107724
107318
  const projectSetupScriptRunner = yield* ProjectSetupScriptRunner;
@@ -108067,6 +107661,7 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(g
108067
107661
  blockers: ["deletion_authority_unavailable"]
108068
107662
  }), { "rpc.aggregate": "workspace" }),
108069
107663
  [WS_METHODS.workspaceReadFile]: (input) => observeRpcStream(WS_METHODS.workspaceReadFile, workspaceBulkTransfer.readFile(input), { "rpc.aggregate": "workspace" }),
107664
+ [WS_METHODS.workspaceListDirectory]: (input) => observeRpcEffect(WS_METHODS.workspaceListDirectory, workspaceDirectoryListing.list(input), { "rpc.aggregate": "workspace" }),
108070
107665
  [WS_METHODS.workspaceBeginWriteFile]: (input) => observeRpcEffect(WS_METHODS.workspaceBeginWriteFile, workspaceBulkTransfer.beginWrite(input), { "rpc.aggregate": "workspace" }),
108071
107666
  [WS_METHODS.workspaceWriteFileChunk]: (input) => observeRpcEffect(WS_METHODS.workspaceWriteFileChunk, workspaceBulkTransfer.writeChunk(input), { "rpc.aggregate": "workspace" }),
108072
107667
  [WS_METHODS.workspaceCommitWriteFile]: (input) => observeRpcEffect(WS_METHODS.workspaceCommitWriteFile, workspaceBulkTransfer.commitWrite(input), { "rpc.aggregate": "workspace" }),
@@ -115873,7 +115468,7 @@ const postgresSchemaStatements = [
115873
115468
  "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)",
115874
115469
  "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)",
115875
115470
  "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)",
115876
- "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)",
115471
+ "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)",
115877
115472
  "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 )",
115878
115473
  "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 )",
115879
115474
  "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 )",
@@ -116186,7 +115781,8 @@ const postgresUpgradeMigrations = new Map([
116186
115781
  WHERE model_selection_json IS NULL`
116187
115782
  ]],
116188
115783
  [77, [`ALTER TABLE projection_threads ADD COLUMN IF NOT EXISTS work_session_id TEXT`]],
116189
- [78, [`ALTER TABLE projection_threads ADD COLUMN IF NOT EXISTS work_session_bound_at TEXT`]]
115784
+ [78, [`ALTER TABLE projection_threads ADD COLUMN IF NOT EXISTS work_session_bound_at TEXT`]],
115785
+ [79, [`ALTER TABLE command_access_tokens DROP COLUMN IF EXISTS refresh_token`]]
116190
115786
  ]);
116191
115787
  function requirePostgresUpgradeStatements(version) {
116192
115788
  const statements = postgresUpgradeMigrations.get(version);
@@ -116212,15 +115808,15 @@ const runPostgresMigrations = fn$1("runPostgresMigrations")(function* () {
116212
115808
  ORDER BY version DESC
116213
115809
  LIMIT 1
116214
115810
  `)[0]?.version ?? 0;
116215
- if (currentVersion > 78) return yield* die$1(/* @__PURE__ */ new Error(`Forge Postgres schema ${currentVersion} is newer than server schema 78.`));
115811
+ if (currentVersion > 79) return yield* die$1(/* @__PURE__ */ new Error(`Forge Postgres schema ${currentVersion} is newer than server schema 79.`));
116216
115812
  if (currentVersion === 0) {
116217
115813
  for (const statement of postgresSchemaStatements) yield* sql.unsafe(statement);
116218
115814
  yield* sql`
116219
115815
  INSERT INTO forge_schema_migrations (version)
116220
- VALUES (${78})
115816
+ VALUES (${79})
116221
115817
  ON CONFLICT (version) DO NOTHING
116222
115818
  `;
116223
- } else for (let version = currentVersion + 1; version <= 78; version += 1) {
115819
+ } else for (let version = currentVersion + 1; version <= 79; version += 1) {
116224
115820
  const statements = yield* sync$1(() => requirePostgresUpgradeStatements(version));
116225
115821
  for (const statement of statements) yield* sql.unsafe(statement);
116226
115822
  yield* sql`
@@ -119028,6 +118624,26 @@ var _078_ProjectionThreadWorkSessionBoundAt_default = gen(function* () {
119028
118624
  `;
119029
118625
  });
119030
118626
  //#endregion
118627
+ //#region src/persistence/Migrations/079_DropCommandRefreshToken.ts
118628
+ /**
118629
+ * Drop the single-use CMMD refresh token from the session credential row.
118630
+ *
118631
+ * The rotation it powered is gone. forge#3518 added the CMMD mint lane, and
118632
+ * forge#3521 deleted the legacy ladder that sat beside it: the rotation fence,
118633
+ * the refresh single-flight, self-refresh, grant redemption, the subject
118634
+ * fallback, and the desktop refresh sweep. The resolver now mints a short-lived
118635
+ * JWT from the row's durable, non-rotating session grant, so a credential whose
118636
+ * every use invalidates it has nothing left to do on this row.
118637
+ *
118638
+ * Nothing is copied out first. Every surviving read of the column was deleted
118639
+ * in the same change; the value is a dead secret, and the kind of secret best
118640
+ * kept nowhere. Historical installs that ran migration 034 need this to run
118641
+ * after it, not instead of it.
118642
+ */
118643
+ var _079_DropCommandRefreshToken_default = gen(function* () {
118644
+ yield* (yield* SqlClient)`ALTER TABLE command_access_tokens DROP COLUMN refresh_token`;
118645
+ });
118646
+ //#endregion
119031
118647
  //#region src/persistence/Migrations.ts
119032
118648
  /**
119033
118649
  * MigrationsLive - Migration runner with inline loader
@@ -119438,6 +119054,11 @@ const migrationEntries = [
119438
119054
  78,
119439
119055
  "ProjectionThreadWorkSessionBoundAt",
119440
119056
  _078_ProjectionThreadWorkSessionBoundAt_default
119057
+ ],
119058
+ [
119059
+ 79,
119060
+ "DropCommandRefreshToken",
119061
+ _079_DropCommandRefreshToken_default
119441
119062
  ]
119442
119063
  ];
119443
119064
  const makeMigrationLoader = (throughId) => fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -298465,6 +298086,104 @@ const makeWorkspaceBulkTransfer = (options = {}) => gen(function* () {
298465
298086
  });
298466
298087
  const WorkspaceBulkTransferLive = effect(WorkspaceBulkTransfer, makeWorkspaceBulkTransfer());
298467
298088
  //#endregion
298089
+ //#region src/workspace/Layers/WorkspaceDirectoryListing.ts
298090
+ function isPathWithin(path, root, candidate) {
298091
+ const relative = path.relative(root, candidate);
298092
+ return relative.length === 0 || !relative.startsWith("..") && !path.isAbsolute(relative);
298093
+ }
298094
+ /**
298095
+ * Directories sort before files so the tree reads like every other file tree;
298096
+ * names compare case-insensitively, then by raw name so stable order survives
298097
+ * a platform that folds case differently.
298098
+ */
298099
+ function compareEntries(left, right) {
298100
+ if (left.kind !== right.kind) return left.kind === "directory" ? -1 : 1;
298101
+ const byName = left.name.localeCompare(right.name, void 0, { sensitivity: "accent" });
298102
+ return byName !== 0 ? byName : left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
298103
+ }
298104
+ const WorkspaceDirectoryListingLive = effect(WorkspaceDirectoryListing, gen(function* () {
298105
+ const fileSystem = yield* FileSystem;
298106
+ const path = yield* Path$2;
298107
+ const workspacePaths = yield* WorkspacePaths;
298108
+ return { list: fn$1("WorkspaceDirectoryListing.list")(function* (input) {
298109
+ const workspaceRoot = yield* workspacePaths.normalizeWorkspaceRoot(input.cwd).pipe(mapError(() => new WorkspaceListDirectoryError({
298110
+ cwd: input.cwd,
298111
+ relativePath: input.relativePath,
298112
+ failure: "workspace_root_invalid",
298113
+ detail: `Workspace root is not a usable directory: ${input.cwd}`
298114
+ })));
298115
+ const trimmed = input.relativePath.trim();
298116
+ if (path.isAbsolute(trimmed)) return yield* new WorkspaceListDirectoryError({
298117
+ cwd: input.cwd,
298118
+ relativePath: input.relativePath,
298119
+ failure: "path_outside_workspace",
298120
+ detail: "Directory listings must use workspace-relative paths."
298121
+ });
298122
+ const resolved = trimmed.length === 0 ? {
298123
+ absolutePath: workspaceRoot,
298124
+ relativePath: ""
298125
+ } : yield* workspacePaths.resolveRelativePathWithinRoot({
298126
+ workspaceRoot,
298127
+ relativePath: trimmed
298128
+ }).pipe(mapError(() => new WorkspaceListDirectoryError({
298129
+ cwd: input.cwd,
298130
+ relativePath: input.relativePath,
298131
+ failure: "path_outside_workspace",
298132
+ detail: `Directory is outside the workspace: ${input.relativePath}`
298133
+ })));
298134
+ const canonicalRoot = yield* fileSystem.realPath(workspaceRoot).pipe(orDie);
298135
+ const targetRealPath = yield* fileSystem.realPath(resolved.absolutePath).pipe(catch_(() => succeed(null)));
298136
+ if (targetRealPath === null) return yield* new WorkspaceListDirectoryError({
298137
+ cwd: input.cwd,
298138
+ relativePath: input.relativePath,
298139
+ failure: "read_directory_failed",
298140
+ detail: `Directory does not exist: ${input.relativePath || "."}`
298141
+ });
298142
+ if (!isPathWithin(path, canonicalRoot, targetRealPath)) return yield* new WorkspaceListDirectoryError({
298143
+ cwd: input.cwd,
298144
+ relativePath: input.relativePath,
298145
+ failure: "path_outside_workspace",
298146
+ detail: `Directory does not resolve inside the workspace: ${input.relativePath}`
298147
+ });
298148
+ const targetStat = yield* fileSystem.stat(targetRealPath).pipe(option);
298149
+ if (targetStat._tag === "None" || targetStat.value.type !== "Directory") return yield* new WorkspaceListDirectoryError({
298150
+ cwd: input.cwd,
298151
+ relativePath: input.relativePath,
298152
+ failure: "read_directory_failed",
298153
+ detail: `Not a directory: ${input.relativePath}`
298154
+ });
298155
+ return { entries: (yield* forEach(yield* fileSystem.readDirectory(targetRealPath).pipe(mapError((cause) => new WorkspaceListDirectoryError({
298156
+ cwd: input.cwd,
298157
+ relativePath: input.relativePath,
298158
+ failure: "read_directory_failed",
298159
+ detail: `Could not read directory: ${input.relativePath}`,
298160
+ cause
298161
+ }))), (name) => gen(function* () {
298162
+ const entryPath = path.join(targetRealPath, name);
298163
+ if ((yield* fileSystem.readLink(entryPath).pipe(option))._tag === "Some") return {
298164
+ name,
298165
+ kind: "file",
298166
+ hasChildren: false
298167
+ };
298168
+ const stat = yield* fileSystem.stat(entryPath).pipe(option);
298169
+ if (stat._tag === "None") return void 0;
298170
+ if (stat.value.type === "Directory") {
298171
+ const children = yield* fileSystem.readDirectory(entryPath).pipe(option);
298172
+ return {
298173
+ name,
298174
+ kind: "directory",
298175
+ hasChildren: children._tag === "Some" && children.value.length > 0
298176
+ };
298177
+ }
298178
+ return {
298179
+ name,
298180
+ kind: "file",
298181
+ hasChildren: false
298182
+ };
298183
+ }), { concurrency: 16 })).filter((entry) => entry !== void 0).toSorted(compareEntries) };
298184
+ }) };
298185
+ }));
298186
+ //#endregion
298468
298187
  //#region src/workspace/Layers/WorkspacePaths.ts
298469
298188
  function toPosixRelativePath(input) {
298470
298189
  return input.replaceAll("\\", "/");
@@ -300058,7 +299777,7 @@ function resolveBuildCommitFromEnv(env) {
300058
299777
  * environment descriptor down instead of reporting an honest "unknown".
300059
299778
  */
300060
299779
  function readBakedBuildCommit() {
300061
- return "84e1e008ce3e3d30e5e80361598aa54ed7bda92d";
299780
+ return "15aa06fedcdcf9e165622cf018e31ce724b98ef7";
300062
299781
  }
300063
299782
  async function resolveServerBuildCommit(input) {
300064
299783
  if (isFullCommitSha(input.baked)) return input.baked;
@@ -300304,14 +300023,13 @@ function identityFromRedeemed(redeemed) {
300304
300023
  username: redeemed.displayName?.trim() || `user-${redeemed.userId}`
300305
300024
  };
300306
300025
  }
300026
+ /** Read optional display name, owner assertion, session grant, and expiry from a redeemed handoff. */
300307
300027
  function optionalRedeemedFields(body) {
300308
- const refreshToken = readTrimmedString(body.refreshToken);
300309
300028
  const displayName = typeof body.displayName === "string" ? body.displayName : void 0;
300310
300029
  const forgeOwnerAssertion = readTrimmedString(body.forgeOwnerAssertion);
300311
300030
  const sessionGrant = readTrimmedString(body.sessionGrant);
300312
300031
  const sessionGrantExpiresAt = typeof body.sessionGrantExpiresAt === "string" ? body.sessionGrantExpiresAt : void 0;
300313
300032
  return {
300314
- ...refreshToken !== void 0 ? { refreshToken } : {},
300315
300033
  ...displayName !== void 0 ? { displayName } : {},
300316
300034
  ...forgeOwnerAssertion !== void 0 ? { forgeOwnerAssertion } : {},
300317
300035
  ...sessionGrant !== void 0 ? { sessionGrant } : {},
@@ -300379,6 +300097,7 @@ function redeemEmbedCodeWithCmmd(code) {
300379
300097
  return succeed(redeemed);
300380
300098
  }));
300381
300099
  }
300100
+ /** Store the redeemed PAT, JWT, and identity for a local embed session, failing on write errors. */
300382
300101
  function persistLocalEmbedSession(input) {
300383
300102
  return gen(function* () {
300384
300103
  yield* setCommandAccessTokenForSession(input.sessionId, input.redeemed.mcpToken).pipe(mapError((cause) => new AuthError({
@@ -300386,7 +300105,7 @@ function persistLocalEmbedSession(input) {
300386
300105
  status: 500,
300387
300106
  cause
300388
300107
  })));
300389
- yield* setCommandJwtForSession(input.sessionId, input.redeemed.accessToken, input.redeemed.refreshToken ? { refreshToken: input.redeemed.refreshToken } : {}).pipe(mapError((cause) => new AuthError({
300108
+ yield* setCommandJwtForSession(input.sessionId, input.redeemed.accessToken).pipe(mapError((cause) => new AuthError({
300390
300109
  message: "Failed to persist CMMD JWT.",
300391
300110
  status: 500,
300392
300111
  cause
@@ -300441,7 +300160,6 @@ const authCmmdEmbedExchangeRouteLayer = add$1("POST", "/api/auth/cmmd-embed/exch
300441
300160
  accessToken: redeemed.mcpToken,
300442
300161
  identity,
300443
300162
  jwtToken: redeemed.accessToken,
300444
- refreshToken: redeemed.refreshToken,
300445
300163
  sessionGrant,
300446
300164
  sessionGrantExpiresAt: redeemed.sessionGrantExpiresAt
300447
300165
  });
@@ -300547,7 +300265,7 @@ const cloudOwnerSessionRedeemRouteLayer = add$1("POST", "/api/auth/cloud/session
300547
300265
  status: 403
300548
300266
  });
300549
300267
  const tokenResult = yield* redeemSessionGrantWithCmmd(payload);
300550
- const { tokenObj, cmmdIdentity, userId, accessToken, mcpToken, refreshToken, forgeOwnerAssertion, sessionGrantExpiresAt, isJwt, patToStore, jwtToStore } = yield* resolveCmmdTokenBundleCredentials(tokenResult);
300268
+ const { tokenObj, cmmdIdentity, userId, accessToken, mcpToken, forgeOwnerAssertion, sessionGrantExpiresAt, isJwt, patToStore, jwtToStore } = yield* resolveCmmdTokenBundleCredentials(tokenResult);
300551
300269
  if (!cmmdIdentity || !userId || !patToStore) {
300552
300270
  yield* logError("Forge Cloud session redeem: CMMD token response incomplete.", {
300553
300271
  clientId: payload.clientId,
@@ -300556,7 +300274,6 @@ const cloudOwnerSessionRedeemRouteLayer = add$1("POST", "/api/auth/cloud/session
300556
300274
  tokenResult,
300557
300275
  accessToken,
300558
300276
  mcpToken,
300559
- refreshToken,
300560
300277
  isJwt,
300561
300278
  userId,
300562
300279
  patToStore,
@@ -300579,7 +300296,6 @@ const cloudOwnerSessionRedeemRouteLayer = add$1("POST", "/api/auth/cloud/session
300579
300296
  accessToken: patToStore,
300580
300297
  identity: cmmdIdentity,
300581
300298
  jwtToken: jwtToStore,
300582
- refreshToken,
300583
300299
  sessionGrant: payload.sessionGrant,
300584
300300
  sessionGrantExpiresAt
300585
300301
  });
@@ -324589,7 +324305,7 @@ const ProjectLeadSchedulerRuntimeLayerLive = makeProjectLeadSchedulerRuntimeLive
324589
324305
  const GitManagerLayerLive = GitManagerLive.pipe(provideMerge(ProjectSetupScriptRunnerLive), provideMerge(GitCoreLive), provideMerge(GitHubCliLive), provideMerge(RoutingTextGenerationLive.pipe(provide$1(TextGenerationUsageLive.pipe(provide$1(RuntimeReplicationOutboxLive), provide$1(TextGenerationSessionRepositoryLive))))));
324590
324306
  const GitLayerLive = empty$4.pipe(provideMerge(GitManagerLayerLive), provideMerge(GitStatusBroadcasterLive.pipe(provide$1(GitManagerLayerLive))), provideMerge(GitCoreLive));
324591
324307
  const TerminalLayerLive = TerminalManagerLive.pipe(provide$1(PtyAdapterLive));
324592
- const WorkspaceLayerLive = mergeAll(WorkspacePathsLive, ProjectCheckoutDeletionLive.pipe(provide$1(GitCoreLive), provide$1(ProjectionProjectRepositoryLive)), WorkspaceEntriesLive.pipe(provide$1(WorkspacePathsLive)), WorkspaceFileSystemLive.pipe(provide$1(WorkspacePathsLive), provide$1(WorkspaceEntriesLive.pipe(provide$1(WorkspacePathsLive)))), WorkspaceBulkTransferLive.pipe(provide$1(WorkspacePathsLive), provide$1(WorkspaceEntriesLive.pipe(provide$1(WorkspacePathsLive)))));
324308
+ const WorkspaceLayerLive = mergeAll(WorkspacePathsLive, ProjectCheckoutDeletionLive.pipe(provide$1(GitCoreLive), provide$1(ProjectionProjectRepositoryLive)), WorkspaceEntriesLive.pipe(provide$1(WorkspacePathsLive)), WorkspaceFileSystemLive.pipe(provide$1(WorkspacePathsLive), provide$1(WorkspaceEntriesLive.pipe(provide$1(WorkspacePathsLive)))), WorkspaceBulkTransferLive.pipe(provide$1(WorkspacePathsLive), provide$1(WorkspaceEntriesLive.pipe(provide$1(WorkspacePathsLive)))), WorkspaceDirectoryListingLive.pipe(provide$1(WorkspacePathsLive)));
324593
324309
  const AuthLayerLive = ServerAuthLive.pipe(provideMerge(PersistenceLayerLive), provide$1(ServerSecretStoreLayerLive));
324594
324310
  const MiscRuntimeLayerLive = mergeAll(AnalyticsServiceLayerLive, OpenLive, ServerLifecycleEventsLive, layer$4);
324595
324311
  const RuntimeDependenciesWithoutPullRequestsLive = ReactorLayerLive.pipe(provideMerge(CheckpointingLayerLive), provideMerge(GitLayerLive), provideMerge(OrchestrationLayerLive), provideMerge(ProviderLayerLive), provideMerge(TerminalLayerLive), provideMerge(AgentBrowserManagerLive), provideMerge(mergeAll(ThreadDevRuntimeLive, DevTunnelLive.pipe(provide$1(ThreadDevRuntimeLive)))), provideMerge(PersistenceLayerLive), provideMerge(KeybindingsLive), provideMerge(ProviderRegistryLive), provideMerge(mergeAll(ServerSettingsLive, UsageServiceLive.pipe(provide$1(ServerSettingsLive), provide$1(UsageSessionLookupLive), provide$1(UsageRecordedSourceLive.pipe(provide$1(OrchestrationLayerLive), provide$1(RuntimeReplicationOutboxLive), provide$1(ProviderSessionRuntimeRepositoryLive), provide$1(TextGenerationSessionRepositoryLive)))))), provideMerge(WorkspaceLayerLive), provideMerge(WorkspaceReadinessStateLive), provideMerge(ProjectFaviconResolverLive), provideMerge(RepositoryIdentityResolverLive), provideMerge(ServerEnvironmentLive), provideMerge(AuthLayerLive), provideMerge(ServerSecretStoreLayerLive), provideMerge(mergeAll(CloudRedisCoordinationLive, CloudStateSyncLive.pipe(provide$1(PersistenceLayerLive)))), provideMerge(MiscRuntimeLayerLive));