@p4code/cli 0.0.40 → 0.0.41

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 (86) hide show
  1. package/README.md +18 -18
  2. package/dist/bin.mjs +1346 -1157
  3. package/dist/client/assets/{DiffPanel-CWTAOHXy.js → DiffPanel-D8nVXZ0Y.js} +2 -2
  4. package/dist/client/assets/{FilePreviewPanel-CMon_ZW_.js → FilePreviewPanel-CFKCFyJ3.js} +2 -2
  5. package/dist/client/assets/{PreviewPanel-BbkJhucn.js → PreviewPanel-VQey9906.js} +2 -2
  6. package/dist/client/assets/{angular-html-eRIeZaHm.js → angular-html-D2LtifxG.js} +2 -2
  7. package/dist/client/assets/{angular-ts-CSJI6jVI.js → angular-ts-oVDpKJb2.js} +2 -2
  8. package/dist/client/assets/{apl-N_WYRnOX.js → apl-2vnm7rdp.js} +2 -2
  9. package/dist/client/assets/arrow-right-DmJqM7J1.js +2 -0
  10. package/dist/client/assets/{astro-YwKix1hR.js → astro-CDbEfI_f.js} +2 -2
  11. package/dist/client/assets/{blade-BvX6Aco4.js → blade-B1MwvSO0.js} +2 -2
  12. package/dist/client/assets/{c-DzTwtKJT.js → c-Bi5eHJiI.js} +2 -2
  13. package/dist/client/assets/{cobol-C-OXLFvN.js → cobol-BxESWi7D.js} +2 -2
  14. package/dist/client/assets/{coffee-DZE-1kG8.js → coffee-s151wUsp.js} +2 -2
  15. package/dist/client/assets/{cpp-CeJP0fjF.js → cpp-B51xjAkw.js} +2 -2
  16. package/dist/client/assets/{crystal-ZNMNBNdv.js → crystal-BW0dq2Rw.js} +2 -2
  17. package/dist/client/assets/{css-MuTAnxi-.js → css-Czn5ycv4.js} +2 -2
  18. package/dist/client/assets/{edge-L_Gi4x7J.js → edge-DPtVbjt1.js} +2 -2
  19. package/dist/client/assets/{elixir-CY-skSPd.js → elixir-381Dy51E.js} +2 -2
  20. package/dist/client/assets/{elm-BOr95y4k.js → elm-Bpu_ckb9.js} +2 -2
  21. package/dist/client/assets/{erb-oPVsfoEW.js → erb-u1vpMXOw.js} +2 -2
  22. package/dist/client/assets/{fileCommentAnnotations-B5fswj6j.js → fileCommentAnnotations-BwnrRpfq.js} +2 -2
  23. package/dist/client/assets/{git-rebase-DCP3SOJt.js → git-rebase-ShzdQ6mA.js} +2 -2
  24. package/dist/client/assets/{glimmer-js-ocvtKWV0.js → glimmer-js-DI4_LeGA.js} +2 -2
  25. package/dist/client/assets/{glimmer-ts-D9NLnrn4.js → glimmer-ts-BrA1R4ia.js} +2 -2
  26. package/dist/client/assets/{glsl-B4zX2LmT.js → glsl--BAUZuR7.js} +2 -2
  27. package/dist/client/assets/{graphql-DiTBL-8c.js → graphql-XmtNe4tX.js} +2 -2
  28. package/dist/client/assets/{hack-akJz2jQV.js → hack-ZPcuqt6I.js} +2 -2
  29. package/dist/client/assets/{haml-BVeQWTnw.js → haml-BLmOYSAV.js} +2 -2
  30. package/dist/client/assets/{handlebars--grKNHAn.js → handlebars-BxR19vQc.js} +2 -2
  31. package/dist/client/assets/{html-B2pBdK0D.js → html-Do3TWS07.js} +2 -2
  32. package/dist/client/assets/{html-derivative-CFwZKVpu.js → html-derivative-CLJquIOq.js} +2 -2
  33. package/dist/client/assets/{http-DzxCdJrg.js → http-C6M7KGrt.js} +2 -2
  34. package/dist/client/assets/{hurl-kzM3zOis.js → hurl-AFYDoSjV.js} +2 -2
  35. package/dist/client/assets/index-XxxL_T8f.js +635 -0
  36. package/dist/client/assets/{java-BC2ZGkYM.js → java-DoRNyNZ_.js} +2 -2
  37. package/dist/client/assets/{javascript-gqBOm55O.js → javascript-Dd-zEQJs.js} +2 -2
  38. package/dist/client/assets/{jinja-nVgqZ5uh.js → jinja-TJRtEusk.js} +2 -2
  39. package/dist/client/assets/{jison-Bvv8hysr.js → jison-bG9mcEJe.js} +2 -2
  40. package/dist/client/assets/{json-CLQfEwQv.js → json-DY6EaQUk.js} +2 -2
  41. package/dist/client/assets/{jsx-Bhahz-OT.js → jsx-BuL0_m-Z.js} +2 -2
  42. package/dist/client/assets/{julia-DJU1mzIk.js → julia-4bcuyKPV.js} +2 -2
  43. package/dist/client/assets/{just-XGKk4v1o.js → just-F3UNctxi.js} +2 -2
  44. package/dist/client/assets/{latex-CJcPMeCn.js → latex-DCJY-Aag.js} +2 -2
  45. package/dist/client/assets/{liquid-EuLmnfV7.js → liquid-M906bGYJ.js} +2 -2
  46. package/dist/client/assets/{lua-B9R161Tp.js → lua-CeupCtSb.js} +2 -2
  47. package/dist/client/assets/{marko-CB03L6Sz.js → marko-D_3SsXZn.js} +2 -2
  48. package/dist/client/assets/{mdc-Ccr4X0uO.js → mdc-DRUaGPVE.js} +2 -2
  49. package/dist/client/assets/{nginx-BS3I1QaZ.js → nginx-B_DJSFb0.js} +2 -2
  50. package/dist/client/assets/{nim-D7Zyd_8R.js → nim-BxoRFkNd.js} +2 -2
  51. package/dist/client/assets/{perl-e48m28fL.js → perl-CjLiNsGL.js} +2 -2
  52. package/dist/client/assets/{php-ocRBBnGb.js → php-DcklQON0.js} +2 -2
  53. package/dist/client/assets/{pug-BBs8wX-p.js → pug-DHYHUkbl.js} +2 -2
  54. package/dist/client/assets/{qml-Dwjp2Qzl.js → qml-Cggn-sFR.js} +2 -2
  55. package/dist/client/assets/{r-DF1H1luy.js → r-BhkOaKoM.js} +2 -2
  56. package/dist/client/assets/{razor-U_WjoOo_.js → razor-DksRt091.js} +2 -2
  57. package/dist/client/assets/{regexp-DGgHOmJp.js → regexp-CVeVJKpO.js} +2 -2
  58. package/dist/client/assets/{rst-BUd3Iwh2.js → rst-B8gnyB7p.js} +2 -2
  59. package/dist/client/assets/{ruby-CXnn06qc.js → ruby-B7aYI7EY.js} +2 -2
  60. package/dist/client/assets/{sas-BWPra-UJ.js → sas-DmDsVHsr.js} +2 -2
  61. package/dist/client/assets/{scss-YWDt-8TA.js → scss-Cf0hFbmP.js} +2 -2
  62. package/dist/client/assets/{shellscript-DNaMiy_8.js → shellscript-CJ2l7Ljo.js} +2 -2
  63. package/dist/client/assets/{shellsession-4u8MjHqU.js → shellsession-CE-6mg0T.js} +2 -2
  64. package/dist/client/assets/{soy-DjtwNNsb.js → soy-eTwUOhLL.js} +2 -2
  65. package/dist/client/assets/{sql-Dn7Q7_bz.js → sql-DaTGJ1E1.js} +2 -2
  66. package/dist/client/assets/{stata-B9KaDIK8.js → stata-DCqqmU-V.js} +2 -2
  67. package/dist/client/assets/{surrealql-C8pR4xmG.js → surrealql-DJuyQLb6.js} +2 -2
  68. package/dist/client/assets/{svelte-BsaIxqiC.js → svelte-DZfElFr5.js} +2 -2
  69. package/dist/client/assets/{templ-CzR4BK3F.js → templ-BeuT7j1S.js} +2 -2
  70. package/dist/client/assets/{tex-G7GUVjb0.js → tex-CUjxQ3iG.js} +2 -2
  71. package/dist/client/assets/textarea-BM2z9WNB.js +46 -0
  72. package/dist/client/assets/{ts-tags-5xxlNsGf.js → ts-tags-C00W--Ve.js} +2 -2
  73. package/dist/client/assets/{tsx-DVYZm60Q.js → tsx-h-kXP2gA.js} +2 -2
  74. package/dist/client/assets/{twig-G0p9qB4O.js → twig-DhMoAQY-.js} +2 -2
  75. package/dist/client/assets/{typescript-D8l0OAzJ.js → typescript-DwJdYtiC.js} +2 -2
  76. package/dist/client/assets/{vue-Bd-pqwQF.js → vue-CvLnzHbi.js} +2 -2
  77. package/dist/client/assets/{vue-html-CJEcN7mo.js → vue-html-fj2AgeR4.js} +2 -2
  78. package/dist/client/assets/{vue-vine-D-v2YHMk.js → vue-vine-D1-Lx9ZR.js} +2 -2
  79. package/dist/client/assets/{xml-JS10jM3R.js → xml-CDkXSKu_.js} +2 -2
  80. package/dist/client/assets/{xsl-BsXeGtCx.js → xsl-BkotjMT0.js} +2 -2
  81. package/dist/client/assets/{yaml-DUaQ_QeW.js → yaml-BssI7mmn.js} +2 -2
  82. package/dist/client/index.html +2 -2
  83. package/package.json +2 -2
  84. package/dist/client/assets/arrow-right-CfIjGfWz.js +0 -2
  85. package/dist/client/assets/index-C-C3Iio5.js +0 -635
  86. package/dist/client/assets/textarea-BJwGmoQB.js +0 -46
package/dist/bin.mjs CHANGED
@@ -7298,9 +7298,16 @@ const TaskToolError = Schema$1.Union([
7298
7298
  */
7299
7299
  const AGENT_ASSET_NAME_MAX_LENGTH = 128;
7300
7300
  const AGENT_ASSET_FILE_PATH_MAX_LENGTH = 256;
7301
- /** Per-file and whole-asset ceilings, enforced at the hub boundary. */
7301
+ /**
7302
+ * Per-file and whole-asset ceilings, enforced at the hub boundary.
7303
+ *
7304
+ * The byte total is the meaningful bound — it is what crosses the wire and what
7305
+ * a row costs. The file count is a backstop against something pathological, and
7306
+ * was initially set low enough to refuse real skills: a documentation-heavy one
7307
+ * with a reference directory passes 64 files without being remotely large.
7308
+ */
7302
7309
  const AGENT_ASSET_FILE_MAX_CHARS = 512 * 1024;
7303
- const AGENT_ASSET_TOTAL_MAX_BYTES = 2 * 1024 * 1024;
7310
+ const AGENT_ASSET_TOTAL_MAX_BYTES = 4 * 1024 * 1024;
7304
7311
  /**
7305
7312
  * What kind of thing this is.
7306
7313
  *
@@ -7351,7 +7358,7 @@ const AgentAssetFile = Schema$1.Struct({
7351
7358
  path: AgentAssetFilePath,
7352
7359
  content: Schema$1.String.check(Schema$1.isMaxLength(AGENT_ASSET_FILE_MAX_CHARS))
7353
7360
  });
7354
- const AgentAssetFiles = Schema$1.Array(AgentAssetFile).check(Schema$1.isMinLength(1), Schema$1.isMaxLength(64));
7361
+ const AgentAssetFiles = Schema$1.Array(AgentAssetFile).check(Schema$1.isMinLength(1), Schema$1.isMaxLength(512));
7355
7362
  /**
7356
7363
  * A user-scope asset as the hub holds it.
7357
7364
  *
@@ -7400,6 +7407,20 @@ const AgentAssetListFilter = Schema$1.Struct({ kind: Schema$1.optional(AgentAsse
7400
7407
  * disconnecting.
7401
7408
  */
7402
7409
  const HubSyncMode = Schema$1.Literals(["auto", "paused"]);
7410
+ /**
7411
+ * Whether new local skills are shared automatically.
7412
+ *
7413
+ * `all` by default, because the hub exists so that a skill added on one machine
7414
+ * does not change agent behaviour there and nowhere else — and a share you have
7415
+ * to remember to perform is one that silently stops happening. `selected` is
7416
+ * for a machine that holds skills which should stay put.
7417
+ *
7418
+ * Note what this does not change: nothing is ever taken over. A name the hub
7419
+ * already holds from another machine is reported as a collision rather than
7420
+ * overwritten, whichever mode is set.
7421
+ */
7422
+ const HubShareMode = Schema$1.Literals(["all", "selected"]);
7423
+ const HubShareModeInput = Schema$1.Struct({ mode: HubShareMode });
7403
7424
  /** Where a server's hub link came from, so the UI can say why it is read-only. */
7404
7425
  const HubLinkSource = Schema$1.Literals([
7405
7426
  "environment",
@@ -7440,6 +7461,7 @@ const SkillSyncStatus = Schema$1.Struct({
7440
7461
  configured: Schema$1.Boolean,
7441
7462
  hubUrl: Schema$1.NullOr(Schema$1.String),
7442
7463
  syncMode: HubSyncMode,
7464
+ shareMode: HubShareMode,
7443
7465
  source: HubLinkSource,
7444
7466
  skillsRoot: Schema$1.NullOr(Schema$1.String),
7445
7467
  entries: Schema$1.Array(SkillSyncEntry),
@@ -7749,9 +7771,11 @@ const WS_METHODS = {
7749
7771
  hubConnect: "hub.connect",
7750
7772
  hubDisconnect: "hub.disconnect",
7751
7773
  hubSetSyncMode: "hub.setSyncMode",
7774
+ hubSetShareMode: "hub.setShareMode",
7752
7775
  hubMintToken: "hub.mintToken",
7753
7776
  skillsSync: "skills.sync",
7754
7777
  skillsPublish: "skills.publish",
7778
+ skillsPublishAll: "skills.publishAll",
7755
7779
  skillsUnpublish: "skills.unpublish",
7756
7780
  sourceControlLookupRepository: "sourceControl.lookupRepository",
7757
7781
  sourceControlCloneRepository: "sourceControl.cloneRepository",
@@ -7849,6 +7873,11 @@ const WsHubSetSyncModeRpc = Rpc.make(WS_METHODS.hubSetSyncMode, {
7849
7873
  * has no `crypto.subtle`, and a token generator that silently degrades is not
7850
7874
  * a thing to ship.
7851
7875
  */
7876
+ const WsHubSetShareModeRpc = Rpc.make(WS_METHODS.hubSetShareMode, {
7877
+ payload: HubShareModeInput,
7878
+ success: SkillSyncStatus,
7879
+ error: HubRpcError
7880
+ });
7852
7881
  const WsHubMintTokenRpc = Rpc.make(WS_METHODS.hubMintToken, {
7853
7882
  payload: Schema$1.Struct({}),
7854
7883
  success: HubMintedToken,
@@ -7870,6 +7899,14 @@ const WsSkillsPublishRpc = Rpc.make(WS_METHODS.skillsPublish, {
7870
7899
  }),
7871
7900
  error: Schema$1.Union([EnvironmentAuthorizationError])
7872
7901
  });
7902
+ const WsSkillsPublishAllRpc = Rpc.make(WS_METHODS.skillsPublishAll, {
7903
+ payload: Schema$1.Struct({}),
7904
+ success: Schema$1.Struct({
7905
+ report: SkillSyncReport,
7906
+ status: SkillSyncStatus
7907
+ }),
7908
+ error: Schema$1.Union([EnvironmentAuthorizationError])
7909
+ });
7873
7910
  const WsSkillsUnpublishRpc = Rpc.make(WS_METHODS.skillsUnpublish, {
7874
7911
  payload: SkillNameInput,
7875
7912
  success: Schema$1.Struct({
@@ -8246,7 +8283,7 @@ const WsSubscribeAuthAccessRpc = Rpc.make(WS_METHODS.subscribeAuthAccess, {
8246
8283
  error: Schema$1.Union([AuthAccessStreamError, EnvironmentAuthorizationError]),
8247
8284
  stream: true
8248
8285
  });
8249
- const WsRpcGroup = RpcGroup.make(WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetTraceDiagnosticsRpc, WsServerGetProcessDiagnosticsRpc, WsServerGetProcessResourceHistoryRpc, WsServerSignalProcessRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc, WsSourceControlPublishRepositoryRpc, WsProjectsListEntriesRpc, WsProjectsReadFileRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsShellOpenInEditorRpc, WsFilesystemBrowseRpc, WsAssetsCreateUrlRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsVcsListRefsRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsReviewGetDiffPreviewRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsPreviewOpenRpc, WsPreviewNavigateRpc, WsPreviewResizeRpc, WsPreviewRefreshRpc, WsPreviewCloseRpc, WsPreviewListRpc, WsPreviewReportStatusRpc, WsPreviewAutomationConnectRpc, WsPreviewAutomationRespondRpc, WsPreviewAutomationFocusHostRpc, WsSubscribePreviewEventsRpc, WsSubscribeDiscoveredLocalServersRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsTasksListRpc, WsTasksGetRpc, WsTasksCreateRpc, WsTasksUpdateRpc, WsTasksDeleteRpc, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsUnpublishRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
8286
+ const WsRpcGroup = RpcGroup.make(WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetTraceDiagnosticsRpc, WsServerGetProcessDiagnosticsRpc, WsServerGetProcessResourceHistoryRpc, WsServerSignalProcessRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc, WsSourceControlPublishRepositoryRpc, WsProjectsListEntriesRpc, WsProjectsReadFileRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsShellOpenInEditorRpc, WsFilesystemBrowseRpc, WsAssetsCreateUrlRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsVcsListRefsRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsReviewGetDiffPreviewRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsPreviewOpenRpc, WsPreviewNavigateRpc, WsPreviewResizeRpc, WsPreviewRefreshRpc, WsPreviewCloseRpc, WsPreviewListRpc, WsPreviewReportStatusRpc, WsPreviewAutomationConnectRpc, WsPreviewAutomationRespondRpc, WsPreviewAutomationFocusHostRpc, WsSubscribePreviewEventsRpc, WsSubscribeDiscoveredLocalServersRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsTasksListRpc, WsTasksGetRpc, WsTasksCreateRpc, WsTasksUpdateRpc, WsTasksDeleteRpc, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
8250
8287
  //#endregion
8251
8288
  //#region ../../packages/shared/src/oauthScope.ts
8252
8289
  const OAUTH_SCOPE_TOKEN = /^[\u0021\u0023-\u005b\u005d-\u007e]+$/u;
@@ -8481,58 +8518,109 @@ Schema$1.TaggedErrorClass()("ProviderSessionRepositoryPersistenceError", {
8481
8518
  cause: Schema$1.optional(Schema$1.Defect())
8482
8519
  });
8483
8520
  //#endregion
8484
- //#region src/persistence/AuthPairingLinks.ts
8485
- const AuthPairingLinkRecord = Schema$1.Struct({
8486
- id: Schema$1.String,
8487
- credential: Schema$1.String,
8488
- method: Schema$1.Literals(["desktop-bootstrap", "one-time-token"]),
8489
- scopes: Schema$1.fromJsonString(AuthEnvironmentScopes),
8490
- subject: Schema$1.String,
8521
+ //#region src/persistence/AuthSessions.ts
8522
+ const AuthSessionClientMetadataRecord = Schema$1.Struct({
8491
8523
  label: Schema$1.NullOr(Schema$1.String),
8492
- proofKeyThumbprint: Schema$1.NullOr(Schema$1.String),
8493
- createdAt: Schema$1.DateTimeUtcFromString,
8524
+ ipAddress: Schema$1.NullOr(Schema$1.String),
8525
+ userAgent: Schema$1.NullOr(Schema$1.String),
8526
+ deviceType: AuthClientMetadataDeviceType,
8527
+ os: Schema$1.NullOr(Schema$1.String),
8528
+ browser: Schema$1.NullOr(Schema$1.String)
8529
+ });
8530
+ Schema$1.Struct({
8531
+ sessionId: AuthSessionId,
8532
+ subject: Schema$1.String,
8533
+ scopes: AuthEnvironmentScopes,
8534
+ method: ServerAuthSessionMethod,
8535
+ client: AuthSessionClientMetadataRecord,
8536
+ issuedAt: Schema$1.DateTimeUtcFromString,
8494
8537
  expiresAt: Schema$1.DateTimeUtcFromString,
8495
- consumedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString),
8538
+ lastConnectedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString),
8496
8539
  revokedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString)
8497
8540
  });
8498
- const CreateAuthPairingLinkInput = Schema$1.Struct({
8499
- id: Schema$1.String,
8500
- credential: Schema$1.String,
8501
- method: Schema$1.Literals(["desktop-bootstrap", "one-time-token"]),
8502
- scopes: AuthEnvironmentScopes,
8541
+ const CreateAuthSessionInput = Schema$1.Struct({
8542
+ sessionId: AuthSessionId,
8503
8543
  subject: Schema$1.String,
8504
- label: Schema$1.NullOr(Schema$1.String),
8505
- proofKeyThumbprint: Schema$1.NullOr(Schema$1.String),
8506
- createdAt: Schema$1.DateTimeUtcFromString,
8544
+ scopes: AuthEnvironmentScopes,
8545
+ method: ServerAuthSessionMethod,
8546
+ client: AuthSessionClientMetadataRecord,
8547
+ issuedAt: Schema$1.DateTimeUtcFromString,
8507
8548
  expiresAt: Schema$1.DateTimeUtcFromString
8508
8549
  });
8509
- const ConsumeAuthPairingLinkInput = Schema$1.Struct({
8510
- credential: Schema$1.String,
8511
- proofKeyThumbprint: Schema$1.NullOr(Schema$1.String),
8512
- consumedAt: Schema$1.DateTimeUtcFromString,
8513
- now: Schema$1.DateTimeUtcFromString
8550
+ const GetAuthSessionByIdInput = Schema$1.Struct({ sessionId: AuthSessionId });
8551
+ const ListActiveAuthSessionsInput = Schema$1.Struct({ now: Schema$1.DateTimeUtcFromString });
8552
+ const RevokeAuthSessionInput = Schema$1.Struct({
8553
+ sessionId: AuthSessionId,
8554
+ revokedAt: Schema$1.DateTimeUtcFromString
8514
8555
  });
8515
- const ListActiveAuthPairingLinksInput = Schema$1.Struct({ now: Schema$1.DateTimeUtcFromString });
8516
- const RevokeAuthPairingLinkInput = Schema$1.Struct({
8517
- id: Schema$1.String,
8556
+ const RevokeOtherAuthSessionsInput = Schema$1.Struct({
8557
+ currentSessionId: AuthSessionId,
8518
8558
  revokedAt: Schema$1.DateTimeUtcFromString
8519
8559
  });
8520
- const GetAuthPairingLinkByCredentialInput = Schema$1.Struct({ credential: Schema$1.String });
8521
- const AuthPairingLinkRawDbRow = Schema$1.Struct({
8522
- id: Schema$1.String,
8523
- credential: Schema$1.Unknown,
8524
- method: Schema$1.Unknown,
8525
- scopes: Schema$1.Unknown,
8560
+ const SetAuthSessionLastConnectedAtInput = Schema$1.Struct({
8561
+ sessionId: AuthSessionId,
8562
+ lastConnectedAt: Schema$1.DateTimeUtcFromString
8563
+ });
8564
+ var AuthSessionRepository = class extends Context.Service()("@p4code/cli/persistence/AuthSessions/AuthSessionRepository") {};
8565
+ const AuthSessionDbRow = Schema$1.Struct({
8566
+ sessionId: AuthSessionId,
8567
+ subject: Schema$1.String,
8568
+ scopes: Schema$1.fromJsonString(AuthEnvironmentScopes),
8569
+ method: ServerAuthSessionMethod,
8570
+ clientLabel: Schema$1.NullOr(Schema$1.String),
8571
+ clientIpAddress: Schema$1.NullOr(Schema$1.String),
8572
+ clientUserAgent: Schema$1.NullOr(Schema$1.String),
8573
+ clientDeviceType: Schema$1.Literals([
8574
+ "desktop",
8575
+ "mobile",
8576
+ "tablet",
8577
+ "bot",
8578
+ "unknown"
8579
+ ]),
8580
+ clientOs: Schema$1.NullOr(Schema$1.String),
8581
+ clientBrowser: Schema$1.NullOr(Schema$1.String),
8582
+ issuedAt: Schema$1.DateTimeUtcFromString,
8583
+ expiresAt: Schema$1.DateTimeUtcFromString,
8584
+ lastConnectedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString),
8585
+ revokedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString)
8586
+ });
8587
+ const AuthSessionRawDbRow = Schema$1.Struct({
8588
+ sessionId: Schema$1.String,
8526
8589
  subject: Schema$1.Unknown,
8527
- label: Schema$1.Unknown,
8528
- proofKeyThumbprint: Schema$1.Unknown,
8529
- createdAt: Schema$1.Unknown,
8590
+ scopes: Schema$1.Unknown,
8591
+ method: Schema$1.Unknown,
8592
+ clientLabel: Schema$1.Unknown,
8593
+ clientIpAddress: Schema$1.Unknown,
8594
+ clientUserAgent: Schema$1.Unknown,
8595
+ clientDeviceType: Schema$1.Unknown,
8596
+ clientOs: Schema$1.Unknown,
8597
+ clientBrowser: Schema$1.Unknown,
8598
+ issuedAt: Schema$1.Unknown,
8530
8599
  expiresAt: Schema$1.Unknown,
8531
- consumedAt: Schema$1.Unknown,
8600
+ lastConnectedAt: Schema$1.Unknown,
8532
8601
  revokedAt: Schema$1.Unknown
8533
8602
  });
8534
- const decodeAuthPairingLinkDbRow = Schema$1.decodeUnknownEffect(AuthPairingLinkRecord);
8535
- var AuthPairingLinkRepository = class extends Context.Service()("@p4code/cli/persistence/AuthPairingLinks/AuthPairingLinkRepository") {};
8603
+ const decodeAuthSessionDbRow = Schema$1.decodeUnknownEffect(AuthSessionDbRow);
8604
+ function toAuthSessionRecord(row) {
8605
+ return {
8606
+ sessionId: row.sessionId,
8607
+ subject: row.subject,
8608
+ scopes: row.scopes,
8609
+ method: row.method,
8610
+ client: {
8611
+ label: row.clientLabel,
8612
+ ipAddress: row.clientIpAddress,
8613
+ userAgent: row.clientUserAgent,
8614
+ deviceType: row.clientDeviceType,
8615
+ os: row.clientOs,
8616
+ browser: row.clientBrowser
8617
+ },
8618
+ issuedAt: row.issuedAt,
8619
+ expiresAt: row.expiresAt,
8620
+ lastConnectedAt: row.lastConnectedAt,
8621
+ revokedAt: row.revokedAt
8622
+ };
8623
+ }
8536
8624
  function toPersistenceSqlOrDecodeError$6(sqlOperation, decodeOperation, correlation) {
8537
8625
  return (cause) => Schema$1.isSchemaError(cause) ? PersistenceDecodeError.fromSchemaError(decodeOperation, cause, correlation) : new PersistenceSqlError({
8538
8626
  operation: sqlOperation,
@@ -8542,674 +8630,881 @@ function toPersistenceSqlOrDecodeError$6(sqlOperation, decodeOperation, correlat
8542
8630
  }
8543
8631
  const make$67 = Effect.gen(function* () {
8544
8632
  const sql = yield* SqlClient.SqlClient;
8545
- const createPairingLinkRow = SqlSchema.void({
8546
- Request: CreateAuthPairingLinkInput,
8633
+ const createSessionRow = SqlSchema.void({
8634
+ Request: CreateAuthSessionInput,
8547
8635
  execute: (input) => sql`
8548
- INSERT INTO auth_pairing_links (
8549
- id,
8550
- credential,
8551
- method,
8552
- scopes,
8636
+ INSERT INTO auth_sessions (
8637
+ session_id,
8553
8638
  subject,
8554
- label,
8555
- proof_key_thumbprint,
8556
- created_at,
8639
+ scopes,
8640
+ method,
8641
+ client_label,
8642
+ client_ip_address,
8643
+ client_user_agent,
8644
+ client_device_type,
8645
+ client_os,
8646
+ client_browser,
8647
+ issued_at,
8557
8648
  expires_at,
8558
- consumed_at,
8559
8649
  revoked_at
8560
8650
  )
8561
8651
  VALUES (
8562
- ${input.id},
8563
- ${input.credential},
8564
- ${input.method},
8565
- ${JSON.stringify(input.scopes)},
8652
+ ${input.sessionId},
8566
8653
  ${input.subject},
8567
- ${input.label},
8568
- ${input.proofKeyThumbprint},
8569
- ${input.createdAt},
8654
+ ${JSON.stringify(input.scopes)},
8655
+ ${input.method},
8656
+ ${input.client.label},
8657
+ ${input.client.ipAddress},
8658
+ ${input.client.userAgent},
8659
+ ${input.client.deviceType},
8660
+ ${input.client.os},
8661
+ ${input.client.browser},
8662
+ ${input.issuedAt},
8570
8663
  ${input.expiresAt},
8571
- NULL,
8572
8664
  NULL
8573
8665
  )
8574
8666
  `
8575
8667
  });
8576
- const consumeAvailablePairingLinkRow = SqlSchema.findOneOption({
8577
- Request: ConsumeAuthPairingLinkInput,
8578
- Result: AuthPairingLinkRawDbRow,
8579
- execute: ({ credential, proofKeyThumbprint, consumedAt, now }) => sql`
8580
- UPDATE auth_pairing_links
8581
- SET consumed_at = ${consumedAt}
8582
- WHERE credential = ${credential}
8583
- AND revoked_at IS NULL
8584
- AND consumed_at IS NULL
8585
- AND expires_at > ${now}
8586
- AND (
8587
- proof_key_thumbprint IS NULL
8588
- OR proof_key_thumbprint = ${proofKeyThumbprint}
8589
- )
8590
- RETURNING
8591
- id AS "id",
8592
- credential AS "credential",
8593
- method AS "method",
8594
- scopes AS "scopes",
8668
+ const getSessionRowById = SqlSchema.findOneOption({
8669
+ Request: GetAuthSessionByIdInput,
8670
+ Result: AuthSessionRawDbRow,
8671
+ execute: ({ sessionId }) => sql`
8672
+ SELECT
8673
+ session_id AS "sessionId",
8595
8674
  subject AS "subject",
8596
- label AS "label",
8597
- proof_key_thumbprint AS "proofKeyThumbprint",
8598
- created_at AS "createdAt",
8675
+ scopes AS "scopes",
8676
+ method AS "method",
8677
+ client_label AS "clientLabel",
8678
+ client_ip_address AS "clientIpAddress",
8679
+ client_user_agent AS "clientUserAgent",
8680
+ client_device_type AS "clientDeviceType",
8681
+ client_os AS "clientOs",
8682
+ client_browser AS "clientBrowser",
8683
+ issued_at AS "issuedAt",
8599
8684
  expires_at AS "expiresAt",
8600
- consumed_at AS "consumedAt",
8685
+ last_connected_at AS "lastConnectedAt",
8601
8686
  revoked_at AS "revokedAt"
8687
+ FROM auth_sessions
8688
+ WHERE session_id = ${sessionId}
8602
8689
  `
8603
8690
  });
8604
- const listActivePairingLinkRows = SqlSchema.findAll({
8605
- Request: ListActiveAuthPairingLinksInput,
8606
- Result: AuthPairingLinkRawDbRow,
8691
+ const listActiveSessionRows = SqlSchema.findAll({
8692
+ Request: ListActiveAuthSessionsInput,
8693
+ Result: AuthSessionRawDbRow,
8607
8694
  execute: ({ now }) => sql`
8608
8695
  SELECT
8609
- id AS "id",
8610
- credential AS "credential",
8611
- method AS "method",
8612
- scopes AS "scopes",
8696
+ session_id AS "sessionId",
8613
8697
  subject AS "subject",
8614
- label AS "label",
8615
- proof_key_thumbprint AS "proofKeyThumbprint",
8616
- created_at AS "createdAt",
8698
+ scopes AS "scopes",
8699
+ method AS "method",
8700
+ client_label AS "clientLabel",
8701
+ client_ip_address AS "clientIpAddress",
8702
+ client_user_agent AS "clientUserAgent",
8703
+ client_device_type AS "clientDeviceType",
8704
+ client_os AS "clientOs",
8705
+ client_browser AS "clientBrowser",
8706
+ issued_at AS "issuedAt",
8617
8707
  expires_at AS "expiresAt",
8618
- consumed_at AS "consumedAt",
8708
+ last_connected_at AS "lastConnectedAt",
8619
8709
  revoked_at AS "revokedAt"
8620
- FROM auth_pairing_links
8710
+ FROM auth_sessions
8621
8711
  WHERE revoked_at IS NULL
8622
- AND consumed_at IS NULL
8623
8712
  AND expires_at > ${now}
8624
- ORDER BY created_at DESC, id DESC
8713
+ ORDER BY issued_at DESC, session_id DESC
8625
8714
  `
8626
8715
  });
8627
- const revokePairingLinkRow = SqlSchema.findAll({
8628
- Request: RevokeAuthPairingLinkInput,
8629
- Result: Schema$1.Struct({ id: Schema$1.String }),
8630
- execute: ({ id, revokedAt }) => sql`
8631
- UPDATE auth_pairing_links
8716
+ const setLastConnectedAtRow = SqlSchema.void({
8717
+ Request: SetAuthSessionLastConnectedAtInput,
8718
+ execute: ({ sessionId, lastConnectedAt }) => sql`
8719
+ UPDATE auth_sessions
8720
+ SET last_connected_at = ${lastConnectedAt}
8721
+ WHERE session_id = ${sessionId}
8722
+ AND revoked_at IS NULL
8723
+ `
8724
+ });
8725
+ const revokeSessionRows = SqlSchema.findAll({
8726
+ Request: RevokeAuthSessionInput,
8727
+ Result: Schema$1.Struct({ sessionId: AuthSessionId }),
8728
+ execute: ({ sessionId, revokedAt }) => sql`
8729
+ UPDATE auth_sessions
8632
8730
  SET revoked_at = ${revokedAt}
8633
- WHERE id = ${id}
8731
+ WHERE session_id = ${sessionId}
8634
8732
  AND revoked_at IS NULL
8635
- AND consumed_at IS NULL
8636
- RETURNING id AS "id"
8733
+ RETURNING session_id AS "sessionId"
8637
8734
  `
8638
8735
  });
8639
- const getPairingLinkRowByCredential = SqlSchema.findOneOption({
8640
- Request: GetAuthPairingLinkByCredentialInput,
8641
- Result: AuthPairingLinkRawDbRow,
8642
- execute: ({ credential }) => sql`
8643
- SELECT
8644
- id AS "id",
8645
- credential AS "credential",
8646
- method AS "method",
8647
- scopes AS "scopes",
8648
- subject AS "subject",
8649
- label AS "label",
8650
- proof_key_thumbprint AS "proofKeyThumbprint",
8651
- created_at AS "createdAt",
8652
- expires_at AS "expiresAt",
8653
- consumed_at AS "consumedAt",
8654
- revoked_at AS "revokedAt"
8655
- FROM auth_pairing_links
8656
- WHERE credential = ${credential}
8736
+ const revokeOtherSessionRows = SqlSchema.findAll({
8737
+ Request: RevokeOtherAuthSessionsInput,
8738
+ Result: Schema$1.Struct({ sessionId: AuthSessionId }),
8739
+ execute: ({ currentSessionId, revokedAt }) => sql`
8740
+ UPDATE auth_sessions
8741
+ SET revoked_at = ${revokedAt}
8742
+ WHERE session_id <> ${currentSessionId}
8743
+ AND revoked_at IS NULL
8744
+ RETURNING session_id AS "sessionId"
8657
8745
  `
8658
8746
  });
8659
- const create = (input) => createPairingLinkRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthPairingLinkRepository.create:query", "AuthPairingLinkRepository.create:encodeRequest", { pairingLinkId: input.id })));
8660
- const consumeAvailable = (input) => consumeAvailablePairingLinkRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthPairingLinkRepository.consumeAvailable:query", "AuthPairingLinkRepository.consumeAvailable:decodeRow")), Effect.flatMap((rowOption) => Option.match(rowOption, {
8747
+ const create = (input) => createSessionRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthSessionRepository.create:query", "AuthSessionRepository.create:encodeRequest", { sessionId: input.sessionId })));
8748
+ const getById = (input) => getSessionRowById(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthSessionRepository.getById:query", "AuthSessionRepository.getById:decodeRow", { sessionId: input.sessionId })), Effect.flatMap((rowOption) => Option.match(rowOption, {
8661
8749
  onNone: () => Effect.succeed(Option.none()),
8662
- onSome: (row) => decodeAuthPairingLinkDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthPairingLinkRepository.consumeAvailable:decodeRow", cause, { pairingLinkId: row.id })), Effect.map(Option.some))
8663
- })));
8664
- const listActive = (input) => listActivePairingLinkRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthPairingLinkRepository.listActive:query", "AuthPairingLinkRepository.listActive:decodeRows")), Effect.flatMap((rows) => Effect.forEach(rows, (row) => decodeAuthPairingLinkDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthPairingLinkRepository.listActive:decodeRows", cause, { pairingLinkId: row.id }))))));
8665
- const revoke = (input) => revokePairingLinkRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthPairingLinkRepository.revoke:query", "AuthPairingLinkRepository.revoke:decodeRows", { pairingLinkId: input.id })), Effect.map((rows) => rows.length > 0));
8666
- const getByCredential = (input) => getPairingLinkRowByCredential(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthPairingLinkRepository.getByCredential:query", "AuthPairingLinkRepository.getByCredential:decodeRow")), Effect.flatMap((rowOption) => Option.match(rowOption, {
8667
- onNone: () => Effect.succeed(Option.none()),
8668
- onSome: (row) => decodeAuthPairingLinkDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthPairingLinkRepository.getByCredential:decodeRow", cause, { pairingLinkId: row.id })), Effect.map(Option.some))
8750
+ onSome: (row) => decodeAuthSessionDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthSessionRepository.getById:decodeRow", cause, { sessionId: input.sessionId })), Effect.map((decodedRow) => Option.some(toAuthSessionRecord(decodedRow))))
8669
8751
  })));
8752
+ const listActive = (input) => listActiveSessionRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthSessionRepository.listActive:query", "AuthSessionRepository.listActive:decodeRows")), Effect.flatMap((rows) => Effect.forEach(rows, (row) => decodeAuthSessionDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthSessionRepository.listActive:decodeRows", cause, { sessionId: row.sessionId })), Effect.map(toAuthSessionRecord)))));
8753
+ const revoke = (input) => revokeSessionRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthSessionRepository.revoke:query", "AuthSessionRepository.revoke:decodeRows", { sessionId: input.sessionId })), Effect.map((rows) => rows.length > 0));
8754
+ const revokeAllExcept = (input) => revokeOtherSessionRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthSessionRepository.revokeAllExcept:query", "AuthSessionRepository.revokeAllExcept:decodeRows", { currentSessionId: input.currentSessionId })), Effect.map((rows) => rows.map((row) => row.sessionId)));
8755
+ const setLastConnectedAt = (input) => setLastConnectedAtRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$6("AuthSessionRepository.setLastConnectedAt:query", "AuthSessionRepository.setLastConnectedAt:encodeRequest", { sessionId: input.sessionId })));
8670
8756
  return {
8671
8757
  create,
8672
- consumeAvailable,
8758
+ getById,
8673
8759
  listActive,
8674
8760
  revoke,
8675
- getByCredential
8761
+ revokeAllExcept,
8762
+ setLastConnectedAt
8676
8763
  };
8677
8764
  });
8678
- const layer$63 = Layer.effect(AuthPairingLinkRepository, make$67);
8765
+ const layer$63 = Layer.effect(AuthSessionRepository, make$67);
8679
8766
  //#endregion
8680
- //#region src/auth/PairingGrantStore.ts
8681
- var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
8767
+ //#region src/auth/ServerSecretStore.ts
8768
+ const secretStoreErrorContext = {
8769
+ resource: Schema$1.String,
8770
+ cause: Schema$1.Defect()
8771
+ };
8772
+ var SecretStoreSecureError = class extends Schema$1.TaggedErrorClass()("SecretStoreSecureError", { ...secretStoreErrorContext }) {
8682
8773
  get message() {
8683
- return "Unknown bootstrap credential.";
8774
+ return `Failed to secure ${this.resource}.`;
8684
8775
  }
8685
8776
  };
8686
- var ExpiredBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("ExpiredBootstrapCredentialError", {}) {
8777
+ var SecretStoreReadError = class extends Schema$1.TaggedErrorClass()("SecretStoreReadError", { ...secretStoreErrorContext }) {
8687
8778
  get message() {
8688
- return "Bootstrap credential expired.";
8779
+ return `Failed to read ${this.resource}.`;
8689
8780
  }
8690
8781
  };
8691
- var BootstrapCredentialProofKeyMismatchError = class extends Schema$1.TaggedErrorClass()("BootstrapCredentialProofKeyMismatchError", {}) {
8782
+ var SecretStoreTemporaryPathError = class extends Schema$1.TaggedErrorClass()("SecretStoreTemporaryPathError", { ...secretStoreErrorContext }) {
8692
8783
  get message() {
8693
- return "Bootstrap credential proof key mismatch.";
8784
+ return `Failed to create temporary path for ${this.resource}.`;
8694
8785
  }
8695
8786
  };
8696
- var UnavailableBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnavailableBootstrapCredentialError", {}) {
8787
+ var SecretStorePersistError = class extends Schema$1.TaggedErrorClass()("SecretStorePersistError", { ...secretStoreErrorContext }) {
8697
8788
  get message() {
8698
- return "Bootstrap credential is no longer available.";
8789
+ return `Failed to persist ${this.resource}.`;
8699
8790
  }
8700
8791
  };
8701
- const BootstrapCredentialInvalidError = Schema$1.Union([
8702
- UnknownBootstrapCredentialError,
8703
- ExpiredBootstrapCredentialError,
8704
- BootstrapCredentialProofKeyMismatchError,
8705
- UnavailableBootstrapCredentialError
8792
+ var SecretStoreRandomGenerationError = class extends Schema$1.TaggedErrorClass()("SecretStoreRandomGenerationError", { ...secretStoreErrorContext }) {
8793
+ get message() {
8794
+ return `Failed to generate random bytes for ${this.resource}.`;
8795
+ }
8796
+ };
8797
+ var SecretStoreConcurrentReadError = class extends Schema$1.TaggedErrorClass()("SecretStoreConcurrentReadError", { resource: Schema$1.String }) {
8798
+ get message() {
8799
+ return `Failed to read ${this.resource} after concurrent creation.`;
8800
+ }
8801
+ };
8802
+ var SecretStoreRemoveError = class extends Schema$1.TaggedErrorClass()("SecretStoreRemoveError", { ...secretStoreErrorContext }) {
8803
+ get message() {
8804
+ return `Failed to remove ${this.resource}.`;
8805
+ }
8806
+ };
8807
+ var SecretStoreDecodeError = class extends Schema$1.TaggedErrorClass()("SecretStoreDecodeError", { ...secretStoreErrorContext }) {
8808
+ get message() {
8809
+ return `Failed to decode ${this.resource}.`;
8810
+ }
8811
+ };
8812
+ var SecretStoreEncodeError = class extends Schema$1.TaggedErrorClass()("SecretStoreEncodeError", { ...secretStoreErrorContext }) {
8813
+ get message() {
8814
+ return `Failed to encode ${this.resource}.`;
8815
+ }
8816
+ };
8817
+ const SecretStoreError = Schema$1.Union([
8818
+ SecretStoreSecureError,
8819
+ SecretStoreReadError,
8820
+ SecretStoreTemporaryPathError,
8821
+ SecretStorePersistError,
8822
+ SecretStoreRandomGenerationError,
8823
+ SecretStoreConcurrentReadError,
8824
+ SecretStoreRemoveError,
8825
+ SecretStoreDecodeError,
8826
+ SecretStoreEncodeError
8706
8827
  ]);
8707
- Schema$1.is(BootstrapCredentialInvalidError);
8708
- var ActivePairingLinksLoadError = class extends Schema$1.TaggedErrorClass()("ActivePairingLinksLoadError", { cause: Schema$1.Defect() }) {
8828
+ const isSecretStoreError = Schema$1.is(SecretStoreError);
8829
+ const isPlatformError = (value) => Predicate.isTagged(value, "PlatformError");
8830
+ const isSecretAlreadyExistsError = (error) => "cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists";
8831
+ var ServerSecretStore = class extends Context.Service()("@p4code/cli/auth/ServerSecretStore") {};
8832
+ const make$66 = Effect.gen(function* () {
8833
+ const crypto = yield* Crypto.Crypto;
8834
+ const fileSystem = yield* FileSystem.FileSystem;
8835
+ const path = yield* Path.Path;
8836
+ const serverConfig = yield* ServerConfig$1;
8837
+ yield* fileSystem.makeDirectory(serverConfig.secretsDir, { recursive: true });
8838
+ yield* fileSystem.chmod(serverConfig.secretsDir, 448).pipe(Effect.mapError((cause) => new SecretStoreSecureError({
8839
+ resource: `secrets directory ${serverConfig.secretsDir}`,
8840
+ cause
8841
+ })));
8842
+ const resolveSecretPath = (name) => path.join(serverConfig.secretsDir, `${name}.bin`);
8843
+ const get = (name) => fileSystem.readFile(resolveSecretPath(name)).pipe(Effect.map((bytes) => Option.some(Uint8Array.from(bytes))), Effect.catch((cause) => cause.reason._tag === "NotFound" ? Effect.succeed(Option.none()) : Effect.fail(new SecretStoreReadError({
8844
+ resource: `secret ${name}`,
8845
+ cause
8846
+ }))), Effect.withSpan("ServerSecretStore.get"));
8847
+ const set = (name, value) => {
8848
+ const secretPath = resolveSecretPath(name);
8849
+ return crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new SecretStoreTemporaryPathError({
8850
+ resource: `secret ${name}`,
8851
+ cause
8852
+ })), Effect.flatMap((uuid) => {
8853
+ const tempPath = `${secretPath}.${uuid}.tmp`;
8854
+ return Effect.gen(function* () {
8855
+ yield* fileSystem.writeFile(tempPath, value);
8856
+ yield* fileSystem.chmod(tempPath, 384);
8857
+ yield* fileSystem.rename(tempPath, secretPath);
8858
+ yield* fileSystem.chmod(secretPath, 384);
8859
+ }).pipe(Effect.catch((cause) => fileSystem.remove(tempPath).pipe(Effect.ignore, Effect.flatMap(() => Effect.fail(new SecretStorePersistError({
8860
+ resource: `secret ${name}`,
8861
+ cause
8862
+ }))))));
8863
+ }), Effect.withSpan("ServerSecretStore.set"));
8864
+ };
8865
+ const create = (name, value) => {
8866
+ const secretPath = resolveSecretPath(name);
8867
+ return Effect.scoped(Effect.gen(function* () {
8868
+ const file = yield* fileSystem.open(secretPath, {
8869
+ flag: "wx",
8870
+ mode: 384
8871
+ });
8872
+ yield* file.writeAll(value);
8873
+ yield* file.sync;
8874
+ yield* fileSystem.chmod(secretPath, 384);
8875
+ })).pipe(Effect.mapError((cause) => new SecretStorePersistError({
8876
+ resource: `secret ${name}`,
8877
+ cause
8878
+ })));
8879
+ };
8880
+ const getOrCreateRandom = (name, bytes) => get(name).pipe(Effect.flatMap(Option.match({
8881
+ onSome: Effect.succeed,
8882
+ onNone: () => crypto.randomBytes(bytes).pipe(Effect.mapError((cause) => new SecretStoreRandomGenerationError({
8883
+ resource: `secret ${name}`,
8884
+ cause
8885
+ })), Effect.flatMap((generated) => create(name, generated).pipe(Effect.as(Uint8Array.from(generated)), Effect.catchIf(isSecretStoreError, (error) => isSecretAlreadyExistsError(error) ? get(name).pipe(Effect.flatMap(Option.match({
8886
+ onSome: Effect.succeed,
8887
+ onNone: () => Effect.fail(new SecretStoreConcurrentReadError({ resource: `secret ${name}` }))
8888
+ }))) : Effect.fail(error)))))
8889
+ })), Effect.withSpan("ServerSecretStore.getOrCreateRandom"));
8890
+ const remove = (name) => fileSystem.remove(resolveSecretPath(name)).pipe(Effect.catch((cause) => cause.reason._tag === "NotFound" ? Effect.void : Effect.fail(new SecretStoreRemoveError({
8891
+ resource: `secret ${name}`,
8892
+ cause
8893
+ }))), Effect.withSpan("ServerSecretStore.remove"));
8894
+ return ServerSecretStore.of({
8895
+ get,
8896
+ set,
8897
+ create,
8898
+ getOrCreateRandom,
8899
+ remove
8900
+ });
8901
+ });
8902
+ const layer$62 = Layer.effect(ServerSecretStore, make$66);
8903
+ //#endregion
8904
+ //#region src/auth/SessionStore.ts
8905
+ var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
8709
8906
  get message() {
8710
- return "Failed to load active pairing links.";
8907
+ return "Malformed session token.";
8711
8908
  }
8712
8909
  };
8713
- var PairingLinkRevokeError = class extends Schema$1.TaggedErrorClass()("PairingLinkRevokeError", {
8714
- pairingLinkId: Schema$1.String,
8715
- cause: Schema$1.Defect()
8716
- }) {
8910
+ var InvalidSessionTokenSignatureError = class extends Schema$1.TaggedErrorClass()("InvalidSessionTokenSignatureError", {}) {
8717
8911
  get message() {
8718
- return `Failed to revoke pairing link '${this.pairingLinkId}'.`;
8912
+ return "Invalid session token signature.";
8719
8913
  }
8720
8914
  };
8721
- var PairingCredentialIssueError = class extends Schema$1.TaggedErrorClass()("PairingCredentialIssueError", {
8722
- pairingLinkId: Schema$1.String,
8723
- subject: Schema$1.String,
8724
- label: Schema$1.optional(Schema$1.String),
8725
- cause: Schema$1.Defect()
8915
+ var InvalidSessionTokenPayloadError = class extends Schema$1.TaggedErrorClass()("InvalidSessionTokenPayloadError", { cause: Schema$1.Defect() }) {
8916
+ get message() {
8917
+ return "Invalid session token payload.";
8918
+ }
8919
+ };
8920
+ var SessionTokenExpiredError = class extends Schema$1.TaggedErrorClass()("SessionTokenExpiredError", {
8921
+ sessionId: AuthSessionId,
8922
+ expiresAt: Schema$1.DateTimeUtc,
8923
+ observedAt: Schema$1.DateTimeUtc
8726
8924
  }) {
8727
8925
  get message() {
8728
- return `Failed to issue pairing credential '${this.pairingLinkId}' for '${this.subject}'.`;
8926
+ return "Session token expired.";
8729
8927
  }
8730
8928
  };
8731
- var PairingCredentialRandomGenerationError = class extends Schema$1.TaggedErrorClass()("PairingCredentialRandomGenerationError", {
8732
- operation: Schema$1.Literals(["generate-id", "generate-token"]),
8733
- cause: Schema$1.Defect()
8929
+ var UnknownSessionTokenError = class extends Schema$1.TaggedErrorClass()("UnknownSessionTokenError", { sessionId: AuthSessionId }) {
8930
+ get message() {
8931
+ return "Unknown session token.";
8932
+ }
8933
+ };
8934
+ var SessionTokenRevokedError = class extends Schema$1.TaggedErrorClass()("SessionTokenRevokedError", {
8935
+ sessionId: AuthSessionId,
8936
+ revokedAt: Schema$1.DateTimeUtc
8734
8937
  }) {
8735
8938
  get message() {
8736
- return `Failed to generate pairing credential data during '${this.operation}'.`;
8939
+ return "Session token revoked.";
8737
8940
  }
8738
8941
  };
8739
- var BootstrapCredentialConsumeError = class extends Schema$1.TaggedErrorClass()("BootstrapCredentialConsumeError", { cause: Schema$1.Defect() }) {
8942
+ var InvalidSessionExpirationClaimError = class extends Schema$1.TaggedErrorClass()("InvalidSessionExpirationClaimError", {
8943
+ sessionId: AuthSessionId,
8944
+ expirationClaim: Schema$1.Number
8945
+ }) {
8740
8946
  get message() {
8741
- return "Failed to consume bootstrap credential.";
8947
+ return "Invalid `exp` claim";
8742
8948
  }
8743
8949
  };
8744
- var BootstrapCredentialConsumeAvailableError = class extends Schema$1.TaggedErrorClass()("BootstrapCredentialConsumeAvailableError", { cause: Schema$1.Defect() }) {
8950
+ var MalformedWebSocketTokenError = class extends Schema$1.TaggedErrorClass()("MalformedWebSocketTokenError", {}) {
8745
8951
  get message() {
8746
- return "Failed to atomically consume an available bootstrap credential.";
8952
+ return "Malformed websocket token.";
8747
8953
  }
8748
8954
  };
8749
- var BootstrapCredentialLookupError = class extends Schema$1.TaggedErrorClass()("BootstrapCredentialLookupError", { cause: Schema$1.Defect() }) {
8955
+ var InvalidWebSocketTokenSignatureError = class extends Schema$1.TaggedErrorClass()("InvalidWebSocketTokenSignatureError", {}) {
8750
8956
  get message() {
8751
- return "Failed to look up bootstrap credential state.";
8957
+ return "Invalid websocket token signature.";
8752
8958
  }
8753
8959
  };
8754
- const BootstrapCredentialInternalError = Schema$1.Union([
8755
- ActivePairingLinksLoadError,
8756
- PairingLinkRevokeError,
8757
- PairingCredentialIssueError,
8758
- PairingCredentialRandomGenerationError,
8759
- BootstrapCredentialConsumeError,
8760
- BootstrapCredentialConsumeAvailableError,
8761
- BootstrapCredentialLookupError
8762
- ]);
8763
- const isBootstrapCredentialInternalError = Schema$1.is(BootstrapCredentialInternalError);
8764
- const BootstrapCredentialError = Schema$1.Union([BootstrapCredentialInvalidError, BootstrapCredentialInternalError]);
8765
- Schema$1.is(BootstrapCredentialError);
8766
- var PairingGrantStore = class extends Context.Service()("@p4code/cli/auth/PairingGrantStore") {};
8767
- const DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES = Duration.minutes(5);
8768
- const DESKTOP_BOOTSTRAP_TTL_HOURS = Duration.hours(24);
8769
- const DEV_STARTUP_TTL_HOURS = Duration.hours(24);
8770
- const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
8771
- const PAIRING_TOKEN_LENGTH = 12;
8772
- const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
8773
- const make$66 = Effect.gen(function* () {
8774
- const crypto = yield* Crypto.Crypto;
8775
- const config = yield* ServerConfig$1;
8776
- const pairingLinks = yield* AuthPairingLinkRepository;
8777
- const seededGrantsRef = yield* Ref.make(/* @__PURE__ */ new Map());
8778
- const changesPubSub = yield* PubSub.unbounded();
8779
- const generatePairingToken = Effect.gen(function* () {
8780
- let credential = "";
8781
- while (credential.length < PAIRING_TOKEN_LENGTH) {
8782
- const bytes = yield* crypto.randomBytes(PAIRING_TOKEN_LENGTH).pipe(Effect.mapError((cause) => new PairingCredentialRandomGenerationError({
8783
- operation: "generate-token",
8784
- cause
8785
- })));
8786
- for (const byte of bytes) {
8787
- if (byte >= PAIRING_TOKEN_REJECTION_LIMIT) continue;
8788
- credential += PAIRING_TOKEN_ALPHABET[byte % 32];
8789
- if (credential.length === PAIRING_TOKEN_LENGTH) return credential;
8790
- }
8791
- }
8792
- return credential;
8793
- });
8794
- const seedGrant = (credential, grant) => Ref.update(seededGrantsRef, (current) => {
8795
- const next = new Map(current);
8796
- next.set(credential, grant);
8797
- return next;
8798
- });
8799
- const emitUpsert = (pairingLink) => PubSub.publish(changesPubSub, {
8800
- type: "pairingLinkUpserted",
8801
- pairingLink
8802
- }).pipe(Effect.asVoid);
8803
- const emitRemoved = (id) => PubSub.publish(changesPubSub, {
8804
- type: "pairingLinkRemoved",
8805
- id
8806
- }).pipe(Effect.asVoid);
8807
- if (config.desktopBootstrapToken) {
8808
- const now = yield* DateTime.now;
8809
- yield* seedGrant(config.desktopBootstrapToken, {
8810
- method: "desktop-bootstrap",
8811
- scopes: AuthAdministrativeScopes,
8812
- subject: "desktop-bootstrap",
8813
- expiresAt: DateTime.add(now, { milliseconds: Duration.toMillis(DESKTOP_BOOTSTRAP_TTL_HOURS) }),
8814
- remainingUses: "unbounded"
8815
- });
8960
+ var InvalidWebSocketTokenPayloadError = class extends Schema$1.TaggedErrorClass()("InvalidWebSocketTokenPayloadError", { cause: Schema$1.Defect() }) {
8961
+ get message() {
8962
+ return "Invalid websocket token payload.";
8816
8963
  }
8817
- const listActive = Effect.fn("PairingGrantStore.listActive")(function* () {
8818
- const now = yield* DateTime.now;
8819
- return (yield* pairingLinks.listActive({ now })).map((row) => row.label ? {
8820
- id: row.id,
8821
- credential: row.credential,
8822
- scopes: row.scopes,
8823
- subject: row.subject,
8824
- label: row.label,
8825
- createdAt: row.createdAt,
8826
- expiresAt: row.expiresAt
8827
- } : {
8828
- id: row.id,
8829
- credential: row.credential,
8830
- scopes: row.scopes,
8831
- subject: row.subject,
8832
- createdAt: row.createdAt,
8833
- expiresAt: row.expiresAt
8834
- });
8835
- }, Effect.mapError((cause) => new ActivePairingLinksLoadError({ cause })));
8836
- const revoke = Effect.fn("PairingGrantStore.revoke")(function* (id) {
8837
- const revokedAt = yield* DateTime.now;
8838
- const revoked = yield* pairingLinks.revoke({
8839
- id,
8840
- revokedAt
8841
- }).pipe(Effect.mapError((cause) => new PairingLinkRevokeError({
8842
- pairingLinkId: id,
8843
- cause
8844
- })));
8845
- if (revoked) yield* emitRemoved(id);
8846
- return revoked;
8847
- });
8848
- const issueOneTimeToken = Effect.fn("PairingGrantStore.issueOneTimeToken")(function* (input) {
8849
- const id = yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new PairingCredentialRandomGenerationError({
8850
- operation: "generate-id",
8851
- cause
8852
- })));
8853
- const credential = yield* generatePairingToken;
8854
- const isDevStartupToken = config.devUrl !== void 0 && input?.purpose === "startup";
8855
- const ttl = input?.ttl ?? (isDevStartupToken ? DEV_STARTUP_TTL_HOURS : DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES);
8856
- const now = yield* DateTime.now;
8857
- const expiresAt = DateTime.add(now, { milliseconds: Duration.toMillis(ttl) });
8858
- const issued = {
8859
- id,
8860
- credential,
8861
- ...input?.label ? { label: input.label } : {},
8862
- ...input?.proofKeyThumbprint ? { proofKeyThumbprint: input.proofKeyThumbprint } : {},
8863
- expiresAt
8864
- };
8865
- const subject = input?.subject ?? "one-time-token";
8866
- yield* pairingLinks.create({
8867
- id,
8868
- credential,
8869
- method: "one-time-token",
8870
- scopes: input?.scopes ?? AuthStandardClientScopes,
8871
- subject,
8872
- label: input?.label ?? null,
8873
- proofKeyThumbprint: input?.proofKeyThumbprint ?? null,
8874
- createdAt: now,
8875
- expiresAt
8876
- }).pipe(Effect.mapError((cause) => new PairingCredentialIssueError({
8877
- pairingLinkId: id,
8878
- subject,
8879
- ...input?.label ? { label: input.label } : {},
8880
- cause
8881
- })));
8882
- yield* emitUpsert({
8883
- id,
8884
- credential,
8885
- scopes: input?.scopes ?? AuthStandardClientScopes,
8886
- subject: input?.subject ?? "one-time-token",
8887
- ...input?.label ? { label: input.label } : {},
8888
- createdAt: now,
8889
- expiresAt
8890
- });
8891
- return issued;
8892
- });
8893
- const consume = Effect.fn("PairingGrantStore.consume")(function* (credential, input) {
8894
- const now = yield* DateTime.now;
8895
- const seededResult = yield* Ref.modify(seededGrantsRef, (current) => {
8896
- const grant = current.get(credential);
8897
- if (!grant) return [{
8898
- _tag: "error",
8899
- reason: "not-found",
8900
- error: new UnknownBootstrapCredentialError({})
8901
- }, current];
8902
- const next = new Map(current);
8903
- if (DateTime.isGreaterThanOrEqualTo(now, grant.expiresAt)) {
8904
- next.delete(credential);
8905
- return [{
8906
- _tag: "error",
8907
- reason: "expired",
8908
- error: new ExpiredBootstrapCredentialError({})
8909
- }, next];
8910
- }
8911
- if (grant.proofKeyThumbprint && grant.proofKeyThumbprint !== input?.proofKeyThumbprint) return [{
8912
- _tag: "error",
8913
- reason: "not-found",
8914
- error: new BootstrapCredentialProofKeyMismatchError({})
8915
- }, next];
8916
- const remainingUses = grant.remainingUses;
8917
- if (typeof remainingUses === "number") if (remainingUses <= 1) next.delete(credential);
8918
- else next.set(credential, {
8919
- ...grant,
8920
- remainingUses: remainingUses - 1
8921
- });
8922
- return [{
8923
- _tag: "success",
8924
- grant: {
8925
- method: grant.method,
8926
- scopes: grant.scopes,
8927
- subject: grant.subject,
8928
- ...grant.label ? { label: grant.label } : {},
8929
- ...grant.proofKeyThumbprint ? { proofKeyThumbprint: grant.proofKeyThumbprint } : {},
8930
- expiresAt: grant.expiresAt
8931
- }
8932
- }, next];
8933
- });
8934
- if (seededResult._tag === "success") return seededResult.grant;
8935
- if (seededResult.reason !== "not-found") return yield* seededResult.error;
8936
- const consumed = yield* pairingLinks.consumeAvailable({
8937
- credential,
8938
- proofKeyThumbprint: input?.proofKeyThumbprint ?? null,
8939
- consumedAt: now,
8940
- now
8941
- }).pipe(Effect.mapError((cause) => new BootstrapCredentialConsumeAvailableError({ cause })));
8942
- if (Option.isSome(consumed)) {
8943
- yield* emitRemoved(consumed.value.id);
8944
- return {
8945
- method: consumed.value.method,
8946
- scopes: consumed.value.scopes,
8947
- subject: consumed.value.subject,
8948
- ...consumed.value.label ? { label: consumed.value.label } : {},
8949
- ...consumed.value.proofKeyThumbprint ? { proofKeyThumbprint: consumed.value.proofKeyThumbprint } : {},
8950
- expiresAt: consumed.value.expiresAt
8951
- };
8952
- }
8953
- const matching = yield* pairingLinks.getByCredential({ credential }).pipe(Effect.mapError((cause) => new BootstrapCredentialLookupError({ cause })));
8954
- if (Option.isNone(matching)) return yield* new UnknownBootstrapCredentialError({});
8955
- if (matching.value.revokedAt !== null) return yield* new UnavailableBootstrapCredentialError({});
8956
- if (matching.value.consumedAt !== null) return yield* new UnknownBootstrapCredentialError({});
8957
- if (DateTime.isGreaterThanOrEqualTo(now, matching.value.expiresAt)) return yield* new ExpiredBootstrapCredentialError({});
8958
- if (matching.value.proofKeyThumbprint !== null && matching.value.proofKeyThumbprint !== input?.proofKeyThumbprint) return yield* new BootstrapCredentialProofKeyMismatchError({});
8959
- return yield* new UnavailableBootstrapCredentialError({});
8960
- });
8961
- return PairingGrantStore.of({
8962
- issueOneTimeToken,
8963
- listActive,
8964
- get streamChanges() {
8965
- return Stream.fromPubSub(changesPubSub);
8966
- },
8967
- revoke,
8968
- consume
8969
- });
8970
- });
8971
- const layer$62 = Layer.effect(PairingGrantStore, make$66).pipe(Layer.provideMerge(layer$63));
8972
- //#endregion
8973
- //#region src/auth/ServerSecretStore.ts
8974
- const secretStoreErrorContext = {
8975
- resource: Schema$1.String,
8976
- cause: Schema$1.Defect()
8977
8964
  };
8978
- var SecretStoreSecureError = class extends Schema$1.TaggedErrorClass()("SecretStoreSecureError", { ...secretStoreErrorContext }) {
8965
+ var WebSocketTokenExpiredError = class extends Schema$1.TaggedErrorClass()("WebSocketTokenExpiredError", {
8966
+ sessionId: AuthSessionId,
8967
+ expiresAt: Schema$1.DateTimeUtc,
8968
+ observedAt: Schema$1.DateTimeUtc
8969
+ }) {
8979
8970
  get message() {
8980
- return `Failed to secure ${this.resource}.`;
8971
+ return "Websocket token expired.";
8981
8972
  }
8982
8973
  };
8983
- var SecretStoreReadError = class extends Schema$1.TaggedErrorClass()("SecretStoreReadError", { ...secretStoreErrorContext }) {
8974
+ var UnknownWebSocketSessionError = class extends Schema$1.TaggedErrorClass()("UnknownWebSocketSessionError", { sessionId: AuthSessionId }) {
8984
8975
  get message() {
8985
- return `Failed to read ${this.resource}.`;
8976
+ return "Unknown websocket session.";
8986
8977
  }
8987
8978
  };
8988
- var SecretStoreTemporaryPathError = class extends Schema$1.TaggedErrorClass()("SecretStoreTemporaryPathError", { ...secretStoreErrorContext }) {
8979
+ var WebSocketSessionExpiredError = class extends Schema$1.TaggedErrorClass()("WebSocketSessionExpiredError", {
8980
+ sessionId: AuthSessionId,
8981
+ expiresAt: Schema$1.DateTimeUtc,
8982
+ observedAt: Schema$1.DateTimeUtc
8983
+ }) {
8989
8984
  get message() {
8990
- return `Failed to create temporary path for ${this.resource}.`;
8985
+ return "Websocket session expired.";
8991
8986
  }
8992
8987
  };
8993
- var SecretStorePersistError = class extends Schema$1.TaggedErrorClass()("SecretStorePersistError", { ...secretStoreErrorContext }) {
8988
+ var WebSocketSessionRevokedError = class extends Schema$1.TaggedErrorClass()("WebSocketSessionRevokedError", {
8989
+ sessionId: AuthSessionId,
8990
+ revokedAt: Schema$1.DateTimeUtc
8991
+ }) {
8994
8992
  get message() {
8995
- return `Failed to persist ${this.resource}.`;
8993
+ return "Websocket session revoked.";
8996
8994
  }
8997
8995
  };
8998
- var SecretStoreRandomGenerationError = class extends Schema$1.TaggedErrorClass()("SecretStoreRandomGenerationError", { ...secretStoreErrorContext }) {
8996
+ const SessionCredentialInvalidError = Schema$1.Union([
8997
+ MalformedSessionTokenError,
8998
+ InvalidSessionTokenSignatureError,
8999
+ InvalidSessionTokenPayloadError,
9000
+ SessionTokenExpiredError,
9001
+ UnknownSessionTokenError,
9002
+ SessionTokenRevokedError,
9003
+ InvalidSessionExpirationClaimError,
9004
+ MalformedWebSocketTokenError,
9005
+ InvalidWebSocketTokenSignatureError,
9006
+ InvalidWebSocketTokenPayloadError,
9007
+ WebSocketTokenExpiredError,
9008
+ UnknownWebSocketSessionError,
9009
+ WebSocketSessionExpiredError,
9010
+ WebSocketSessionRevokedError
9011
+ ]);
9012
+ const isSessionCredentialInvalidError = Schema$1.is(SessionCredentialInvalidError);
9013
+ const sessionCredentialInternalErrorContext = { cause: Schema$1.Defect() };
9014
+ var SessionClaimsEncodingError = class extends Schema$1.TaggedErrorClass()("SessionClaimsEncodingError", {
9015
+ sessionId: AuthSessionId,
9016
+ operation: Schema$1.Literals(["encode_session_claims", "encode_websocket_claims"]),
9017
+ ...sessionCredentialInternalErrorContext
9018
+ }) {
8999
9019
  get message() {
9000
- return `Failed to generate random bytes for ${this.resource}.`;
9020
+ return "Failed to encode claims";
9001
9021
  }
9002
9022
  };
9003
- var SecretStoreConcurrentReadError = class extends Schema$1.TaggedErrorClass()("SecretStoreConcurrentReadError", { resource: Schema$1.String }) {
9023
+ var SessionCredentialIssueError = class extends Schema$1.TaggedErrorClass()("SessionCredentialIssueError", {
9024
+ sessionId: Schema$1.optional(AuthSessionId),
9025
+ ...sessionCredentialInternalErrorContext
9026
+ }) {
9004
9027
  get message() {
9005
- return `Failed to read ${this.resource} after concurrent creation.`;
9028
+ return "Failed to issue session credential.";
9006
9029
  }
9007
9030
  };
9008
- var SecretStoreRemoveError = class extends Schema$1.TaggedErrorClass()("SecretStoreRemoveError", { ...secretStoreErrorContext }) {
9031
+ var SessionCredentialVerificationError = class extends Schema$1.TaggedErrorClass()("SessionCredentialVerificationError", {
9032
+ sessionId: AuthSessionId,
9033
+ ...sessionCredentialInternalErrorContext
9034
+ }) {
9009
9035
  get message() {
9010
- return `Failed to remove ${this.resource}.`;
9036
+ return "Failed to verify session credential.";
9011
9037
  }
9012
9038
  };
9013
- var SecretStoreDecodeError = class extends Schema$1.TaggedErrorClass()("SecretStoreDecodeError", { ...secretStoreErrorContext }) {
9039
+ var WebSocketTokenIssueError = class extends Schema$1.TaggedErrorClass()("WebSocketTokenIssueError", {
9040
+ sessionId: AuthSessionId,
9041
+ ...sessionCredentialInternalErrorContext
9042
+ }) {
9014
9043
  get message() {
9015
- return `Failed to decode ${this.resource}.`;
9044
+ return "Failed to issue websocket token.";
9016
9045
  }
9017
9046
  };
9018
- var SecretStoreEncodeError = class extends Schema$1.TaggedErrorClass()("SecretStoreEncodeError", { ...secretStoreErrorContext }) {
9047
+ var WebSocketTokenVerificationError = class extends Schema$1.TaggedErrorClass()("WebSocketTokenVerificationError", {
9048
+ sessionId: AuthSessionId,
9049
+ ...sessionCredentialInternalErrorContext
9050
+ }) {
9019
9051
  get message() {
9020
- return `Failed to encode ${this.resource}.`;
9052
+ return "Failed to verify websocket token.";
9021
9053
  }
9022
9054
  };
9023
- const SecretStoreError = Schema$1.Union([
9024
- SecretStoreSecureError,
9025
- SecretStoreReadError,
9026
- SecretStoreTemporaryPathError,
9027
- SecretStorePersistError,
9028
- SecretStoreRandomGenerationError,
9029
- SecretStoreConcurrentReadError,
9030
- SecretStoreRemoveError,
9031
- SecretStoreDecodeError,
9032
- SecretStoreEncodeError
9055
+ var ActiveSessionsListError = class extends Schema$1.TaggedErrorClass()("ActiveSessionsListError", { ...sessionCredentialInternalErrorContext }) {
9056
+ get message() {
9057
+ return "Failed to list active sessions.";
9058
+ }
9059
+ };
9060
+ var SessionRevocationError = class extends Schema$1.TaggedErrorClass()("SessionRevocationError", {
9061
+ sessionId: AuthSessionId,
9062
+ ...sessionCredentialInternalErrorContext
9063
+ }) {
9064
+ get message() {
9065
+ return "Failed to revoke session.";
9066
+ }
9067
+ };
9068
+ var OtherSessionsRevocationError = class extends Schema$1.TaggedErrorClass()("OtherSessionsRevocationError", {
9069
+ currentSessionId: AuthSessionId,
9070
+ ...sessionCredentialInternalErrorContext
9071
+ }) {
9072
+ get message() {
9073
+ return "Failed to revoke other sessions.";
9074
+ }
9075
+ };
9076
+ const SessionCredentialInternalError = Schema$1.Union([
9077
+ SessionClaimsEncodingError,
9078
+ SessionCredentialIssueError,
9079
+ SessionCredentialVerificationError,
9080
+ WebSocketTokenIssueError,
9081
+ WebSocketTokenVerificationError,
9082
+ ActiveSessionsListError,
9083
+ SessionRevocationError,
9084
+ OtherSessionsRevocationError
9033
9085
  ]);
9034
- const isSecretStoreError = Schema$1.is(SecretStoreError);
9035
- const isPlatformError = (value) => Predicate.isTagged(value, "PlatformError");
9036
- const isSecretAlreadyExistsError = (error) => "cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists";
9037
- var ServerSecretStore = class extends Context.Service()("@p4code/cli/auth/ServerSecretStore") {};
9086
+ Schema$1.is(SessionCredentialInternalError);
9087
+ const SessionCredentialError = Schema$1.Union([SessionCredentialInvalidError, SessionCredentialInternalError]);
9088
+ Schema$1.is(SessionCredentialError);
9089
+ /**
9090
+ * Subject the desktop bootstrap grant issues sessions under.
9091
+ *
9092
+ * Exported so the grant seed and the supersede rule cannot drift: they are the
9093
+ * two halves of one decision, and a typo in either would silently turn the
9094
+ * cleanup off.
9095
+ */
9096
+ const DESKTOP_BOOTSTRAP_SUBJECT = "desktop-bootstrap";
9097
+ var SessionStore = class extends Context.Service()("@p4code/cli/auth/SessionStore") {};
9098
+ const SIGNING_SECRET_NAME$1 = "server-signing-key";
9099
+ const DEFAULT_SESSION_TTL = Duration.days(30);
9100
+ const DEFAULT_WEBSOCKET_TOKEN_TTL = Duration.minutes(5);
9101
+ const SessionClaims = Schema$1.Struct({
9102
+ v: Schema$1.Literal(1),
9103
+ kind: Schema$1.Literal("session"),
9104
+ sid: AuthSessionId,
9105
+ sub: Schema$1.String,
9106
+ scopes: AuthEnvironmentScopes,
9107
+ method: Schema$1.Literals(["browser-session-cookie", "bearer-access-token"]),
9108
+ jkt: Schema$1.optionalKey(Schema$1.String),
9109
+ iat: Schema$1.Number,
9110
+ exp: Schema$1.Number
9111
+ });
9112
+ const WebSocketClaims = Schema$1.Struct({
9113
+ v: Schema$1.Literal(1),
9114
+ kind: Schema$1.Literal("websocket"),
9115
+ sid: AuthSessionId,
9116
+ iat: Schema$1.Number,
9117
+ exp: Schema$1.Number
9118
+ });
9119
+ const decodeSessionClaims = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(SessionClaims));
9120
+ const decodeWebSocketClaims = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(WebSocketClaims));
9121
+ function createDefaultClientMetadata() {
9122
+ return { deviceType: "unknown" };
9123
+ }
9124
+ function toClientMetadata(record) {
9125
+ return {
9126
+ ...record.label ? { label: record.label } : {},
9127
+ ...record.ipAddress ? { ipAddress: record.ipAddress } : {},
9128
+ ...record.userAgent ? { userAgent: record.userAgent } : {},
9129
+ deviceType: record.deviceType,
9130
+ ...record.os ? { os: record.os } : {},
9131
+ ...record.browser ? { browser: record.browser } : {}
9132
+ };
9133
+ }
9134
+ function toAuthClientSession(input) {
9135
+ return {
9136
+ ...input,
9137
+ current: false
9138
+ };
9139
+ }
9038
9140
  const make$65 = Effect.gen(function* () {
9039
9141
  const crypto = yield* Crypto.Crypto;
9040
- const fileSystem = yield* FileSystem.FileSystem;
9041
- const path = yield* Path.Path;
9042
9142
  const serverConfig = yield* ServerConfig$1;
9043
- yield* fileSystem.makeDirectory(serverConfig.secretsDir, { recursive: true });
9044
- yield* fileSystem.chmod(serverConfig.secretsDir, 448).pipe(Effect.mapError((cause) => new SecretStoreSecureError({
9045
- resource: `secrets directory ${serverConfig.secretsDir}`,
9143
+ const secretStore = yield* ServerSecretStore;
9144
+ const authSessions = yield* AuthSessionRepository;
9145
+ const signingSecret = yield* secretStore.getOrCreateRandom(SIGNING_SECRET_NAME$1, 32);
9146
+ const connectedSessionsRef = yield* Ref.make(/* @__PURE__ */ new Map());
9147
+ const changesPubSub = yield* PubSub.unbounded();
9148
+ const cookieName = resolveSessionCookieName({
9149
+ mode: serverConfig.mode,
9150
+ port: serverConfig.port,
9151
+ host: serverConfig.host,
9152
+ instanceKey: serverConfig.stateDir,
9153
+ development: serverConfig.devUrl !== void 0
9154
+ });
9155
+ const emitUpsert = (clientSession) => PubSub.publish(changesPubSub, {
9156
+ type: "clientUpserted",
9157
+ clientSession
9158
+ }).pipe(Effect.asVoid);
9159
+ const emitRemoved = (sessionId) => PubSub.publish(changesPubSub, {
9160
+ type: "clientRemoved",
9161
+ sessionId
9162
+ }).pipe(Effect.asVoid);
9163
+ const loadActiveSession = (sessionId) => Effect.gen(function* () {
9164
+ const row = yield* authSessions.getById({ sessionId });
9165
+ if (Option.isNone(row) || row.value.revokedAt !== null) return Option.none();
9166
+ const connectedSessions = yield* Ref.get(connectedSessionsRef);
9167
+ return Option.some(toAuthClientSession({
9168
+ sessionId: row.value.sessionId,
9169
+ subject: row.value.subject,
9170
+ scopes: row.value.scopes,
9171
+ method: row.value.method,
9172
+ client: toClientMetadata(row.value.client),
9173
+ issuedAt: row.value.issuedAt,
9174
+ expiresAt: row.value.expiresAt,
9175
+ lastConnectedAt: row.value.lastConnectedAt,
9176
+ connected: connectedSessions.has(row.value.sessionId)
9177
+ }));
9178
+ });
9179
+ const markConnected = (sessionId) => Ref.modify(connectedSessionsRef, (current) => {
9180
+ const next = new Map(current);
9181
+ const wasDisconnected = !next.has(sessionId);
9182
+ next.set(sessionId, (next.get(sessionId) ?? 0) + 1);
9183
+ return [wasDisconnected, next];
9184
+ }).pipe(Effect.flatMap((wasDisconnected) => wasDisconnected ? DateTime.now.pipe(Effect.flatMap((lastConnectedAt) => authSessions.setLastConnectedAt({
9185
+ sessionId,
9186
+ lastConnectedAt
9187
+ }))) : Effect.void), Effect.flatMap(() => loadActiveSession(sessionId)), Effect.flatMap((session) => Option.isSome(session) ? emitUpsert(session.value) : Effect.void), Effect.catchCause((cause) => Effect.logError("Failed to publish connected-session auth update.").pipe(Effect.annotateLogs({
9188
+ sessionId,
9046
9189
  cause
9047
- })));
9048
- const resolveSecretPath = (name) => path.join(serverConfig.secretsDir, `${name}.bin`);
9049
- const get = (name) => fileSystem.readFile(resolveSecretPath(name)).pipe(Effect.map((bytes) => Option.some(Uint8Array.from(bytes))), Effect.catch((cause) => cause.reason._tag === "NotFound" ? Effect.succeed(Option.none()) : Effect.fail(new SecretStoreReadError({
9050
- resource: `secret ${name}`,
9190
+ }))), Effect.withSpan("SessionStore.markConnected"));
9191
+ const markDisconnected = (sessionId) => Ref.update(connectedSessionsRef, (current) => {
9192
+ const next = new Map(current);
9193
+ const remaining = (next.get(sessionId) ?? 0) - 1;
9194
+ if (remaining > 0) next.set(sessionId, remaining);
9195
+ else next.delete(sessionId);
9196
+ return next;
9197
+ }).pipe(Effect.flatMap(() => loadActiveSession(sessionId)), Effect.flatMap((session) => Option.isSome(session) ? emitUpsert(session.value) : Effect.void), Effect.catchCause((cause) => Effect.logError("Failed to publish disconnected-session auth update.").pipe(Effect.annotateLogs({
9198
+ sessionId,
9051
9199
  cause
9052
- }))), Effect.withSpan("ServerSecretStore.get"));
9053
- const set = (name, value) => {
9054
- const secretPath = resolveSecretPath(name);
9055
- return crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new SecretStoreTemporaryPathError({
9056
- resource: `secret ${name}`,
9200
+ }))), Effect.withSpan("SessionStore.markDisconnected"));
9201
+ const encodeClaims = Schema$1.encodeEffect(Schema$1.fromJsonString(SessionClaims));
9202
+ /**
9203
+ * Revokes every other live session belonging to the same subject.
9204
+ *
9205
+ * Best effort: a session that could not be retired is a stale row in a list,
9206
+ * which is untidy, and failing the issue over it would mean a desktop app
9207
+ * that cannot start. The next launch tries again.
9208
+ */
9209
+ const supersedePriorSessions = Effect.fn("SessionStore.supersedePriorSessions")(function* (keep, subject) {
9210
+ const active = yield* listActive().pipe(Effect.orElseSucceed(() => []));
9211
+ for (const session of active) {
9212
+ if (session.sessionId === keep || session.subject !== subject) continue;
9213
+ yield* revoke(session.sessionId).pipe(Effect.ignore);
9214
+ }
9215
+ });
9216
+ const issue = Effect.fn("SessionStore.issue")(function* (input) {
9217
+ const sessionId = AuthSessionId.make(yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new SessionCredentialIssueError({ cause }))));
9218
+ const issuedAt = yield* DateTime.now;
9219
+ const expiresAt = DateTime.add(issuedAt, { milliseconds: Duration.toMillis(input?.ttl ?? DEFAULT_SESSION_TTL) });
9220
+ const claims = {
9221
+ v: 1,
9222
+ kind: "session",
9223
+ sid: sessionId,
9224
+ sub: input?.subject ?? "browser",
9225
+ scopes: input?.scopes ?? AuthStandardClientScopes,
9226
+ method: input?.method ?? "browser-session-cookie",
9227
+ ...input?.proofKeyThumbprint ? { jkt: input.proofKeyThumbprint } : {},
9228
+ iat: issuedAt.epochMilliseconds,
9229
+ exp: expiresAt.epochMilliseconds
9230
+ };
9231
+ const encodedPayload = yield* encodeClaims(claims).pipe(Effect.map(base64UrlEncode), Effect.mapError((cause) => new SessionCredentialIssueError({
9232
+ sessionId,
9233
+ cause: new SessionClaimsEncodingError({
9234
+ sessionId,
9235
+ operation: "encode_session_claims",
9236
+ cause
9237
+ })
9238
+ })));
9239
+ const signature = signPayload(encodedPayload, signingSecret);
9240
+ const client = input?.client ?? createDefaultClientMetadata();
9241
+ yield* authSessions.create({
9242
+ sessionId,
9243
+ subject: claims.sub,
9244
+ scopes: claims.scopes,
9245
+ method: claims.method,
9246
+ client: {
9247
+ label: client.label ?? null,
9248
+ ipAddress: client.ipAddress ?? null,
9249
+ userAgent: client.userAgent ?? null,
9250
+ deviceType: client.deviceType,
9251
+ os: client.os ?? null,
9252
+ browser: client.browser ?? null
9253
+ },
9254
+ issuedAt,
9255
+ expiresAt
9256
+ }).pipe(Effect.mapError((cause) => new SessionCredentialIssueError({
9257
+ sessionId,
9057
9258
  cause
9058
- })), Effect.flatMap((uuid) => {
9059
- const tempPath = `${secretPath}.${uuid}.tmp`;
9060
- return Effect.gen(function* () {
9061
- yield* fileSystem.writeFile(tempPath, value);
9062
- yield* fileSystem.chmod(tempPath, 384);
9063
- yield* fileSystem.rename(tempPath, secretPath);
9064
- yield* fileSystem.chmod(secretPath, 384);
9065
- }).pipe(Effect.catch((cause) => fileSystem.remove(tempPath).pipe(Effect.ignore, Effect.flatMap(() => Effect.fail(new SecretStorePersistError({
9066
- resource: `secret ${name}`,
9259
+ })));
9260
+ yield* emitUpsert(toAuthClientSession({
9261
+ sessionId,
9262
+ subject: claims.sub,
9263
+ scopes: claims.scopes,
9264
+ method: claims.method,
9265
+ client,
9266
+ issuedAt,
9267
+ expiresAt,
9268
+ lastConnectedAt: null,
9269
+ connected: false
9270
+ }));
9271
+ if (claims.sub === "desktop-bootstrap") yield* supersedePriorSessions(sessionId, claims.sub);
9272
+ return {
9273
+ sessionId,
9274
+ token: `${encodedPayload}.${signature}`,
9275
+ method: claims.method,
9276
+ client,
9277
+ expiresAt,
9278
+ scopes: claims.scopes,
9279
+ ...claims.jkt ? { proofKeyThumbprint: claims.jkt } : {}
9280
+ };
9281
+ });
9282
+ const verify = Effect.fn("SessionStore.verify")(function* (token) {
9283
+ const [encodedPayload, signature] = token.split(".");
9284
+ if (!encodedPayload || !signature) return yield* new MalformedSessionTokenError({});
9285
+ if (!timingSafeEqualBase64Url(signature, signPayload(encodedPayload, signingSecret))) return yield* new InvalidSessionTokenSignatureError({});
9286
+ const claims = yield* decodeSessionClaims(base64UrlDecodeUtf8(encodedPayload)).pipe(Effect.mapError((cause) => new InvalidSessionTokenPayloadError({ cause })));
9287
+ const observedAt = yield* DateTime.now;
9288
+ const expiresAt = DateTime.make(claims.exp);
9289
+ if (Option.isNone(expiresAt)) return yield* new InvalidSessionExpirationClaimError({
9290
+ sessionId: claims.sid,
9291
+ expirationClaim: claims.exp
9292
+ });
9293
+ if (claims.exp <= observedAt.epochMilliseconds) return yield* new SessionTokenExpiredError({
9294
+ sessionId: claims.sid,
9295
+ expiresAt: expiresAt.value,
9296
+ observedAt
9297
+ });
9298
+ const row = yield* authSessions.getById({ sessionId: claims.sid }).pipe(Effect.mapError((cause) => new SessionCredentialVerificationError({
9299
+ sessionId: claims.sid,
9300
+ cause
9301
+ })));
9302
+ if (Option.isNone(row)) return yield* new UnknownSessionTokenError({ sessionId: claims.sid });
9303
+ if (row.value.revokedAt !== null) return yield* new SessionTokenRevokedError({
9304
+ sessionId: claims.sid,
9305
+ revokedAt: row.value.revokedAt
9306
+ });
9307
+ return {
9308
+ sessionId: claims.sid,
9309
+ token,
9310
+ method: claims.method,
9311
+ client: toClientMetadata(row.value.client),
9312
+ expiresAt: expiresAt.value,
9313
+ subject: claims.sub,
9314
+ scopes: claims.scopes,
9315
+ ...claims.jkt ? { proofKeyThumbprint: claims.jkt } : {}
9316
+ };
9317
+ });
9318
+ const encodeWsClaims = Schema$1.encodeEffect(Schema$1.fromJsonString(WebSocketClaims));
9319
+ const issueWebSocketToken = Effect.fn("SessionStore.issueWebSocketToken")(function* (sessionId, input) {
9320
+ const issuedAt = yield* DateTime.now;
9321
+ const expiresAt = DateTime.add(issuedAt, { milliseconds: Duration.toMillis(input?.ttl ?? DEFAULT_WEBSOCKET_TOKEN_TTL) });
9322
+ const claims = {
9323
+ v: 1,
9324
+ kind: "websocket",
9325
+ sid: sessionId,
9326
+ iat: issuedAt.epochMilliseconds,
9327
+ exp: expiresAt.epochMilliseconds
9328
+ };
9329
+ const encodedPayload = yield* encodeWsClaims(claims).pipe(Effect.map(base64UrlEncode), Effect.mapError((cause) => new WebSocketTokenIssueError({
9330
+ sessionId,
9331
+ cause: new SessionClaimsEncodingError({
9332
+ sessionId,
9333
+ operation: "encode_websocket_claims",
9067
9334
  cause
9068
- }))))));
9069
- }), Effect.withSpan("ServerSecretStore.set"));
9070
- };
9071
- const create = (name, value) => {
9072
- const secretPath = resolveSecretPath(name);
9073
- return Effect.scoped(Effect.gen(function* () {
9074
- const file = yield* fileSystem.open(secretPath, {
9075
- flag: "wx",
9076
- mode: 384
9077
- });
9078
- yield* file.writeAll(value);
9079
- yield* file.sync;
9080
- yield* fileSystem.chmod(secretPath, 384);
9081
- })).pipe(Effect.mapError((cause) => new SecretStorePersistError({
9082
- resource: `secret ${name}`,
9335
+ })
9336
+ })));
9337
+ return {
9338
+ token: `${encodedPayload}.${signPayload(encodedPayload, signingSecret)}`,
9339
+ expiresAt
9340
+ };
9341
+ });
9342
+ const verifyWebSocketToken = Effect.fn("SessionStore.verifyWebSocketToken")(function* (token) {
9343
+ const [encodedPayload, signature] = token.split(".");
9344
+ if (!encodedPayload || !signature) return yield* new MalformedWebSocketTokenError({});
9345
+ if (!timingSafeEqualBase64Url(signature, signPayload(encodedPayload, signingSecret))) return yield* new InvalidWebSocketTokenSignatureError({});
9346
+ const claims = yield* decodeWebSocketClaims(base64UrlDecodeUtf8(encodedPayload)).pipe(Effect.mapError((cause) => new InvalidWebSocketTokenPayloadError({ cause })));
9347
+ const observedAt = yield* DateTime.now;
9348
+ const expiresAt = DateTime.make(claims.exp);
9349
+ if (Option.isNone(expiresAt)) return yield* new InvalidSessionExpirationClaimError({
9350
+ sessionId: claims.sid,
9351
+ expirationClaim: claims.exp
9352
+ });
9353
+ if (claims.exp <= observedAt.epochMilliseconds) return yield* new WebSocketTokenExpiredError({
9354
+ sessionId: claims.sid,
9355
+ expiresAt: expiresAt.value,
9356
+ observedAt
9357
+ });
9358
+ const row = yield* authSessions.getById({ sessionId: claims.sid }).pipe(Effect.mapError((cause) => new WebSocketTokenVerificationError({
9359
+ sessionId: claims.sid,
9083
9360
  cause
9084
9361
  })));
9085
- };
9086
- const getOrCreateRandom = (name, bytes) => get(name).pipe(Effect.flatMap(Option.match({
9087
- onSome: Effect.succeed,
9088
- onNone: () => crypto.randomBytes(bytes).pipe(Effect.mapError((cause) => new SecretStoreRandomGenerationError({
9089
- resource: `secret ${name}`,
9362
+ if (Option.isNone(row)) return yield* new UnknownWebSocketSessionError({ sessionId: claims.sid });
9363
+ if (row.value.expiresAt.epochMilliseconds <= observedAt.epochMilliseconds) return yield* new WebSocketSessionExpiredError({
9364
+ sessionId: claims.sid,
9365
+ expiresAt: row.value.expiresAt,
9366
+ observedAt
9367
+ });
9368
+ if (row.value.revokedAt !== null) return yield* new WebSocketSessionRevokedError({
9369
+ sessionId: claims.sid,
9370
+ revokedAt: row.value.revokedAt
9371
+ });
9372
+ return {
9373
+ sessionId: row.value.sessionId,
9374
+ token,
9375
+ method: row.value.method,
9376
+ client: toClientMetadata(row.value.client),
9377
+ expiresAt: row.value.expiresAt,
9378
+ subject: row.value.subject,
9379
+ scopes: row.value.scopes
9380
+ };
9381
+ });
9382
+ const listActive = Effect.fn("SessionStore.listActive")(function* () {
9383
+ const now = yield* DateTime.now;
9384
+ const connectedSessions = yield* Ref.get(connectedSessionsRef);
9385
+ return (yield* authSessions.listActive({ now })).map((row) => toAuthClientSession({
9386
+ sessionId: row.sessionId,
9387
+ subject: row.subject,
9388
+ scopes: row.scopes,
9389
+ method: row.method,
9390
+ client: toClientMetadata(row.client),
9391
+ issuedAt: row.issuedAt,
9392
+ expiresAt: row.expiresAt,
9393
+ lastConnectedAt: row.lastConnectedAt,
9394
+ connected: connectedSessions.has(row.sessionId)
9395
+ }));
9396
+ }, Effect.mapError((cause) => new ActiveSessionsListError({ cause })));
9397
+ const revoke = Effect.fn("SessionStore.revoke")(function* (sessionId) {
9398
+ const revokedAt = yield* DateTime.now;
9399
+ const revoked = yield* authSessions.revoke({
9400
+ sessionId,
9401
+ revokedAt
9402
+ }).pipe(Effect.mapError((cause) => new SessionRevocationError({
9403
+ sessionId,
9090
9404
  cause
9091
- })), Effect.flatMap((generated) => create(name, generated).pipe(Effect.as(Uint8Array.from(generated)), Effect.catchIf(isSecretStoreError, (error) => isSecretAlreadyExistsError(error) ? get(name).pipe(Effect.flatMap(Option.match({
9092
- onSome: Effect.succeed,
9093
- onNone: () => Effect.fail(new SecretStoreConcurrentReadError({ resource: `secret ${name}` }))
9094
- }))) : Effect.fail(error)))))
9095
- })), Effect.withSpan("ServerSecretStore.getOrCreateRandom"));
9096
- const remove = (name) => fileSystem.remove(resolveSecretPath(name)).pipe(Effect.catch((cause) => cause.reason._tag === "NotFound" ? Effect.void : Effect.fail(new SecretStoreRemoveError({
9097
- resource: `secret ${name}`,
9098
- cause
9099
- }))), Effect.withSpan("ServerSecretStore.remove"));
9100
- return ServerSecretStore.of({
9101
- get,
9102
- set,
9103
- create,
9104
- getOrCreateRandom,
9105
- remove
9405
+ })));
9406
+ if (revoked) {
9407
+ yield* Ref.update(connectedSessionsRef, (current) => {
9408
+ const next = new Map(current);
9409
+ next.delete(sessionId);
9410
+ return next;
9411
+ });
9412
+ yield* emitRemoved(sessionId);
9413
+ }
9414
+ return revoked;
9415
+ });
9416
+ const revokeAllExcept = Effect.fn("SessionStore.revokeAllExcept")(function* (sessionId) {
9417
+ const revokedAt = yield* DateTime.now;
9418
+ const revokedSessionIds = yield* authSessions.revokeAllExcept({
9419
+ currentSessionId: sessionId,
9420
+ revokedAt
9421
+ }).pipe(Effect.mapError((cause) => new OtherSessionsRevocationError({
9422
+ currentSessionId: sessionId,
9423
+ cause
9424
+ })));
9425
+ if (revokedSessionIds.length > 0) {
9426
+ yield* Ref.update(connectedSessionsRef, (current) => {
9427
+ const next = new Map(current);
9428
+ for (const revokedSessionId of revokedSessionIds) next.delete(revokedSessionId);
9429
+ return next;
9430
+ });
9431
+ yield* Effect.forEach(revokedSessionIds, (revokedSessionId) => emitRemoved(revokedSessionId), {
9432
+ concurrency: "unbounded",
9433
+ discard: true
9434
+ });
9435
+ }
9436
+ return revokedSessionIds.length;
9437
+ });
9438
+ return SessionStore.of({
9439
+ cookieName,
9440
+ issue,
9441
+ verify,
9442
+ issueWebSocketToken,
9443
+ verifyWebSocketToken,
9444
+ listActive,
9445
+ get streamChanges() {
9446
+ return Stream.fromPubSub(changesPubSub);
9447
+ },
9448
+ revoke,
9449
+ revokeAllExcept,
9450
+ markConnected,
9451
+ markDisconnected
9106
9452
  });
9107
9453
  });
9108
- const layer$61 = Layer.effect(ServerSecretStore, make$65);
9454
+ const layer$61 = Layer.effect(SessionStore, make$65).pipe(Layer.provideMerge(layer$63));
9109
9455
  //#endregion
9110
- //#region src/persistence/AuthSessions.ts
9111
- const AuthSessionClientMetadataRecord = Schema$1.Struct({
9112
- label: Schema$1.NullOr(Schema$1.String),
9113
- ipAddress: Schema$1.NullOr(Schema$1.String),
9114
- userAgent: Schema$1.NullOr(Schema$1.String),
9115
- deviceType: AuthClientMetadataDeviceType,
9116
- os: Schema$1.NullOr(Schema$1.String),
9117
- browser: Schema$1.NullOr(Schema$1.String)
9118
- });
9119
- Schema$1.Struct({
9120
- sessionId: AuthSessionId,
9121
- subject: Schema$1.String,
9122
- scopes: AuthEnvironmentScopes,
9123
- method: ServerAuthSessionMethod,
9124
- client: AuthSessionClientMetadataRecord,
9125
- issuedAt: Schema$1.DateTimeUtcFromString,
9456
+ //#region src/persistence/AuthPairingLinks.ts
9457
+ const AuthPairingLinkRecord = Schema$1.Struct({
9458
+ id: Schema$1.String,
9459
+ credential: Schema$1.String,
9460
+ method: Schema$1.Literals(["desktop-bootstrap", "one-time-token"]),
9461
+ scopes: Schema$1.fromJsonString(AuthEnvironmentScopes),
9462
+ subject: Schema$1.String,
9463
+ label: Schema$1.NullOr(Schema$1.String),
9464
+ proofKeyThumbprint: Schema$1.NullOr(Schema$1.String),
9465
+ createdAt: Schema$1.DateTimeUtcFromString,
9126
9466
  expiresAt: Schema$1.DateTimeUtcFromString,
9127
- lastConnectedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString),
9467
+ consumedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString),
9128
9468
  revokedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString)
9129
9469
  });
9130
- const CreateAuthSessionInput = Schema$1.Struct({
9131
- sessionId: AuthSessionId,
9132
- subject: Schema$1.String,
9470
+ const CreateAuthPairingLinkInput = Schema$1.Struct({
9471
+ id: Schema$1.String,
9472
+ credential: Schema$1.String,
9473
+ method: Schema$1.Literals(["desktop-bootstrap", "one-time-token"]),
9133
9474
  scopes: AuthEnvironmentScopes,
9134
- method: ServerAuthSessionMethod,
9135
- client: AuthSessionClientMetadataRecord,
9136
- issuedAt: Schema$1.DateTimeUtcFromString,
9475
+ subject: Schema$1.String,
9476
+ label: Schema$1.NullOr(Schema$1.String),
9477
+ proofKeyThumbprint: Schema$1.NullOr(Schema$1.String),
9478
+ createdAt: Schema$1.DateTimeUtcFromString,
9137
9479
  expiresAt: Schema$1.DateTimeUtcFromString
9138
9480
  });
9139
- const GetAuthSessionByIdInput = Schema$1.Struct({ sessionId: AuthSessionId });
9140
- const ListActiveAuthSessionsInput = Schema$1.Struct({ now: Schema$1.DateTimeUtcFromString });
9141
- const RevokeAuthSessionInput = Schema$1.Struct({
9142
- sessionId: AuthSessionId,
9143
- revokedAt: Schema$1.DateTimeUtcFromString
9481
+ const ConsumeAuthPairingLinkInput = Schema$1.Struct({
9482
+ credential: Schema$1.String,
9483
+ proofKeyThumbprint: Schema$1.NullOr(Schema$1.String),
9484
+ consumedAt: Schema$1.DateTimeUtcFromString,
9485
+ now: Schema$1.DateTimeUtcFromString
9144
9486
  });
9145
- const RevokeOtherAuthSessionsInput = Schema$1.Struct({
9146
- currentSessionId: AuthSessionId,
9487
+ const ListActiveAuthPairingLinksInput = Schema$1.Struct({ now: Schema$1.DateTimeUtcFromString });
9488
+ const RevokeAuthPairingLinkInput = Schema$1.Struct({
9489
+ id: Schema$1.String,
9147
9490
  revokedAt: Schema$1.DateTimeUtcFromString
9148
9491
  });
9149
- const SetAuthSessionLastConnectedAtInput = Schema$1.Struct({
9150
- sessionId: AuthSessionId,
9151
- lastConnectedAt: Schema$1.DateTimeUtcFromString
9152
- });
9153
- var AuthSessionRepository = class extends Context.Service()("@p4code/cli/persistence/AuthSessions/AuthSessionRepository") {};
9154
- const AuthSessionDbRow = Schema$1.Struct({
9155
- sessionId: AuthSessionId,
9156
- subject: Schema$1.String,
9157
- scopes: Schema$1.fromJsonString(AuthEnvironmentScopes),
9158
- method: ServerAuthSessionMethod,
9159
- clientLabel: Schema$1.NullOr(Schema$1.String),
9160
- clientIpAddress: Schema$1.NullOr(Schema$1.String),
9161
- clientUserAgent: Schema$1.NullOr(Schema$1.String),
9162
- clientDeviceType: Schema$1.Literals([
9163
- "desktop",
9164
- "mobile",
9165
- "tablet",
9166
- "bot",
9167
- "unknown"
9168
- ]),
9169
- clientOs: Schema$1.NullOr(Schema$1.String),
9170
- clientBrowser: Schema$1.NullOr(Schema$1.String),
9171
- issuedAt: Schema$1.DateTimeUtcFromString,
9172
- expiresAt: Schema$1.DateTimeUtcFromString,
9173
- lastConnectedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString),
9174
- revokedAt: Schema$1.NullOr(Schema$1.DateTimeUtcFromString)
9175
- });
9176
- const AuthSessionRawDbRow = Schema$1.Struct({
9177
- sessionId: Schema$1.String,
9178
- subject: Schema$1.Unknown,
9179
- scopes: Schema$1.Unknown,
9492
+ const GetAuthPairingLinkByCredentialInput = Schema$1.Struct({ credential: Schema$1.String });
9493
+ const AuthPairingLinkRawDbRow = Schema$1.Struct({
9494
+ id: Schema$1.String,
9495
+ credential: Schema$1.Unknown,
9180
9496
  method: Schema$1.Unknown,
9181
- clientLabel: Schema$1.Unknown,
9182
- clientIpAddress: Schema$1.Unknown,
9183
- clientUserAgent: Schema$1.Unknown,
9184
- clientDeviceType: Schema$1.Unknown,
9185
- clientOs: Schema$1.Unknown,
9186
- clientBrowser: Schema$1.Unknown,
9187
- issuedAt: Schema$1.Unknown,
9497
+ scopes: Schema$1.Unknown,
9498
+ subject: Schema$1.Unknown,
9499
+ label: Schema$1.Unknown,
9500
+ proofKeyThumbprint: Schema$1.Unknown,
9501
+ createdAt: Schema$1.Unknown,
9188
9502
  expiresAt: Schema$1.Unknown,
9189
- lastConnectedAt: Schema$1.Unknown,
9503
+ consumedAt: Schema$1.Unknown,
9190
9504
  revokedAt: Schema$1.Unknown
9191
9505
  });
9192
- const decodeAuthSessionDbRow = Schema$1.decodeUnknownEffect(AuthSessionDbRow);
9193
- function toAuthSessionRecord(row) {
9194
- return {
9195
- sessionId: row.sessionId,
9196
- subject: row.subject,
9197
- scopes: row.scopes,
9198
- method: row.method,
9199
- client: {
9200
- label: row.clientLabel,
9201
- ipAddress: row.clientIpAddress,
9202
- userAgent: row.clientUserAgent,
9203
- deviceType: row.clientDeviceType,
9204
- os: row.clientOs,
9205
- browser: row.clientBrowser
9206
- },
9207
- issuedAt: row.issuedAt,
9208
- expiresAt: row.expiresAt,
9209
- lastConnectedAt: row.lastConnectedAt,
9210
- revokedAt: row.revokedAt
9211
- };
9212
- }
9506
+ const decodeAuthPairingLinkDbRow = Schema$1.decodeUnknownEffect(AuthPairingLinkRecord);
9507
+ var AuthPairingLinkRepository = class extends Context.Service()("@p4code/cli/persistence/AuthPairingLinks/AuthPairingLinkRepository") {};
9213
9508
  function toPersistenceSqlOrDecodeError$5(sqlOperation, decodeOperation, correlation) {
9214
9509
  return (cause) => Schema$1.isSchemaError(cause) ? PersistenceDecodeError.fromSchemaError(decodeOperation, cause, correlation) : new PersistenceSqlError({
9215
9510
  operation: sqlOperation,
@@ -9219,668 +9514,433 @@ function toPersistenceSqlOrDecodeError$5(sqlOperation, decodeOperation, correlat
9219
9514
  }
9220
9515
  const make$64 = Effect.gen(function* () {
9221
9516
  const sql = yield* SqlClient.SqlClient;
9222
- const createSessionRow = SqlSchema.void({
9223
- Request: CreateAuthSessionInput,
9517
+ const createPairingLinkRow = SqlSchema.void({
9518
+ Request: CreateAuthPairingLinkInput,
9224
9519
  execute: (input) => sql`
9225
- INSERT INTO auth_sessions (
9226
- session_id,
9227
- subject,
9228
- scopes,
9520
+ INSERT INTO auth_pairing_links (
9521
+ id,
9522
+ credential,
9229
9523
  method,
9230
- client_label,
9231
- client_ip_address,
9232
- client_user_agent,
9233
- client_device_type,
9234
- client_os,
9235
- client_browser,
9236
- issued_at,
9524
+ scopes,
9525
+ subject,
9526
+ label,
9527
+ proof_key_thumbprint,
9528
+ created_at,
9237
9529
  expires_at,
9530
+ consumed_at,
9238
9531
  revoked_at
9239
9532
  )
9240
9533
  VALUES (
9241
- ${input.sessionId},
9242
- ${input.subject},
9243
- ${JSON.stringify(input.scopes)},
9534
+ ${input.id},
9535
+ ${input.credential},
9244
9536
  ${input.method},
9245
- ${input.client.label},
9246
- ${input.client.ipAddress},
9247
- ${input.client.userAgent},
9248
- ${input.client.deviceType},
9249
- ${input.client.os},
9250
- ${input.client.browser},
9251
- ${input.issuedAt},
9537
+ ${JSON.stringify(input.scopes)},
9538
+ ${input.subject},
9539
+ ${input.label},
9540
+ ${input.proofKeyThumbprint},
9541
+ ${input.createdAt},
9252
9542
  ${input.expiresAt},
9543
+ NULL,
9253
9544
  NULL
9254
9545
  )
9255
9546
  `
9256
9547
  });
9257
- const getSessionRowById = SqlSchema.findOneOption({
9258
- Request: GetAuthSessionByIdInput,
9259
- Result: AuthSessionRawDbRow,
9260
- execute: ({ sessionId }) => sql`
9261
- SELECT
9262
- session_id AS "sessionId",
9263
- subject AS "subject",
9264
- scopes AS "scopes",
9548
+ const consumeAvailablePairingLinkRow = SqlSchema.findOneOption({
9549
+ Request: ConsumeAuthPairingLinkInput,
9550
+ Result: AuthPairingLinkRawDbRow,
9551
+ execute: ({ credential, proofKeyThumbprint, consumedAt, now }) => sql`
9552
+ UPDATE auth_pairing_links
9553
+ SET consumed_at = ${consumedAt}
9554
+ WHERE credential = ${credential}
9555
+ AND revoked_at IS NULL
9556
+ AND consumed_at IS NULL
9557
+ AND expires_at > ${now}
9558
+ AND (
9559
+ proof_key_thumbprint IS NULL
9560
+ OR proof_key_thumbprint = ${proofKeyThumbprint}
9561
+ )
9562
+ RETURNING
9563
+ id AS "id",
9564
+ credential AS "credential",
9265
9565
  method AS "method",
9266
- client_label AS "clientLabel",
9267
- client_ip_address AS "clientIpAddress",
9268
- client_user_agent AS "clientUserAgent",
9269
- client_device_type AS "clientDeviceType",
9270
- client_os AS "clientOs",
9271
- client_browser AS "clientBrowser",
9272
- issued_at AS "issuedAt",
9566
+ scopes AS "scopes",
9567
+ subject AS "subject",
9568
+ label AS "label",
9569
+ proof_key_thumbprint AS "proofKeyThumbprint",
9570
+ created_at AS "createdAt",
9273
9571
  expires_at AS "expiresAt",
9274
- last_connected_at AS "lastConnectedAt",
9572
+ consumed_at AS "consumedAt",
9275
9573
  revoked_at AS "revokedAt"
9276
- FROM auth_sessions
9277
- WHERE session_id = ${sessionId}
9278
9574
  `
9279
9575
  });
9280
- const listActiveSessionRows = SqlSchema.findAll({
9281
- Request: ListActiveAuthSessionsInput,
9282
- Result: AuthSessionRawDbRow,
9576
+ const listActivePairingLinkRows = SqlSchema.findAll({
9577
+ Request: ListActiveAuthPairingLinksInput,
9578
+ Result: AuthPairingLinkRawDbRow,
9283
9579
  execute: ({ now }) => sql`
9284
9580
  SELECT
9285
- session_id AS "sessionId",
9286
- subject AS "subject",
9287
- scopes AS "scopes",
9581
+ id AS "id",
9582
+ credential AS "credential",
9288
9583
  method AS "method",
9289
- client_label AS "clientLabel",
9290
- client_ip_address AS "clientIpAddress",
9291
- client_user_agent AS "clientUserAgent",
9292
- client_device_type AS "clientDeviceType",
9293
- client_os AS "clientOs",
9294
- client_browser AS "clientBrowser",
9295
- issued_at AS "issuedAt",
9584
+ scopes AS "scopes",
9585
+ subject AS "subject",
9586
+ label AS "label",
9587
+ proof_key_thumbprint AS "proofKeyThumbprint",
9588
+ created_at AS "createdAt",
9296
9589
  expires_at AS "expiresAt",
9297
- last_connected_at AS "lastConnectedAt",
9590
+ consumed_at AS "consumedAt",
9298
9591
  revoked_at AS "revokedAt"
9299
- FROM auth_sessions
9592
+ FROM auth_pairing_links
9300
9593
  WHERE revoked_at IS NULL
9594
+ AND consumed_at IS NULL
9301
9595
  AND expires_at > ${now}
9302
- ORDER BY issued_at DESC, session_id DESC
9303
- `
9304
- });
9305
- const setLastConnectedAtRow = SqlSchema.void({
9306
- Request: SetAuthSessionLastConnectedAtInput,
9307
- execute: ({ sessionId, lastConnectedAt }) => sql`
9308
- UPDATE auth_sessions
9309
- SET last_connected_at = ${lastConnectedAt}
9310
- WHERE session_id = ${sessionId}
9311
- AND revoked_at IS NULL
9596
+ ORDER BY created_at DESC, id DESC
9312
9597
  `
9313
9598
  });
9314
- const revokeSessionRows = SqlSchema.findAll({
9315
- Request: RevokeAuthSessionInput,
9316
- Result: Schema$1.Struct({ sessionId: AuthSessionId }),
9317
- execute: ({ sessionId, revokedAt }) => sql`
9318
- UPDATE auth_sessions
9599
+ const revokePairingLinkRow = SqlSchema.findAll({
9600
+ Request: RevokeAuthPairingLinkInput,
9601
+ Result: Schema$1.Struct({ id: Schema$1.String }),
9602
+ execute: ({ id, revokedAt }) => sql`
9603
+ UPDATE auth_pairing_links
9319
9604
  SET revoked_at = ${revokedAt}
9320
- WHERE session_id = ${sessionId}
9605
+ WHERE id = ${id}
9321
9606
  AND revoked_at IS NULL
9322
- RETURNING session_id AS "sessionId"
9607
+ AND consumed_at IS NULL
9608
+ RETURNING id AS "id"
9323
9609
  `
9324
9610
  });
9325
- const revokeOtherSessionRows = SqlSchema.findAll({
9326
- Request: RevokeOtherAuthSessionsInput,
9327
- Result: Schema$1.Struct({ sessionId: AuthSessionId }),
9328
- execute: ({ currentSessionId, revokedAt }) => sql`
9329
- UPDATE auth_sessions
9330
- SET revoked_at = ${revokedAt}
9331
- WHERE session_id <> ${currentSessionId}
9332
- AND revoked_at IS NULL
9333
- RETURNING session_id AS "sessionId"
9611
+ const getPairingLinkRowByCredential = SqlSchema.findOneOption({
9612
+ Request: GetAuthPairingLinkByCredentialInput,
9613
+ Result: AuthPairingLinkRawDbRow,
9614
+ execute: ({ credential }) => sql`
9615
+ SELECT
9616
+ id AS "id",
9617
+ credential AS "credential",
9618
+ method AS "method",
9619
+ scopes AS "scopes",
9620
+ subject AS "subject",
9621
+ label AS "label",
9622
+ proof_key_thumbprint AS "proofKeyThumbprint",
9623
+ created_at AS "createdAt",
9624
+ expires_at AS "expiresAt",
9625
+ consumed_at AS "consumedAt",
9626
+ revoked_at AS "revokedAt"
9627
+ FROM auth_pairing_links
9628
+ WHERE credential = ${credential}
9334
9629
  `
9335
9630
  });
9336
- const create = (input) => createSessionRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthSessionRepository.create:query", "AuthSessionRepository.create:encodeRequest", { sessionId: input.sessionId })));
9337
- const getById = (input) => getSessionRowById(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthSessionRepository.getById:query", "AuthSessionRepository.getById:decodeRow", { sessionId: input.sessionId })), Effect.flatMap((rowOption) => Option.match(rowOption, {
9631
+ const create = (input) => createPairingLinkRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthPairingLinkRepository.create:query", "AuthPairingLinkRepository.create:encodeRequest", { pairingLinkId: input.id })));
9632
+ const consumeAvailable = (input) => consumeAvailablePairingLinkRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthPairingLinkRepository.consumeAvailable:query", "AuthPairingLinkRepository.consumeAvailable:decodeRow")), Effect.flatMap((rowOption) => Option.match(rowOption, {
9338
9633
  onNone: () => Effect.succeed(Option.none()),
9339
- onSome: (row) => decodeAuthSessionDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthSessionRepository.getById:decodeRow", cause, { sessionId: input.sessionId })), Effect.map((decodedRow) => Option.some(toAuthSessionRecord(decodedRow))))
9634
+ onSome: (row) => decodeAuthPairingLinkDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthPairingLinkRepository.consumeAvailable:decodeRow", cause, { pairingLinkId: row.id })), Effect.map(Option.some))
9635
+ })));
9636
+ const listActive = (input) => listActivePairingLinkRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthPairingLinkRepository.listActive:query", "AuthPairingLinkRepository.listActive:decodeRows")), Effect.flatMap((rows) => Effect.forEach(rows, (row) => decodeAuthPairingLinkDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthPairingLinkRepository.listActive:decodeRows", cause, { pairingLinkId: row.id }))))));
9637
+ const revoke = (input) => revokePairingLinkRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthPairingLinkRepository.revoke:query", "AuthPairingLinkRepository.revoke:decodeRows", { pairingLinkId: input.id })), Effect.map((rows) => rows.length > 0));
9638
+ const getByCredential = (input) => getPairingLinkRowByCredential(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthPairingLinkRepository.getByCredential:query", "AuthPairingLinkRepository.getByCredential:decodeRow")), Effect.flatMap((rowOption) => Option.match(rowOption, {
9639
+ onNone: () => Effect.succeed(Option.none()),
9640
+ onSome: (row) => decodeAuthPairingLinkDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthPairingLinkRepository.getByCredential:decodeRow", cause, { pairingLinkId: row.id })), Effect.map(Option.some))
9340
9641
  })));
9341
- const listActive = (input) => listActiveSessionRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthSessionRepository.listActive:query", "AuthSessionRepository.listActive:decodeRows")), Effect.flatMap((rows) => Effect.forEach(rows, (row) => decodeAuthSessionDbRow(row).pipe(Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError("AuthSessionRepository.listActive:decodeRows", cause, { sessionId: row.sessionId })), Effect.map(toAuthSessionRecord)))));
9342
- const revoke = (input) => revokeSessionRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthSessionRepository.revoke:query", "AuthSessionRepository.revoke:decodeRows", { sessionId: input.sessionId })), Effect.map((rows) => rows.length > 0));
9343
- const revokeAllExcept = (input) => revokeOtherSessionRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthSessionRepository.revokeAllExcept:query", "AuthSessionRepository.revokeAllExcept:decodeRows", { currentSessionId: input.currentSessionId })), Effect.map((rows) => rows.map((row) => row.sessionId)));
9344
- const setLastConnectedAt = (input) => setLastConnectedAtRow(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$5("AuthSessionRepository.setLastConnectedAt:query", "AuthSessionRepository.setLastConnectedAt:encodeRequest", { sessionId: input.sessionId })));
9345
9642
  return {
9346
9643
  create,
9347
- getById,
9644
+ consumeAvailable,
9348
9645
  listActive,
9349
9646
  revoke,
9350
- revokeAllExcept,
9351
- setLastConnectedAt
9647
+ getByCredential
9352
9648
  };
9353
9649
  });
9354
- const layer$60 = Layer.effect(AuthSessionRepository, make$64);
9650
+ const layer$60 = Layer.effect(AuthPairingLinkRepository, make$64);
9355
9651
  //#endregion
9356
- //#region src/auth/SessionStore.ts
9357
- var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
9358
- get message() {
9359
- return "Malformed session token.";
9360
- }
9361
- };
9362
- var InvalidSessionTokenSignatureError = class extends Schema$1.TaggedErrorClass()("InvalidSessionTokenSignatureError", {}) {
9363
- get message() {
9364
- return "Invalid session token signature.";
9365
- }
9366
- };
9367
- var InvalidSessionTokenPayloadError = class extends Schema$1.TaggedErrorClass()("InvalidSessionTokenPayloadError", { cause: Schema$1.Defect() }) {
9368
- get message() {
9369
- return "Invalid session token payload.";
9370
- }
9371
- };
9372
- var SessionTokenExpiredError = class extends Schema$1.TaggedErrorClass()("SessionTokenExpiredError", {
9373
- sessionId: AuthSessionId,
9374
- expiresAt: Schema$1.DateTimeUtc,
9375
- observedAt: Schema$1.DateTimeUtc
9376
- }) {
9377
- get message() {
9378
- return "Session token expired.";
9379
- }
9380
- };
9381
- var UnknownSessionTokenError = class extends Schema$1.TaggedErrorClass()("UnknownSessionTokenError", { sessionId: AuthSessionId }) {
9382
- get message() {
9383
- return "Unknown session token.";
9384
- }
9385
- };
9386
- var SessionTokenRevokedError = class extends Schema$1.TaggedErrorClass()("SessionTokenRevokedError", {
9387
- sessionId: AuthSessionId,
9388
- revokedAt: Schema$1.DateTimeUtc
9389
- }) {
9390
- get message() {
9391
- return "Session token revoked.";
9392
- }
9393
- };
9394
- var InvalidSessionExpirationClaimError = class extends Schema$1.TaggedErrorClass()("InvalidSessionExpirationClaimError", {
9395
- sessionId: AuthSessionId,
9396
- expirationClaim: Schema$1.Number
9397
- }) {
9398
- get message() {
9399
- return "Invalid `exp` claim";
9400
- }
9401
- };
9402
- var MalformedWebSocketTokenError = class extends Schema$1.TaggedErrorClass()("MalformedWebSocketTokenError", {}) {
9403
- get message() {
9404
- return "Malformed websocket token.";
9405
- }
9406
- };
9407
- var InvalidWebSocketTokenSignatureError = class extends Schema$1.TaggedErrorClass()("InvalidWebSocketTokenSignatureError", {}) {
9408
- get message() {
9409
- return "Invalid websocket token signature.";
9410
- }
9411
- };
9412
- var InvalidWebSocketTokenPayloadError = class extends Schema$1.TaggedErrorClass()("InvalidWebSocketTokenPayloadError", { cause: Schema$1.Defect() }) {
9413
- get message() {
9414
- return "Invalid websocket token payload.";
9415
- }
9416
- };
9417
- var WebSocketTokenExpiredError = class extends Schema$1.TaggedErrorClass()("WebSocketTokenExpiredError", {
9418
- sessionId: AuthSessionId,
9419
- expiresAt: Schema$1.DateTimeUtc,
9420
- observedAt: Schema$1.DateTimeUtc
9421
- }) {
9652
+ //#region src/auth/PairingGrantStore.ts
9653
+ var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
9422
9654
  get message() {
9423
- return "Websocket token expired.";
9655
+ return "Unknown bootstrap credential.";
9424
9656
  }
9425
9657
  };
9426
- var UnknownWebSocketSessionError = class extends Schema$1.TaggedErrorClass()("UnknownWebSocketSessionError", { sessionId: AuthSessionId }) {
9658
+ var ExpiredBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("ExpiredBootstrapCredentialError", {}) {
9427
9659
  get message() {
9428
- return "Unknown websocket session.";
9660
+ return "Bootstrap credential expired.";
9429
9661
  }
9430
9662
  };
9431
- var WebSocketSessionExpiredError = class extends Schema$1.TaggedErrorClass()("WebSocketSessionExpiredError", {
9432
- sessionId: AuthSessionId,
9433
- expiresAt: Schema$1.DateTimeUtc,
9434
- observedAt: Schema$1.DateTimeUtc
9435
- }) {
9663
+ var BootstrapCredentialProofKeyMismatchError = class extends Schema$1.TaggedErrorClass()("BootstrapCredentialProofKeyMismatchError", {}) {
9436
9664
  get message() {
9437
- return "Websocket session expired.";
9665
+ return "Bootstrap credential proof key mismatch.";
9438
9666
  }
9439
9667
  };
9440
- var WebSocketSessionRevokedError = class extends Schema$1.TaggedErrorClass()("WebSocketSessionRevokedError", {
9441
- sessionId: AuthSessionId,
9442
- revokedAt: Schema$1.DateTimeUtc
9443
- }) {
9668
+ var UnavailableBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnavailableBootstrapCredentialError", {}) {
9444
9669
  get message() {
9445
- return "Websocket session revoked.";
9670
+ return "Bootstrap credential is no longer available.";
9446
9671
  }
9447
9672
  };
9448
- const SessionCredentialInvalidError = Schema$1.Union([
9449
- MalformedSessionTokenError,
9450
- InvalidSessionTokenSignatureError,
9451
- InvalidSessionTokenPayloadError,
9452
- SessionTokenExpiredError,
9453
- UnknownSessionTokenError,
9454
- SessionTokenRevokedError,
9455
- InvalidSessionExpirationClaimError,
9456
- MalformedWebSocketTokenError,
9457
- InvalidWebSocketTokenSignatureError,
9458
- InvalidWebSocketTokenPayloadError,
9459
- WebSocketTokenExpiredError,
9460
- UnknownWebSocketSessionError,
9461
- WebSocketSessionExpiredError,
9462
- WebSocketSessionRevokedError
9673
+ const BootstrapCredentialInvalidError = Schema$1.Union([
9674
+ UnknownBootstrapCredentialError,
9675
+ ExpiredBootstrapCredentialError,
9676
+ BootstrapCredentialProofKeyMismatchError,
9677
+ UnavailableBootstrapCredentialError
9463
9678
  ]);
9464
- const isSessionCredentialInvalidError = Schema$1.is(SessionCredentialInvalidError);
9465
- const sessionCredentialInternalErrorContext = { cause: Schema$1.Defect() };
9466
- var SessionClaimsEncodingError = class extends Schema$1.TaggedErrorClass()("SessionClaimsEncodingError", {
9467
- sessionId: AuthSessionId,
9468
- operation: Schema$1.Literals(["encode_session_claims", "encode_websocket_claims"]),
9469
- ...sessionCredentialInternalErrorContext
9470
- }) {
9471
- get message() {
9472
- return "Failed to encode claims";
9473
- }
9474
- };
9475
- var SessionCredentialIssueError = class extends Schema$1.TaggedErrorClass()("SessionCredentialIssueError", {
9476
- sessionId: Schema$1.optional(AuthSessionId),
9477
- ...sessionCredentialInternalErrorContext
9478
- }) {
9679
+ Schema$1.is(BootstrapCredentialInvalidError);
9680
+ var ActivePairingLinksLoadError = class extends Schema$1.TaggedErrorClass()("ActivePairingLinksLoadError", { cause: Schema$1.Defect() }) {
9479
9681
  get message() {
9480
- return "Failed to issue session credential.";
9682
+ return "Failed to load active pairing links.";
9481
9683
  }
9482
9684
  };
9483
- var SessionCredentialVerificationError = class extends Schema$1.TaggedErrorClass()("SessionCredentialVerificationError", {
9484
- sessionId: AuthSessionId,
9485
- ...sessionCredentialInternalErrorContext
9685
+ var PairingLinkRevokeError = class extends Schema$1.TaggedErrorClass()("PairingLinkRevokeError", {
9686
+ pairingLinkId: Schema$1.String,
9687
+ cause: Schema$1.Defect()
9486
9688
  }) {
9487
9689
  get message() {
9488
- return "Failed to verify session credential.";
9690
+ return `Failed to revoke pairing link '${this.pairingLinkId}'.`;
9489
9691
  }
9490
9692
  };
9491
- var WebSocketTokenIssueError = class extends Schema$1.TaggedErrorClass()("WebSocketTokenIssueError", {
9492
- sessionId: AuthSessionId,
9493
- ...sessionCredentialInternalErrorContext
9693
+ var PairingCredentialIssueError = class extends Schema$1.TaggedErrorClass()("PairingCredentialIssueError", {
9694
+ pairingLinkId: Schema$1.String,
9695
+ subject: Schema$1.String,
9696
+ label: Schema$1.optional(Schema$1.String),
9697
+ cause: Schema$1.Defect()
9494
9698
  }) {
9495
9699
  get message() {
9496
- return "Failed to issue websocket token.";
9700
+ return `Failed to issue pairing credential '${this.pairingLinkId}' for '${this.subject}'.`;
9497
9701
  }
9498
9702
  };
9499
- var WebSocketTokenVerificationError = class extends Schema$1.TaggedErrorClass()("WebSocketTokenVerificationError", {
9500
- sessionId: AuthSessionId,
9501
- ...sessionCredentialInternalErrorContext
9703
+ var PairingCredentialRandomGenerationError = class extends Schema$1.TaggedErrorClass()("PairingCredentialRandomGenerationError", {
9704
+ operation: Schema$1.Literals(["generate-id", "generate-token"]),
9705
+ cause: Schema$1.Defect()
9502
9706
  }) {
9503
9707
  get message() {
9504
- return "Failed to verify websocket token.";
9708
+ return `Failed to generate pairing credential data during '${this.operation}'.`;
9505
9709
  }
9506
9710
  };
9507
- var ActiveSessionsListError = class extends Schema$1.TaggedErrorClass()("ActiveSessionsListError", { ...sessionCredentialInternalErrorContext }) {
9711
+ var BootstrapCredentialConsumeError = class extends Schema$1.TaggedErrorClass()("BootstrapCredentialConsumeError", { cause: Schema$1.Defect() }) {
9508
9712
  get message() {
9509
- return "Failed to list active sessions.";
9713
+ return "Failed to consume bootstrap credential.";
9510
9714
  }
9511
9715
  };
9512
- var SessionRevocationError = class extends Schema$1.TaggedErrorClass()("SessionRevocationError", {
9513
- sessionId: AuthSessionId,
9514
- ...sessionCredentialInternalErrorContext
9515
- }) {
9716
+ var BootstrapCredentialConsumeAvailableError = class extends Schema$1.TaggedErrorClass()("BootstrapCredentialConsumeAvailableError", { cause: Schema$1.Defect() }) {
9516
9717
  get message() {
9517
- return "Failed to revoke session.";
9718
+ return "Failed to atomically consume an available bootstrap credential.";
9518
9719
  }
9519
9720
  };
9520
- var OtherSessionsRevocationError = class extends Schema$1.TaggedErrorClass()("OtherSessionsRevocationError", {
9521
- currentSessionId: AuthSessionId,
9522
- ...sessionCredentialInternalErrorContext
9523
- }) {
9721
+ var BootstrapCredentialLookupError = class extends Schema$1.TaggedErrorClass()("BootstrapCredentialLookupError", { cause: Schema$1.Defect() }) {
9524
9722
  get message() {
9525
- return "Failed to revoke other sessions.";
9723
+ return "Failed to look up bootstrap credential state.";
9526
9724
  }
9527
9725
  };
9528
- const SessionCredentialInternalError = Schema$1.Union([
9529
- SessionClaimsEncodingError,
9530
- SessionCredentialIssueError,
9531
- SessionCredentialVerificationError,
9532
- WebSocketTokenIssueError,
9533
- WebSocketTokenVerificationError,
9534
- ActiveSessionsListError,
9535
- SessionRevocationError,
9536
- OtherSessionsRevocationError
9537
- ]);
9538
- Schema$1.is(SessionCredentialInternalError);
9539
- const SessionCredentialError = Schema$1.Union([SessionCredentialInvalidError, SessionCredentialInternalError]);
9540
- Schema$1.is(SessionCredentialError);
9541
- var SessionStore = class extends Context.Service()("@p4code/cli/auth/SessionStore") {};
9542
- const SIGNING_SECRET_NAME$1 = "server-signing-key";
9543
- const DEFAULT_SESSION_TTL = Duration.days(30);
9544
- const DEFAULT_WEBSOCKET_TOKEN_TTL = Duration.minutes(5);
9545
- const SessionClaims = Schema$1.Struct({
9546
- v: Schema$1.Literal(1),
9547
- kind: Schema$1.Literal("session"),
9548
- sid: AuthSessionId,
9549
- sub: Schema$1.String,
9550
- scopes: AuthEnvironmentScopes,
9551
- method: Schema$1.Literals(["browser-session-cookie", "bearer-access-token"]),
9552
- jkt: Schema$1.optionalKey(Schema$1.String),
9553
- iat: Schema$1.Number,
9554
- exp: Schema$1.Number
9555
- });
9556
- const WebSocketClaims = Schema$1.Struct({
9557
- v: Schema$1.Literal(1),
9558
- kind: Schema$1.Literal("websocket"),
9559
- sid: AuthSessionId,
9560
- iat: Schema$1.Number,
9561
- exp: Schema$1.Number
9562
- });
9563
- const decodeSessionClaims = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(SessionClaims));
9564
- const decodeWebSocketClaims = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(WebSocketClaims));
9565
- function createDefaultClientMetadata() {
9566
- return { deviceType: "unknown" };
9567
- }
9568
- function toClientMetadata(record) {
9569
- return {
9570
- ...record.label ? { label: record.label } : {},
9571
- ...record.ipAddress ? { ipAddress: record.ipAddress } : {},
9572
- ...record.userAgent ? { userAgent: record.userAgent } : {},
9573
- deviceType: record.deviceType,
9574
- ...record.os ? { os: record.os } : {},
9575
- ...record.browser ? { browser: record.browser } : {}
9576
- };
9577
- }
9578
- function toAuthClientSession(input) {
9579
- return {
9580
- ...input,
9581
- current: false
9582
- };
9583
- }
9726
+ const BootstrapCredentialInternalError = Schema$1.Union([
9727
+ ActivePairingLinksLoadError,
9728
+ PairingLinkRevokeError,
9729
+ PairingCredentialIssueError,
9730
+ PairingCredentialRandomGenerationError,
9731
+ BootstrapCredentialConsumeError,
9732
+ BootstrapCredentialConsumeAvailableError,
9733
+ BootstrapCredentialLookupError
9734
+ ]);
9735
+ const isBootstrapCredentialInternalError = Schema$1.is(BootstrapCredentialInternalError);
9736
+ const BootstrapCredentialError = Schema$1.Union([BootstrapCredentialInvalidError, BootstrapCredentialInternalError]);
9737
+ Schema$1.is(BootstrapCredentialError);
9738
+ var PairingGrantStore = class extends Context.Service()("@p4code/cli/auth/PairingGrantStore") {};
9739
+ const DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES = Duration.minutes(5);
9740
+ const DESKTOP_BOOTSTRAP_TTL_HOURS = Duration.hours(24);
9741
+ const DEV_STARTUP_TTL_HOURS = Duration.hours(24);
9742
+ const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
9743
+ const PAIRING_TOKEN_LENGTH = 12;
9744
+ const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
9584
9745
  const make$63 = Effect.gen(function* () {
9585
9746
  const crypto = yield* Crypto.Crypto;
9586
- const serverConfig = yield* ServerConfig$1;
9587
- const secretStore = yield* ServerSecretStore;
9588
- const authSessions = yield* AuthSessionRepository;
9589
- const signingSecret = yield* secretStore.getOrCreateRandom(SIGNING_SECRET_NAME$1, 32);
9590
- const connectedSessionsRef = yield* Ref.make(/* @__PURE__ */ new Map());
9747
+ const config = yield* ServerConfig$1;
9748
+ const pairingLinks = yield* AuthPairingLinkRepository;
9749
+ const seededGrantsRef = yield* Ref.make(/* @__PURE__ */ new Map());
9591
9750
  const changesPubSub = yield* PubSub.unbounded();
9592
- const cookieName = resolveSessionCookieName({
9593
- mode: serverConfig.mode,
9594
- port: serverConfig.port,
9595
- host: serverConfig.host,
9596
- instanceKey: serverConfig.stateDir,
9597
- development: serverConfig.devUrl !== void 0
9598
- });
9599
- const emitUpsert = (clientSession) => PubSub.publish(changesPubSub, {
9600
- type: "clientUpserted",
9601
- clientSession
9602
- }).pipe(Effect.asVoid);
9603
- const emitRemoved = (sessionId) => PubSub.publish(changesPubSub, {
9604
- type: "clientRemoved",
9605
- sessionId
9606
- }).pipe(Effect.asVoid);
9607
- const loadActiveSession = (sessionId) => Effect.gen(function* () {
9608
- const row = yield* authSessions.getById({ sessionId });
9609
- if (Option.isNone(row) || row.value.revokedAt !== null) return Option.none();
9610
- const connectedSessions = yield* Ref.get(connectedSessionsRef);
9611
- return Option.some(toAuthClientSession({
9612
- sessionId: row.value.sessionId,
9613
- subject: row.value.subject,
9614
- scopes: row.value.scopes,
9615
- method: row.value.method,
9616
- client: toClientMetadata(row.value.client),
9617
- issuedAt: row.value.issuedAt,
9618
- expiresAt: row.value.expiresAt,
9619
- lastConnectedAt: row.value.lastConnectedAt,
9620
- connected: connectedSessions.has(row.value.sessionId)
9621
- }));
9751
+ const generatePairingToken = Effect.gen(function* () {
9752
+ let credential = "";
9753
+ while (credential.length < PAIRING_TOKEN_LENGTH) {
9754
+ const bytes = yield* crypto.randomBytes(PAIRING_TOKEN_LENGTH).pipe(Effect.mapError((cause) => new PairingCredentialRandomGenerationError({
9755
+ operation: "generate-token",
9756
+ cause
9757
+ })));
9758
+ for (const byte of bytes) {
9759
+ if (byte >= PAIRING_TOKEN_REJECTION_LIMIT) continue;
9760
+ credential += PAIRING_TOKEN_ALPHABET[byte % 32];
9761
+ if (credential.length === PAIRING_TOKEN_LENGTH) return credential;
9762
+ }
9763
+ }
9764
+ return credential;
9622
9765
  });
9623
- const markConnected = (sessionId) => Ref.modify(connectedSessionsRef, (current) => {
9624
- const next = new Map(current);
9625
- const wasDisconnected = !next.has(sessionId);
9626
- next.set(sessionId, (next.get(sessionId) ?? 0) + 1);
9627
- return [wasDisconnected, next];
9628
- }).pipe(Effect.flatMap((wasDisconnected) => wasDisconnected ? DateTime.now.pipe(Effect.flatMap((lastConnectedAt) => authSessions.setLastConnectedAt({
9629
- sessionId,
9630
- lastConnectedAt
9631
- }))) : Effect.void), Effect.flatMap(() => loadActiveSession(sessionId)), Effect.flatMap((session) => Option.isSome(session) ? emitUpsert(session.value) : Effect.void), Effect.catchCause((cause) => Effect.logError("Failed to publish connected-session auth update.").pipe(Effect.annotateLogs({
9632
- sessionId,
9633
- cause
9634
- }))), Effect.withSpan("SessionStore.markConnected"));
9635
- const markDisconnected = (sessionId) => Ref.update(connectedSessionsRef, (current) => {
9766
+ const seedGrant = (credential, grant) => Ref.update(seededGrantsRef, (current) => {
9636
9767
  const next = new Map(current);
9637
- const remaining = (next.get(sessionId) ?? 0) - 1;
9638
- if (remaining > 0) next.set(sessionId, remaining);
9639
- else next.delete(sessionId);
9768
+ next.set(credential, grant);
9640
9769
  return next;
9641
- }).pipe(Effect.flatMap(() => loadActiveSession(sessionId)), Effect.flatMap((session) => Option.isSome(session) ? emitUpsert(session.value) : Effect.void), Effect.catchCause((cause) => Effect.logError("Failed to publish disconnected-session auth update.").pipe(Effect.annotateLogs({
9642
- sessionId,
9643
- cause
9644
- }))), Effect.withSpan("SessionStore.markDisconnected"));
9645
- const encodeClaims = Schema$1.encodeEffect(Schema$1.fromJsonString(SessionClaims));
9646
- const issue = Effect.fn("SessionStore.issue")(function* (input) {
9647
- const sessionId = AuthSessionId.make(yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new SessionCredentialIssueError({ cause }))));
9648
- const issuedAt = yield* DateTime.now;
9649
- const expiresAt = DateTime.add(issuedAt, { milliseconds: Duration.toMillis(input?.ttl ?? DEFAULT_SESSION_TTL) });
9650
- const claims = {
9651
- v: 1,
9652
- kind: "session",
9653
- sid: sessionId,
9654
- sub: input?.subject ?? "browser",
9655
- scopes: input?.scopes ?? AuthStandardClientScopes,
9656
- method: input?.method ?? "browser-session-cookie",
9657
- ...input?.proofKeyThumbprint ? { jkt: input.proofKeyThumbprint } : {},
9658
- iat: issuedAt.epochMilliseconds,
9659
- exp: expiresAt.epochMilliseconds
9660
- };
9661
- const encodedPayload = yield* encodeClaims(claims).pipe(Effect.map(base64UrlEncode), Effect.mapError((cause) => new SessionCredentialIssueError({
9662
- sessionId,
9663
- cause: new SessionClaimsEncodingError({
9664
- sessionId,
9665
- operation: "encode_session_claims",
9666
- cause
9667
- })
9668
- })));
9669
- const signature = signPayload(encodedPayload, signingSecret);
9670
- const client = input?.client ?? createDefaultClientMetadata();
9671
- yield* authSessions.create({
9672
- sessionId,
9673
- subject: claims.sub,
9674
- scopes: claims.scopes,
9675
- method: claims.method,
9676
- client: {
9677
- label: client.label ?? null,
9678
- ipAddress: client.ipAddress ?? null,
9679
- userAgent: client.userAgent ?? null,
9680
- deviceType: client.deviceType,
9681
- os: client.os ?? null,
9682
- browser: client.browser ?? null
9683
- },
9684
- issuedAt,
9685
- expiresAt
9686
- }).pipe(Effect.mapError((cause) => new SessionCredentialIssueError({
9687
- sessionId,
9688
- cause
9689
- })));
9690
- yield* emitUpsert(toAuthClientSession({
9691
- sessionId,
9692
- subject: claims.sub,
9693
- scopes: claims.scopes,
9694
- method: claims.method,
9695
- client,
9696
- issuedAt,
9697
- expiresAt,
9698
- lastConnectedAt: null,
9699
- connected: false
9700
- }));
9701
- return {
9702
- sessionId,
9703
- token: `${encodedPayload}.${signature}`,
9704
- method: claims.method,
9705
- client,
9706
- expiresAt,
9707
- scopes: claims.scopes,
9708
- ...claims.jkt ? { proofKeyThumbprint: claims.jkt } : {}
9709
- };
9710
9770
  });
9711
- const verify = Effect.fn("SessionStore.verify")(function* (token) {
9712
- const [encodedPayload, signature] = token.split(".");
9713
- if (!encodedPayload || !signature) return yield* new MalformedSessionTokenError({});
9714
- if (!timingSafeEqualBase64Url(signature, signPayload(encodedPayload, signingSecret))) return yield* new InvalidSessionTokenSignatureError({});
9715
- const claims = yield* decodeSessionClaims(base64UrlDecodeUtf8(encodedPayload)).pipe(Effect.mapError((cause) => new InvalidSessionTokenPayloadError({ cause })));
9716
- const observedAt = yield* DateTime.now;
9717
- const expiresAt = DateTime.make(claims.exp);
9718
- if (Option.isNone(expiresAt)) return yield* new InvalidSessionExpirationClaimError({
9719
- sessionId: claims.sid,
9720
- expirationClaim: claims.exp
9771
+ const emitUpsert = (pairingLink) => PubSub.publish(changesPubSub, {
9772
+ type: "pairingLinkUpserted",
9773
+ pairingLink
9774
+ }).pipe(Effect.asVoid);
9775
+ const emitRemoved = (id) => PubSub.publish(changesPubSub, {
9776
+ type: "pairingLinkRemoved",
9777
+ id
9778
+ }).pipe(Effect.asVoid);
9779
+ if (config.desktopBootstrapToken) {
9780
+ const now = yield* DateTime.now;
9781
+ yield* seedGrant(config.desktopBootstrapToken, {
9782
+ method: "desktop-bootstrap",
9783
+ scopes: AuthAdministrativeScopes,
9784
+ subject: DESKTOP_BOOTSTRAP_SUBJECT,
9785
+ expiresAt: DateTime.add(now, { milliseconds: Duration.toMillis(DESKTOP_BOOTSTRAP_TTL_HOURS) }),
9786
+ remainingUses: "unbounded"
9721
9787
  });
9722
- if (claims.exp <= observedAt.epochMilliseconds) return yield* new SessionTokenExpiredError({
9723
- sessionId: claims.sid,
9724
- expiresAt: expiresAt.value,
9725
- observedAt
9788
+ }
9789
+ const listActive = Effect.fn("PairingGrantStore.listActive")(function* () {
9790
+ const now = yield* DateTime.now;
9791
+ return (yield* pairingLinks.listActive({ now })).map((row) => row.label ? {
9792
+ id: row.id,
9793
+ credential: row.credential,
9794
+ scopes: row.scopes,
9795
+ subject: row.subject,
9796
+ label: row.label,
9797
+ createdAt: row.createdAt,
9798
+ expiresAt: row.expiresAt
9799
+ } : {
9800
+ id: row.id,
9801
+ credential: row.credential,
9802
+ scopes: row.scopes,
9803
+ subject: row.subject,
9804
+ createdAt: row.createdAt,
9805
+ expiresAt: row.expiresAt
9726
9806
  });
9727
- const row = yield* authSessions.getById({ sessionId: claims.sid }).pipe(Effect.mapError((cause) => new SessionCredentialVerificationError({
9728
- sessionId: claims.sid,
9807
+ }, Effect.mapError((cause) => new ActivePairingLinksLoadError({ cause })));
9808
+ const revoke = Effect.fn("PairingGrantStore.revoke")(function* (id) {
9809
+ const revokedAt = yield* DateTime.now;
9810
+ const revoked = yield* pairingLinks.revoke({
9811
+ id,
9812
+ revokedAt
9813
+ }).pipe(Effect.mapError((cause) => new PairingLinkRevokeError({
9814
+ pairingLinkId: id,
9729
9815
  cause
9730
9816
  })));
9731
- if (Option.isNone(row)) return yield* new UnknownSessionTokenError({ sessionId: claims.sid });
9732
- if (row.value.revokedAt !== null) return yield* new SessionTokenRevokedError({
9733
- sessionId: claims.sid,
9734
- revokedAt: row.value.revokedAt
9735
- });
9736
- return {
9737
- sessionId: claims.sid,
9738
- token,
9739
- method: claims.method,
9740
- client: toClientMetadata(row.value.client),
9741
- expiresAt: expiresAt.value,
9742
- subject: claims.sub,
9743
- scopes: claims.scopes,
9744
- ...claims.jkt ? { proofKeyThumbprint: claims.jkt } : {}
9745
- };
9817
+ if (revoked) yield* emitRemoved(id);
9818
+ return revoked;
9746
9819
  });
9747
- const encodeWsClaims = Schema$1.encodeEffect(Schema$1.fromJsonString(WebSocketClaims));
9748
- const issueWebSocketToken = Effect.fn("SessionStore.issueWebSocketToken")(function* (sessionId, input) {
9749
- const issuedAt = yield* DateTime.now;
9750
- const expiresAt = DateTime.add(issuedAt, { milliseconds: Duration.toMillis(input?.ttl ?? DEFAULT_WEBSOCKET_TOKEN_TTL) });
9751
- const claims = {
9752
- v: 1,
9753
- kind: "websocket",
9754
- sid: sessionId,
9755
- iat: issuedAt.epochMilliseconds,
9756
- exp: expiresAt.epochMilliseconds
9757
- };
9758
- const encodedPayload = yield* encodeWsClaims(claims).pipe(Effect.map(base64UrlEncode), Effect.mapError((cause) => new WebSocketTokenIssueError({
9759
- sessionId,
9760
- cause: new SessionClaimsEncodingError({
9761
- sessionId,
9762
- operation: "encode_websocket_claims",
9763
- cause
9764
- })
9820
+ const issueOneTimeToken = Effect.fn("PairingGrantStore.issueOneTimeToken")(function* (input) {
9821
+ const id = yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new PairingCredentialRandomGenerationError({
9822
+ operation: "generate-id",
9823
+ cause
9765
9824
  })));
9766
- return {
9767
- token: `${encodedPayload}.${signPayload(encodedPayload, signingSecret)}`,
9825
+ const credential = yield* generatePairingToken;
9826
+ const isDevStartupToken = config.devUrl !== void 0 && input?.purpose === "startup";
9827
+ const ttl = input?.ttl ?? (isDevStartupToken ? DEV_STARTUP_TTL_HOURS : DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES);
9828
+ const now = yield* DateTime.now;
9829
+ const expiresAt = DateTime.add(now, { milliseconds: Duration.toMillis(ttl) });
9830
+ const issued = {
9831
+ id,
9832
+ credential,
9833
+ ...input?.label ? { label: input.label } : {},
9834
+ ...input?.proofKeyThumbprint ? { proofKeyThumbprint: input.proofKeyThumbprint } : {},
9768
9835
  expiresAt
9769
9836
  };
9770
- });
9771
- const verifyWebSocketToken = Effect.fn("SessionStore.verifyWebSocketToken")(function* (token) {
9772
- const [encodedPayload, signature] = token.split(".");
9773
- if (!encodedPayload || !signature) return yield* new MalformedWebSocketTokenError({});
9774
- if (!timingSafeEqualBase64Url(signature, signPayload(encodedPayload, signingSecret))) return yield* new InvalidWebSocketTokenSignatureError({});
9775
- const claims = yield* decodeWebSocketClaims(base64UrlDecodeUtf8(encodedPayload)).pipe(Effect.mapError((cause) => new InvalidWebSocketTokenPayloadError({ cause })));
9776
- const observedAt = yield* DateTime.now;
9777
- const expiresAt = DateTime.make(claims.exp);
9778
- if (Option.isNone(expiresAt)) return yield* new InvalidSessionExpirationClaimError({
9779
- sessionId: claims.sid,
9780
- expirationClaim: claims.exp
9781
- });
9782
- if (claims.exp <= observedAt.epochMilliseconds) return yield* new WebSocketTokenExpiredError({
9783
- sessionId: claims.sid,
9784
- expiresAt: expiresAt.value,
9785
- observedAt
9786
- });
9787
- const row = yield* authSessions.getById({ sessionId: claims.sid }).pipe(Effect.mapError((cause) => new WebSocketTokenVerificationError({
9788
- sessionId: claims.sid,
9837
+ const subject = input?.subject ?? "one-time-token";
9838
+ yield* pairingLinks.create({
9839
+ id,
9840
+ credential,
9841
+ method: "one-time-token",
9842
+ scopes: input?.scopes ?? AuthStandardClientScopes,
9843
+ subject,
9844
+ label: input?.label ?? null,
9845
+ proofKeyThumbprint: input?.proofKeyThumbprint ?? null,
9846
+ createdAt: now,
9847
+ expiresAt
9848
+ }).pipe(Effect.mapError((cause) => new PairingCredentialIssueError({
9849
+ pairingLinkId: id,
9850
+ subject,
9851
+ ...input?.label ? { label: input.label } : {},
9789
9852
  cause
9790
9853
  })));
9791
- if (Option.isNone(row)) return yield* new UnknownWebSocketSessionError({ sessionId: claims.sid });
9792
- if (row.value.expiresAt.epochMilliseconds <= observedAt.epochMilliseconds) return yield* new WebSocketSessionExpiredError({
9793
- sessionId: claims.sid,
9794
- expiresAt: row.value.expiresAt,
9795
- observedAt
9796
- });
9797
- if (row.value.revokedAt !== null) return yield* new WebSocketSessionRevokedError({
9798
- sessionId: claims.sid,
9799
- revokedAt: row.value.revokedAt
9854
+ yield* emitUpsert({
9855
+ id,
9856
+ credential,
9857
+ scopes: input?.scopes ?? AuthStandardClientScopes,
9858
+ subject: input?.subject ?? "one-time-token",
9859
+ ...input?.label ? { label: input.label } : {},
9860
+ createdAt: now,
9861
+ expiresAt
9800
9862
  });
9801
- return {
9802
- sessionId: row.value.sessionId,
9803
- token,
9804
- method: row.value.method,
9805
- client: toClientMetadata(row.value.client),
9806
- expiresAt: row.value.expiresAt,
9807
- subject: row.value.subject,
9808
- scopes: row.value.scopes
9809
- };
9863
+ return issued;
9810
9864
  });
9811
- const listActive = Effect.fn("SessionStore.listActive")(function* () {
9865
+ const consume = Effect.fn("PairingGrantStore.consume")(function* (credential, input) {
9812
9866
  const now = yield* DateTime.now;
9813
- const connectedSessions = yield* Ref.get(connectedSessionsRef);
9814
- return (yield* authSessions.listActive({ now })).map((row) => toAuthClientSession({
9815
- sessionId: row.sessionId,
9816
- subject: row.subject,
9817
- scopes: row.scopes,
9818
- method: row.method,
9819
- client: toClientMetadata(row.client),
9820
- issuedAt: row.issuedAt,
9821
- expiresAt: row.expiresAt,
9822
- lastConnectedAt: row.lastConnectedAt,
9823
- connected: connectedSessions.has(row.sessionId)
9824
- }));
9825
- }, Effect.mapError((cause) => new ActiveSessionsListError({ cause })));
9826
- const revoke = Effect.fn("SessionStore.revoke")(function* (sessionId) {
9827
- const revokedAt = yield* DateTime.now;
9828
- const revoked = yield* authSessions.revoke({
9829
- sessionId,
9830
- revokedAt
9831
- }).pipe(Effect.mapError((cause) => new SessionRevocationError({
9832
- sessionId,
9833
- cause
9834
- })));
9835
- if (revoked) {
9836
- yield* Ref.update(connectedSessionsRef, (current) => {
9837
- const next = new Map(current);
9838
- next.delete(sessionId);
9839
- return next;
9840
- });
9841
- yield* emitRemoved(sessionId);
9842
- }
9843
- return revoked;
9844
- });
9845
- const revokeAllExcept = Effect.fn("SessionStore.revokeAllExcept")(function* (sessionId) {
9846
- const revokedAt = yield* DateTime.now;
9847
- const revokedSessionIds = yield* authSessions.revokeAllExcept({
9848
- currentSessionId: sessionId,
9849
- revokedAt
9850
- }).pipe(Effect.mapError((cause) => new OtherSessionsRevocationError({
9851
- currentSessionId: sessionId,
9852
- cause
9853
- })));
9854
- if (revokedSessionIds.length > 0) {
9855
- yield* Ref.update(connectedSessionsRef, (current) => {
9856
- const next = new Map(current);
9857
- for (const revokedSessionId of revokedSessionIds) next.delete(revokedSessionId);
9858
- return next;
9859
- });
9860
- yield* Effect.forEach(revokedSessionIds, (revokedSessionId) => emitRemoved(revokedSessionId), {
9861
- concurrency: "unbounded",
9862
- discard: true
9867
+ const seededResult = yield* Ref.modify(seededGrantsRef, (current) => {
9868
+ const grant = current.get(credential);
9869
+ if (!grant) return [{
9870
+ _tag: "error",
9871
+ reason: "not-found",
9872
+ error: new UnknownBootstrapCredentialError({})
9873
+ }, current];
9874
+ const next = new Map(current);
9875
+ if (DateTime.isGreaterThanOrEqualTo(now, grant.expiresAt)) {
9876
+ next.delete(credential);
9877
+ return [{
9878
+ _tag: "error",
9879
+ reason: "expired",
9880
+ error: new ExpiredBootstrapCredentialError({})
9881
+ }, next];
9882
+ }
9883
+ if (grant.proofKeyThumbprint && grant.proofKeyThumbprint !== input?.proofKeyThumbprint) return [{
9884
+ _tag: "error",
9885
+ reason: "not-found",
9886
+ error: new BootstrapCredentialProofKeyMismatchError({})
9887
+ }, next];
9888
+ const remainingUses = grant.remainingUses;
9889
+ if (typeof remainingUses === "number") if (remainingUses <= 1) next.delete(credential);
9890
+ else next.set(credential, {
9891
+ ...grant,
9892
+ remainingUses: remainingUses - 1
9863
9893
  });
9894
+ return [{
9895
+ _tag: "success",
9896
+ grant: {
9897
+ method: grant.method,
9898
+ scopes: grant.scopes,
9899
+ subject: grant.subject,
9900
+ ...grant.label ? { label: grant.label } : {},
9901
+ ...grant.proofKeyThumbprint ? { proofKeyThumbprint: grant.proofKeyThumbprint } : {},
9902
+ expiresAt: grant.expiresAt
9903
+ }
9904
+ }, next];
9905
+ });
9906
+ if (seededResult._tag === "success") return seededResult.grant;
9907
+ if (seededResult.reason !== "not-found") return yield* seededResult.error;
9908
+ const consumed = yield* pairingLinks.consumeAvailable({
9909
+ credential,
9910
+ proofKeyThumbprint: input?.proofKeyThumbprint ?? null,
9911
+ consumedAt: now,
9912
+ now
9913
+ }).pipe(Effect.mapError((cause) => new BootstrapCredentialConsumeAvailableError({ cause })));
9914
+ if (Option.isSome(consumed)) {
9915
+ yield* emitRemoved(consumed.value.id);
9916
+ return {
9917
+ method: consumed.value.method,
9918
+ scopes: consumed.value.scopes,
9919
+ subject: consumed.value.subject,
9920
+ ...consumed.value.label ? { label: consumed.value.label } : {},
9921
+ ...consumed.value.proofKeyThumbprint ? { proofKeyThumbprint: consumed.value.proofKeyThumbprint } : {},
9922
+ expiresAt: consumed.value.expiresAt
9923
+ };
9864
9924
  }
9865
- return revokedSessionIds.length;
9925
+ const matching = yield* pairingLinks.getByCredential({ credential }).pipe(Effect.mapError((cause) => new BootstrapCredentialLookupError({ cause })));
9926
+ if (Option.isNone(matching)) return yield* new UnknownBootstrapCredentialError({});
9927
+ if (matching.value.revokedAt !== null) return yield* new UnavailableBootstrapCredentialError({});
9928
+ if (matching.value.consumedAt !== null) return yield* new UnknownBootstrapCredentialError({});
9929
+ if (DateTime.isGreaterThanOrEqualTo(now, matching.value.expiresAt)) return yield* new ExpiredBootstrapCredentialError({});
9930
+ if (matching.value.proofKeyThumbprint !== null && matching.value.proofKeyThumbprint !== input?.proofKeyThumbprint) return yield* new BootstrapCredentialProofKeyMismatchError({});
9931
+ return yield* new UnavailableBootstrapCredentialError({});
9866
9932
  });
9867
- return SessionStore.of({
9868
- cookieName,
9869
- issue,
9870
- verify,
9871
- issueWebSocketToken,
9872
- verifyWebSocketToken,
9933
+ return PairingGrantStore.of({
9934
+ issueOneTimeToken,
9873
9935
  listActive,
9874
9936
  get streamChanges() {
9875
9937
  return Stream.fromPubSub(changesPubSub);
9876
9938
  },
9877
9939
  revoke,
9878
- revokeAllExcept,
9879
- markConnected,
9880
- markDisconnected
9940
+ consume
9881
9941
  });
9882
9942
  });
9883
- const layer$59 = Layer.effect(SessionStore, make$63).pipe(Layer.provideMerge(layer$60));
9943
+ const layer$59 = Layer.effect(PairingGrantStore, make$63).pipe(Layer.provideMerge(layer$60));
9884
9944
  //#endregion
9885
9945
  //#region src/persistence/Migrations/001_OrchestrationEvents.ts
9886
9946
  var _001_OrchestrationEvents_default = Effect.gen(function* () {
@@ -11848,8 +11908,8 @@ const make$62 = Effect.gen(function* () {
11848
11908
  issueStartupPairingUrl
11849
11909
  });
11850
11910
  });
11851
- const layer$58 = Layer.effect(EnvironmentAuth, make$62).pipe(Layer.provideMerge(layer$62), Layer.provideMerge(layer$59), Layer.provideMerge(layer$64));
11852
- const storageLayer = Layer.mergeAll(layer$61, layerConfig);
11911
+ const layer$58 = Layer.effect(EnvironmentAuth, make$62).pipe(Layer.provideMerge(layer$59), Layer.provideMerge(layer$61), Layer.provideMerge(layer$64));
11912
+ const storageLayer = Layer.mergeAll(layer$62, layerConfig);
11853
11913
  const runtimeLayer = layer$58.pipe(Layer.provideMerge(storageLayer));
11854
11914
  //#endregion
11855
11915
  //#region src/cliAuthFormat.ts
@@ -13703,7 +13763,7 @@ const assetNotFound = (name) => HttpServerResponse.jsonUnsafe({
13703
13763
  */
13704
13764
  const rejectOversizedAsset = Effect.fn("HubHttpServer.rejectOversizedAsset")(function* (input) {
13705
13765
  const bytes = agentAssetTotalBytes(input.files);
13706
- if (bytes > 2097152) return yield* new InvalidHubRequest({ detail: `Asset is ${bytes} bytes, over the ${AGENT_ASSET_TOTAL_MAX_BYTES} byte limit.` });
13766
+ if (bytes > 4194304) return yield* new InvalidHubRequest({ detail: `Asset is ${bytes} bytes, over the ${AGENT_ASSET_TOTAL_MAX_BYTES} byte limit.` });
13707
13767
  });
13708
13768
  const listAssetsRoute = HttpRouter.add("GET", "/assets", respondToHubFailures(Effect.gen(function* () {
13709
13769
  yield* authenticateHubRequest();
@@ -14389,7 +14449,8 @@ const validateHubLink = (rawBaseUrl, rawToken) => {
14389
14449
  const StoredHubLink = Schema$1.fromJsonString(Schema$1.Struct({
14390
14450
  baseUrl: Schema$1.String,
14391
14451
  token: Schema$1.String,
14392
- syncMode: Schema$1.optional(Schema$1.Literals(["auto", "paused"]))
14452
+ syncMode: Schema$1.optional(Schema$1.Literals(["auto", "paused"])),
14453
+ shareMode: Schema$1.optional(Schema$1.Literals(["all", "selected"]))
14393
14454
  }));
14394
14455
  const decodeStoredHubLink = Schema$1.decodeUnknownExit(StoredHubLink);
14395
14456
  const encodeStoredHubLink = Schema$1.encodeSync(StoredHubLink);
@@ -14404,11 +14465,13 @@ const parseHubLink = (raw) => {
14404
14465
  const decoded = decodeStoredHubLink(raw);
14405
14466
  if (decoded._tag !== "Success") return {
14406
14467
  settings: Option.none(),
14407
- syncMode: "auto"
14468
+ syncMode: "auto",
14469
+ shareMode: "all"
14408
14470
  };
14409
14471
  return {
14410
14472
  settings: validateHubLink(decoded.value.baseUrl, decoded.value.token),
14411
- syncMode: decoded.value.syncMode ?? "auto"
14473
+ syncMode: decoded.value.syncMode ?? "auto",
14474
+ shareMode: decoded.value.shareMode ?? "all"
14412
14475
  };
14413
14476
  };
14414
14477
  const formatHubLink = (input) => encodeStoredHubLink({
@@ -14420,7 +14483,8 @@ const formatHubLink = (input) => encodeStoredHubLink({
14420
14483
  onNone: () => "",
14421
14484
  onSome: (s) => s.token
14422
14485
  }),
14423
- syncMode: input.syncMode
14486
+ syncMode: input.syncMode,
14487
+ shareMode: input.shareMode
14424
14488
  });
14425
14489
  const fromEnvironment = (environment) => validateHubLink(environment.P4CODE_HUB_URL ?? "", environment.P4CODE_HUB_TOKEN ?? "");
14426
14490
  const make$59 = Effect.fn("HubLink.make")(function* (environment) {
@@ -14432,7 +14496,8 @@ const make$59 = Effect.fn("HubLink.make")(function* (environment) {
14432
14496
  return Option.match(stored, {
14433
14497
  onNone: () => ({
14434
14498
  settings: Option.none(),
14435
- syncMode: "auto"
14499
+ syncMode: "auto",
14500
+ shareMode: "all"
14436
14501
  }),
14437
14502
  onSome: (bytes) => parseHubLink(new TextDecoder().decode(bytes))
14438
14503
  });
@@ -14440,10 +14505,12 @@ const make$59 = Effect.fn("HubLink.make")(function* (environment) {
14440
14505
  const toState = (record) => Option.isSome(fromEnv) ? {
14441
14506
  settings: fromEnv,
14442
14507
  syncMode: record.syncMode,
14508
+ shareMode: record.shareMode,
14443
14509
  source: "environment"
14444
14510
  } : {
14445
14511
  settings: record.settings,
14446
14512
  syncMode: record.syncMode,
14513
+ shareMode: record.shareMode,
14447
14514
  source: Option.isSome(record.settings) ? "stored" : "none"
14448
14515
  };
14449
14516
  const cache = yield* Ref.make(yield* readStored());
@@ -14459,23 +14526,30 @@ const make$59 = Effect.fn("HubLink.make")(function* (environment) {
14459
14526
  if (Option.isNone(validated)) return yield* new InvalidHubLink({ detail: "A hub needs an http or https URL and a non-empty token." });
14460
14527
  const record = yield* Ref.get(cache);
14461
14528
  return yield* persist({
14462
- settings: validated,
14463
- syncMode: record.syncMode
14529
+ ...record,
14530
+ settings: validated
14464
14531
  });
14465
14532
  }),
14466
14533
  disconnect: Effect.gen(function* () {
14467
14534
  const record = yield* Ref.get(cache);
14468
14535
  return yield* persist({
14469
- settings: Option.none(),
14470
- syncMode: record.syncMode
14536
+ ...record,
14537
+ settings: Option.none()
14471
14538
  });
14472
14539
  }),
14473
14540
  setSyncMode: (mode) => Effect.gen(function* () {
14474
14541
  const record = yield* Ref.get(cache);
14475
14542
  return yield* persist({
14476
- settings: record.settings,
14543
+ ...record,
14477
14544
  syncMode: mode
14478
14545
  });
14546
+ }),
14547
+ setShareMode: (mode) => Effect.gen(function* () {
14548
+ const record = yield* Ref.get(cache);
14549
+ return yield* persist({
14550
+ ...record,
14551
+ shareMode: mode
14552
+ });
14479
14553
  })
14480
14554
  };
14481
14555
  });
@@ -14776,6 +14850,38 @@ const writeSkillManifest = Effect.fn("skillManifest.write")(function* (manifestP
14776
14850
  /** Bounded so a stray symlink or a vendored `node_modules` cannot walk forever. */
14777
14851
  const MAX_SKILL_DEPTH = 4;
14778
14852
  /**
14853
+ * Entries that are never part of a skill, skipped rather than refused.
14854
+ *
14855
+ * The distinction matters. Refusing an asset because it cannot be represented
14856
+ * faithfully is right — a half-materialized skill loads and misbehaves. But a
14857
+ * `.DS_Store` is not part of the skill at all, and macOS drops one into any
14858
+ * directory that has been opened in Finder, so refusing on its account rejects
14859
+ * a perfectly good skill for a reason its author cannot see.
14860
+ *
14861
+ * These could not sync in any case: `AgentAssetFilePath` rejects a leading dot,
14862
+ * so a dotfile has no representable path even if it were text.
14863
+ */
14864
+ const IGNORED_ENTRIES = /* @__PURE__ */ new Set([
14865
+ ".DS_Store",
14866
+ "Thumbs.db",
14867
+ ".git",
14868
+ ".svn",
14869
+ ".hg",
14870
+ "node_modules",
14871
+ "__pycache__",
14872
+ ".venv",
14873
+ ".pytest_cache",
14874
+ ".ruff_cache",
14875
+ ".mypy_cache"
14876
+ ]);
14877
+ /**
14878
+ * Anything else with a leading dot is skipped too — an editor's `.idea`, a
14879
+ * `.gitignore` that belongs to the checkout rather than the skill. The rule is
14880
+ * the same one the path schema already enforces, applied earlier so it reads as
14881
+ * "not part of the skill" instead of "this skill is broken".
14882
+ */
14883
+ const isIgnoredEntry = (entry) => IGNORED_ENTRIES.has(entry) || entry.startsWith(".");
14884
+ /**
14779
14885
  * A file is binary if decoding it yields a NUL.
14780
14886
  *
14781
14887
  * Cheap, and right for what is being asked: the question is not "is this valid
@@ -14791,6 +14897,7 @@ const readSkillFiles = Effect.fn("skillDirectory.readSkillFiles")(function* (roo
14791
14897
  if (depth > MAX_SKILL_DEPTH) return "too-deep";
14792
14898
  const entries = yield* fileSystem.readDirectory(directory).pipe(Effect.orElseSucceed(() => []));
14793
14899
  for (const entry of [...entries].sort()) {
14900
+ if (isIgnoredEntry(entry)) continue;
14794
14901
  const absolute = path.join(directory, entry);
14795
14902
  const relative = prefix === "" ? entry : `${prefix}/${entry}`;
14796
14903
  const info = yield* fileSystem.stat(absolute).pipe(Effect.orElseSucceed(() => void 0));
@@ -14801,7 +14908,7 @@ const readSkillFiles = Effect.fn("skillDirectory.readSkillFiles")(function* (roo
14801
14908
  continue;
14802
14909
  }
14803
14910
  if (info.type !== "File") continue;
14804
- if (files.length >= 64) return "too-many-files";
14911
+ if (files.length >= 512) return "too-many-files";
14805
14912
  const content = yield* fileSystem.readFileString(absolute).pipe(Effect.orElseSucceed(() => void 0));
14806
14913
  if (content === void 0) return "unreadable";
14807
14914
  if (looksBinary(content)) return "binary-file";
@@ -14815,7 +14922,7 @@ const readSkillFiles = Effect.fn("skillDirectory.readSkillFiles")(function* (roo
14815
14922
  });
14816
14923
  const refusal = yield* walk(root, "", 1);
14817
14924
  if (refusal !== null) return { refusal };
14818
- if (agentAssetTotalBytes(files) > 2097152) return { refusal: "asset-too-large" };
14925
+ if (agentAssetTotalBytes(files) > 4194304) return { refusal: "asset-too-large" };
14819
14926
  return { files };
14820
14927
  });
14821
14928
  /**
@@ -14832,6 +14939,7 @@ const readSkillDirectory = Effect.fn("skillDirectory.readSkillDirectory")(functi
14832
14939
  const skills = [];
14833
14940
  const refused = [];
14834
14941
  for (const entry of [...entries].sort()) {
14942
+ if (isIgnoredEntry(entry)) continue;
14835
14943
  const directory = path.join(skillsRoot, entry);
14836
14944
  if ((yield* fileSystem.stat(directory).pipe(Effect.orElseSucceed(() => void 0)))?.type !== "Directory") continue;
14837
14945
  if (!(yield* fileSystem.exists(path.join(directory, "SKILL.md")).pipe(Effect.orElseSucceed(() => false)))) continue;
@@ -15146,14 +15254,41 @@ const make$57 = Effect.gen(function* () {
15146
15254
  break;
15147
15255
  }
15148
15256
  yield* writeSkillManifest(manifestPath, withEntriesFor(manifest, skillsRoot, [...entries.values()]));
15257
+ const shareMode = (yield* link.current).shareMode;
15258
+ const autoShared = [];
15259
+ if (shareMode === "all") {
15260
+ for (const name of plan.unmanaged) {
15261
+ const skill = local.skills.find((entry) => entry.name === name);
15262
+ if (skill === void 0) continue;
15263
+ const written = yield* client.write({
15264
+ kind: "skill",
15265
+ name,
15266
+ files: skill.files
15267
+ }).pipe(Effect.option);
15268
+ if (Option.isNone(written)) {
15269
+ conflicts.push({
15270
+ name,
15271
+ reason: "unmanaged-local-collision"
15272
+ });
15273
+ continue;
15274
+ }
15275
+ entries.set(name, {
15276
+ name,
15277
+ version: written.value.version,
15278
+ digest: digestSkillFiles(written.value.files)
15279
+ });
15280
+ autoShared.push(name);
15281
+ }
15282
+ yield* writeSkillManifest(manifestPath, withEntriesFor(manifest, skillsRoot, [...entries.values()]));
15283
+ }
15149
15284
  return {
15150
15285
  ran: true,
15151
15286
  skillsRoot,
15152
15287
  materialized,
15153
- published,
15288
+ published: [...published, ...autoShared],
15154
15289
  removed,
15155
15290
  conflicts,
15156
- unmanaged: plan.unmanaged,
15291
+ unmanaged: plan.unmanaged.filter((name) => !autoShared.includes(name)),
15157
15292
  refused: local.refused,
15158
15293
  unavailable: null
15159
15294
  };
@@ -15165,13 +15300,14 @@ const make$57 = Effect.gen(function* () {
15165
15300
  * sharing the executing code path is one `if` away from writing during a
15166
15301
  * preview, and this is the call a panel makes every time it opens.
15167
15302
  */
15168
- const status = Effect.gen(function* () {
15303
+ const skillStatus = Effect.gen(function* () {
15169
15304
  const state = yield* link.current;
15170
15305
  const skillsRoot = yield* resolveSkillsRoot();
15171
15306
  const local = yield* readSkillDirectory(skillsRoot);
15172
15307
  const managed = entriesFor(yield* readSkillManifest(manifestPath), skillsRoot);
15173
15308
  const base = {
15174
15309
  syncMode: state.syncMode,
15310
+ shareMode: state.shareMode,
15175
15311
  source: state.source,
15176
15312
  skillsRoot,
15177
15313
  refused: local.refused
@@ -15256,6 +15392,7 @@ const make$57 = Effect.gen(function* () {
15256
15392
  configured: false,
15257
15393
  hubUrl: null,
15258
15394
  syncMode: "auto",
15395
+ shareMode: "all",
15259
15396
  source: "none",
15260
15397
  skillsRoot: null,
15261
15398
  entries: [],
@@ -15313,6 +15450,27 @@ const make$57 = Effect.gen(function* () {
15313
15450
  published: [name]
15314
15451
  };
15315
15452
  }).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("skill publish failed", { cause }).pipe(Effect.as(EMPTY_REPORT))));
15453
+ const publishAll = Effect.gen(function* () {
15454
+ if (!(yield* isConfigured)) return EMPTY_REPORT;
15455
+ const skillsRoot = yield* resolveSkillsRoot();
15456
+ const status = yield* skillStatus;
15457
+ const candidates = status.entries.filter((entry) => entry.state === "unmanaged").map((entry) => entry.name);
15458
+ const published = [];
15459
+ const conflicts = [];
15460
+ for (const name of candidates) {
15461
+ const report = yield* publish(name);
15462
+ published.push(...report.published);
15463
+ conflicts.push(...report.conflicts);
15464
+ }
15465
+ return {
15466
+ ...EMPTY_REPORT,
15467
+ ran: true,
15468
+ skillsRoot,
15469
+ published,
15470
+ conflicts,
15471
+ refused: status.refused
15472
+ };
15473
+ }).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("skill publish-all failed", { cause }).pipe(Effect.as(EMPTY_REPORT))));
15316
15474
  const unpublish = (name) => Effect.gen(function* () {
15317
15475
  if (!(yield* isConfigured)) return EMPTY_REPORT;
15318
15476
  const skillsRoot = yield* resolveSkillsRoot();
@@ -15348,15 +15506,16 @@ const make$57 = Effect.gen(function* () {
15348
15506
  }).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("skill unpublish failed", { cause }).pipe(Effect.as(EMPTY_REPORT))));
15349
15507
  return {
15350
15508
  run,
15351
- status,
15509
+ status: skillStatus,
15352
15510
  publish,
15511
+ publishAll,
15353
15512
  unpublish
15354
15513
  };
15355
15514
  });
15356
15515
  const layer$52 = Layer.effect(SkillSync, make$57);
15357
15516
  //#endregion
15358
15517
  //#region src/cli/skills.ts
15359
- const SyncServicesLive = layer$52.pipe(Layer.provideMerge(layer$53), Layer.provideMerge(layer$54), Layer.provideMerge(layer$55.pipe(Layer.provide(layer$61))), Layer.provideMerge(layer$61), Layer.provideMerge(FetchHttpClient.layer));
15518
+ const SyncServicesLive = layer$52.pipe(Layer.provideMerge(layer$53), Layer.provideMerge(layer$54), Layer.provideMerge(layer$55.pipe(Layer.provide(layer$62))), Layer.provideMerge(layer$62), Layer.provideMerge(FetchHttpClient.layer));
15360
15519
  const runWithSync = (flags, run) => Effect.gen(function* () {
15361
15520
  const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
15362
15521
  return yield* Effect.gen(function* () {
@@ -15384,6 +15543,7 @@ const formatStatus = (status) => {
15384
15543
  else {
15385
15544
  lines.push(`Hub: ${status.hubUrl ?? "unknown"}${status.source === "environment" ? " (from the environment)" : ""}`);
15386
15545
  lines.push(`Sync: ${status.syncMode}`);
15546
+ lines.push(`Share: ${status.shareMode === "all" ? "all skills (new ones share automatically)" : "only the ones you choose"}`);
15387
15547
  }
15388
15548
  if (status.skillsRoot !== null) lines.push(`Skills: ${status.skillsRoot}`);
15389
15549
  if (status.unavailable !== null) lines.push(`Warning: ${status.unavailable}`);
@@ -15406,7 +15566,10 @@ const formatReport = (report) => {
15406
15566
  if (report.removed.length > 0) parts.push(`removed ${report.removed.join(", ")}`);
15407
15567
  for (const conflict of report.conflicts) parts.push(`CONFLICT on ${conflict.name} (${conflict.reason})`);
15408
15568
  for (const refusal of report.refused) parts.push(`cannot sync ${refusal.name} (${refusal.reason})`);
15409
- return parts.length === 0 ? "Already up to date." : parts.join("\n");
15569
+ const summary = parts.length === 0 ? "Already up to date." : parts.join("\n");
15570
+ if (report.unmanaged.length === 0) return summary;
15571
+ const count = report.unmanaged.length;
15572
+ return `${summary}\n\n${count} local skill${count === 1 ? " is" : "s are"} not shared. Share ${count === 1 ? "it" : "them"} with: p4 skills publish --all`;
15410
15573
  };
15411
15574
  const urlFlag = Flag.string("url").pipe(Flag.withDescription("Base URL of the hub, for example https://hub.example.com."));
15412
15575
  const tokenFlag = Flag.string("token").pipe(Flag.withDescription("Bearer token for this server, from `p4 hub token add`."));
@@ -15428,14 +15591,29 @@ const hubDisconnectCommand = Command.make("disconnect", { ...authLocationFlags }
15428
15591
  }))));
15429
15592
  const statusCommand = Command.make("status", { ...authLocationFlags }).pipe(Command.withDescription("Show which skills are shared, changed, or in conflict."), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => sync.status.pipe(Effect.flatMap((s) => Console.log(formatStatus(s)))))));
15430
15593
  const syncCommand = Command.make("sync", { ...authLocationFlags }).pipe(Command.withDescription("Reconcile this machine's shared skills with the hub now."), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => sync.run.pipe(Effect.flatMap((r) => Console.log(formatReport(r)))))));
15594
+ const allFlag = Flag.boolean("all").pipe(Flag.withDescription("Share every local skill this machine does not already share."));
15431
15595
  const publishCommand = Command.make("publish", {
15432
15596
  ...authLocationFlags,
15433
- name: skillNameArgument
15434
- }).pipe(Command.withDescription("Share one local skill through the hub, so every machine gets it."), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => sync.publish(flags.name).pipe(Effect.flatMap((report) => Console.log(formatReport(report)))))));
15597
+ name: skillNameArgument.pipe(Argument.optional),
15598
+ all: allFlag
15599
+ }).pipe(Command.withDescription("Share local skills through the hub, so every machine gets them."), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => Effect.gen(function* () {
15600
+ if (flags.all) return yield* sync.publishAll.pipe(Effect.flatMap((report) => Console.log(formatReport(report))));
15601
+ const name = Option.getOrUndefined(flags.name);
15602
+ if (name === void 0) return yield* Console.error("Name a skill, or pass --all to share every unshared one.");
15603
+ return yield* sync.publish(name).pipe(Effect.flatMap((report) => Console.log(formatReport(report))));
15604
+ }))));
15435
15605
  const unpublishCommand = Command.make("unpublish", {
15436
15606
  ...authLocationFlags,
15437
15607
  name: skillNameArgument
15438
15608
  }).pipe(Command.withDescription("Stop sharing a skill. It stays on this machine and leaves the others."), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => sync.unpublish(flags.name).pipe(Effect.flatMap((report) => Console.log(formatReport(report)))))));
15609
+ const shareAllCommand = Command.make("share-all", { ...authLocationFlags }).pipe(Command.withDescription("Share every skill, including new ones, automatically. The default."), Command.withHandler((flags) => runWithSync(flags, ({ sync, link }) => Effect.gen(function* () {
15610
+ yield* link.setShareMode("all");
15611
+ yield* Console.log(formatStatus(yield* sync.status));
15612
+ }))));
15613
+ const shareSelectedCommand = Command.make("share-selected", { ...authLocationFlags }).pipe(Command.withDescription("Share only the skills you name with `publish`. Nothing shares on its own."), Command.withHandler((flags) => runWithSync(flags, ({ sync, link }) => Effect.gen(function* () {
15614
+ yield* link.setShareMode("selected");
15615
+ yield* Console.log(formatStatus(yield* sync.status));
15616
+ }))));
15439
15617
  const pauseCommand = Command.make("pause", { ...authLocationFlags }).pipe(Command.withDescription("Stop syncing automatically. `p4 skills sync` still works."), Command.withHandler((flags) => runWithSync(flags, ({ sync, link }) => Effect.gen(function* () {
15440
15618
  yield* link.setSyncMode("paused");
15441
15619
  yield* Console.log(formatStatus(yield* sync.status));
@@ -15450,7 +15628,9 @@ const skillsCommand = Command.make("skills", {}).pipe(Command.withDescription("S
15450
15628
  publishCommand,
15451
15629
  unpublishCommand,
15452
15630
  pauseCommand,
15453
- resumeCommand
15631
+ resumeCommand,
15632
+ shareAllCommand,
15633
+ shareSelectedCommand
15454
15634
  ]));
15455
15635
  /** Exported for the hub command, which owns `connect`/`disconnect`. */
15456
15636
  const hubLinkCommands = [hubConnectCommand, hubDisconnectCommand];
@@ -39446,9 +39626,11 @@ const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
39446
39626
  [WS_METHODS.hubConnect, AuthOrchestrationOperateScope],
39447
39627
  [WS_METHODS.hubDisconnect, AuthOrchestrationOperateScope],
39448
39628
  [WS_METHODS.hubSetSyncMode, AuthOrchestrationOperateScope],
39629
+ [WS_METHODS.hubSetShareMode, AuthOrchestrationOperateScope],
39449
39630
  [WS_METHODS.hubMintToken, AuthOrchestrationOperateScope],
39450
39631
  [WS_METHODS.skillsSync, AuthOrchestrationOperateScope],
39451
39632
  [WS_METHODS.skillsPublish, AuthOrchestrationOperateScope],
39633
+ [WS_METHODS.skillsPublishAll, AuthOrchestrationOperateScope],
39452
39634
  [WS_METHODS.skillsUnpublish, AuthOrchestrationOperateScope],
39453
39635
  [WS_METHODS.serverGetSettings, AuthOrchestrationReadScope],
39454
39636
  [WS_METHODS.serverUpdateSettings, AuthOrchestrationOperateScope],
@@ -40026,6 +40208,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
40026
40208
  [WS_METHODS.hubConnect]: (input) => observeRpcEffect$1(WS_METHODS.hubConnect, hubLink.connect(input).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(skillSync.status)), { "rpc.aggregate": "hub" }),
40027
40209
  [WS_METHODS.hubDisconnect]: (_input) => observeRpcEffect$1(WS_METHODS.hubDisconnect, hubLink.disconnect.pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(skillSync.status)), { "rpc.aggregate": "hub" }),
40028
40210
  [WS_METHODS.hubSetSyncMode]: ({ mode }) => observeRpcEffect$1(WS_METHODS.hubSetSyncMode, hubLink.setSyncMode(mode).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(skillSync.status)), { "rpc.aggregate": "hub" }),
40211
+ [WS_METHODS.hubSetShareMode]: ({ mode }) => observeRpcEffect$1(WS_METHODS.hubSetShareMode, hubLink.setShareMode(mode).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(skillSync.status)), { "rpc.aggregate": "hub" }),
40029
40212
  [WS_METHODS.hubMintToken]: (_input) => observeRpcEffect$1(WS_METHODS.hubMintToken, Effect.gen(function* () {
40030
40213
  const token = yield* crypto.randomBytes(HUB_TOKEN_BYTES).pipe(Effect.map((bytes) => Buffer.from(bytes).toString("base64url")), Effect.orDie);
40031
40214
  return {
@@ -40045,6 +40228,12 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
40045
40228
  status: yield* skillSync.status
40046
40229
  };
40047
40230
  }), { "rpc.aggregate": "skills" }),
40231
+ [WS_METHODS.skillsPublishAll]: (_input) => observeRpcEffect$1(WS_METHODS.skillsPublishAll, Effect.gen(function* () {
40232
+ return {
40233
+ report: yield* skillSync.publishAll,
40234
+ status: yield* skillSync.status
40235
+ };
40236
+ }), { "rpc.aggregate": "skills" }),
40048
40237
  [WS_METHODS.skillsUnpublish]: ({ name }) => observeRpcEffect$1(WS_METHODS.skillsUnpublish, Effect.gen(function* () {
40049
40238
  return {
40050
40239
  report: yield* skillSync.unpublish(name),
@@ -81336,9 +81525,9 @@ const WorkspaceEntriesLayerLive = layer$26.pipe(Layer.provide(layer$42));
81336
81525
  const WorkspaceFileSystemLayerLive = layer$25.pipe(Layer.provide(layer$42), Layer.provide(WorkspaceEntriesLayerLive));
81337
81526
  const WorkspaceLayerLive = Layer.mergeAll(layer$42, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
81338
81527
  const ProjectFaviconResolverLayerLive = layer$40.pipe(Layer.provide(layer$42), Layer.provide(layer$41));
81339
- const AuthLayerLive = layer$58.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$61));
81528
+ const AuthLayerLive = layer$58.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$62));
81340
81529
  const ProviderRuntimeLayerLive = ProviderSessionReaperLive.pipe(Layer.provideMerge(ProviderLayerLive), Layer.provideMerge(OrchestrationLayerLive));
81341
- const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(CheckpointingLayerLive), Layer.provideMerge(SourceControlProviderRegistryLayerLive), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive)), Layer.provideMerge(PersistenceLayerLive), Layer.provideMerge(layer$50), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$55.pipe(Layer.provide(layer$61))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$51), Layer.provideMerge(layer$44), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(layer$61)).pipe(Layer.provideMerge(layer$8), Layer.provideMerge(layer$7), Layer.provideMerge(layer$6), Layer.provideMerge(layer$47), Layer.provideMerge(layer$49), Layer.provideMerge(layer$48), Layer.provide(layer$66));
81530
+ const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(CheckpointingLayerLive), Layer.provideMerge(SourceControlProviderRegistryLayerLive), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive)), Layer.provideMerge(PersistenceLayerLive), Layer.provideMerge(layer$50), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$55.pipe(Layer.provide(layer$62))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$51), Layer.provideMerge(layer$44), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(layer$62)).pipe(Layer.provideMerge(layer$8), Layer.provideMerge(layer$7), Layer.provideMerge(layer$6), Layer.provideMerge(layer$47), Layer.provideMerge(layer$49), Layer.provideMerge(layer$48), Layer.provide(layer$66));
81342
81531
  /**
81343
81532
  * Hub asset sync.
81344
81533
  *