@p4code/cli 0.1.12 → 0.1.14

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 (88) hide show
  1. package/dist/bin.mjs +801 -72
  2. package/dist/client/assets/{DiffPanel-DLEF2Os4.js → DiffPanel-BdNsdwM5.js} +3 -3
  3. package/dist/client/assets/{FilePreviewPanel-8D_2Xs92.js → FilePreviewPanel-CWcDqLso.js} +5 -5
  4. package/dist/client/assets/PreviewPanel-2u47bK5v.js +2 -0
  5. package/dist/client/assets/{angular-html-CMBWPyD3.js → angular-html-BLv19dHn.js} +2 -2
  6. package/dist/client/assets/{angular-ts-BatZhjLf.js → angular-ts-D41w0Au3.js} +2 -2
  7. package/dist/client/assets/{apl-Cx1sW3hC.js → apl-BEl8UFu4.js} +2 -2
  8. package/dist/client/assets/arrow-right-Tef5pBUM.js +2 -0
  9. package/dist/client/assets/{astro-nMa7HQcv.js → astro-U1Y_O85L.js} +2 -2
  10. package/dist/client/assets/{blade-JxAt8tFv.js → blade-CZ232EWr.js} +2 -2
  11. package/dist/client/assets/{c-C-yEqtHp.js → c-DirgNKaz.js} +2 -2
  12. package/dist/client/assets/{cobol-CA3DrkO1.js → cobol-B7WyPi1F.js} +2 -2
  13. package/dist/client/assets/{coffee-CNzul851.js → coffee-C_qHkMIP.js} +2 -2
  14. package/dist/client/assets/{cpp-DYasO6ny.js → cpp-SPUhkLTs.js} +2 -2
  15. package/dist/client/assets/{crystal-BkYnv3Y5.js → crystal-vq6jdakY.js} +2 -2
  16. package/dist/client/assets/{css-GnxXZE-U.js → css-BAnKum7K.js} +2 -2
  17. package/dist/client/assets/{edge-DHx6AdO-.js → edge-BuMKesaI.js} +2 -2
  18. package/dist/client/assets/{elixir-XiEvMT4-.js → elixir-BL-W6BH4.js} +2 -2
  19. package/dist/client/assets/{elm-BY4GYI5F.js → elm-DiUCNL3v.js} +2 -2
  20. package/dist/client/assets/{erb-DDG6lOki.js → erb-skcSurJb.js} +2 -2
  21. package/dist/client/assets/{fileCommentAnnotations-BTZDcAeh.js → fileCommentAnnotations-BRTew3I1.js} +2 -2
  22. package/dist/client/assets/{git-rebase-BDUy60An.js → git-rebase-dO-xI28x.js} +2 -2
  23. package/dist/client/assets/{glimmer-js-Dpr_XG7j.js → glimmer-js-CECukRm7.js} +2 -2
  24. package/dist/client/assets/{glimmer-ts-CtZ0ZipS.js → glimmer-ts-CIaRC8zm.js} +2 -2
  25. package/dist/client/assets/{glsl-DHtQlcKL.js → glsl-CKSbugKg.js} +2 -2
  26. package/dist/client/assets/{graphql-DtNuYKEb.js → graphql-BiZ7twV7.js} +2 -2
  27. package/dist/client/assets/{hack-BqNfldCe.js → hack-BdAxCPD7.js} +2 -2
  28. package/dist/client/assets/{haml-mPiLB45s.js → haml-SZR9Ndrp.js} +2 -2
  29. package/dist/client/assets/{handlebars-BLxX_l2A.js → handlebars-BExVQKqz.js} +2 -2
  30. package/dist/client/assets/{html-DU5G3F4s.js → html-CkYFYJn1.js} +2 -2
  31. package/dist/client/assets/{html-derivative-BMJPXTuS.js → html-derivative-Cop3EQeZ.js} +2 -2
  32. package/dist/client/assets/{http-6kGF1eof.js → http-DQ8c4u3s.js} +2 -2
  33. package/dist/client/assets/{hurl-CvT1bXUG.js → hurl-BKyyIuNR.js} +2 -2
  34. package/dist/client/assets/index-ClVe76A5.js +660 -0
  35. package/dist/client/assets/index-DGu8ejUP.css +1 -0
  36. package/dist/client/assets/{java-bBnbDfw8.js → java-CedFcWif.js} +2 -2
  37. package/dist/client/assets/{javascript-CpOINVUc.js → javascript-BdXDoWFM.js} +2 -2
  38. package/dist/client/assets/{jinja-CItwptlU.js → jinja-CK8YWL7a.js} +2 -2
  39. package/dist/client/assets/{jison-BWvHru6a.js → jison-C-mnFz-X.js} +2 -2
  40. package/dist/client/assets/{json-ClObSVU5.js → json-Dxix4Qbo.js} +2 -2
  41. package/dist/client/assets/{jsx-vHozUqKh.js → jsx-6M0gL61I.js} +2 -2
  42. package/dist/client/assets/{julia-CITEF0SC.js → julia-SXfQGJ8P.js} +2 -2
  43. package/dist/client/assets/{just-CAMHq4cU.js → just-O0TR05uE.js} +2 -2
  44. package/dist/client/assets/{latex-BHB-Vy2g.js → latex-w7R_uAyc.js} +2 -2
  45. package/dist/client/assets/{liquid-rsijXGco.js → liquid-hPzEzmzZ.js} +2 -2
  46. package/dist/client/assets/{lua-CPA1zmkS.js → lua-D24pQ94w.js} +2 -2
  47. package/dist/client/assets/{marko-D9CJEgH2.js → marko-DsaGFeHY.js} +2 -2
  48. package/dist/client/assets/{mdc-DL5ptUuf.js → mdc-C4vifn2U.js} +2 -2
  49. package/dist/client/assets/{nginx-DAAmFPDe.js → nginx-KnKOgHWr.js} +2 -2
  50. package/dist/client/assets/{nim-DmaHLOWa.js → nim-CXVNWegc.js} +2 -2
  51. package/dist/client/assets/{perl-Bw03qQ4D.js → perl-_PU7gLJn.js} +2 -2
  52. package/dist/client/assets/{php-DdSlZ5fF.js → php-B--0cDl3.js} +2 -2
  53. package/dist/client/assets/{pug-CgQdUdQK.js → pug-DELbYjHv.js} +2 -2
  54. package/dist/client/assets/{qml-6-Z__KDk.js → qml-CyGKPCuH.js} +2 -2
  55. package/dist/client/assets/{r-BwsNM1XU.js → r-CSjDVJJY.js} +2 -2
  56. package/dist/client/assets/{razor-DRWY5qRs.js → razor-ppZnQuQy.js} +2 -2
  57. package/dist/client/assets/{regexp-BGiLplmg.js → regexp-DAYlObpi.js} +2 -2
  58. package/dist/client/assets/{rst-CND1NmX_.js → rst-DV8sAt14.js} +2 -2
  59. package/dist/client/assets/{ruby-Dlxt-z_r.js → ruby-DXISWktP.js} +2 -2
  60. package/dist/client/assets/{sas-DFeunfku.js → sas-CJl6Tu3p.js} +2 -2
  61. package/dist/client/assets/{scss-D_BTo2XD.js → scss-DmbKMDuD.js} +2 -2
  62. package/dist/client/assets/{shellscript-cEuOjvsv.js → shellscript-BFH8QS9G.js} +2 -2
  63. package/dist/client/assets/{shellsession-CeWrE-Lw.js → shellsession-CIHFsdwm.js} +2 -2
  64. package/dist/client/assets/{soy-DdFNKMwD.js → soy--q_a3wNH.js} +2 -2
  65. package/dist/client/assets/{sql-3jzdtF3L.js → sql-DE3xygzU.js} +2 -2
  66. package/dist/client/assets/{stata-CfftDdbo.js → stata-BZORI8K-.js} +2 -2
  67. package/dist/client/assets/{surrealql-BgLa39M_.js → surrealql-DQwYGqPv.js} +2 -2
  68. package/dist/client/assets/{svelte-CJNznzEX.js → svelte-C8rcZCbS.js} +2 -2
  69. package/dist/client/assets/{templ-DiQ0ONH_.js → templ-YxwRimHC.js} +2 -2
  70. package/dist/client/assets/{tex-Dj5-ewTi.js → tex-CreWrrVU.js} +2 -2
  71. package/dist/client/assets/textarea-DogWRke3.js +46 -0
  72. package/dist/client/assets/{ts-tags-Rut2YPAl.js → ts-tags-BGYLwTar.js} +2 -2
  73. package/dist/client/assets/{tsx-TxGWIfKc.js → tsx-dvRLM93g.js} +2 -2
  74. package/dist/client/assets/{twig-CEDmL9ir.js → twig-BwKK1q-j.js} +2 -2
  75. package/dist/client/assets/{typescript-B1NW_Svr.js → typescript-CIA9s0mZ.js} +2 -2
  76. package/dist/client/assets/{vue-CEjpLlUp.js → vue-RR_2K8-s.js} +2 -2
  77. package/dist/client/assets/{vue-html-BLRayXbW.js → vue-html-SobvEO2e.js} +2 -2
  78. package/dist/client/assets/{vue-vine-BaZ_eWYq.js → vue-vine-BQgxB5Yf.js} +2 -2
  79. package/dist/client/assets/{xml-WjUY_YSL.js → xml-qR9OaTYe.js} +2 -2
  80. package/dist/client/assets/{xsl-rMBkkP2N.js → xsl-CNAIK0Wp.js} +2 -2
  81. package/dist/client/assets/{yaml-BVcdKGq_.js → yaml-CnmdSZli.js} +2 -2
  82. package/dist/client/index.html +3 -3
  83. package/package.json +1 -1
  84. package/dist/client/assets/PreviewPanel-DiR-_u2D.js +0 -2
  85. package/dist/client/assets/arrow-right-D6CCHJEU.js +0 -2
  86. package/dist/client/assets/index-JvBwT8k1.js +0 -660
  87. package/dist/client/assets/index-nkPMSYxq.css +0 -1
  88. package/dist/client/assets/textarea-CtwXMNmJ.js +0 -46
package/dist/bin.mjs CHANGED
@@ -121,7 +121,7 @@ const closeServer = (server) => {
121
121
  * NetService - Service tag for startup networking helpers.
122
122
  */
123
123
  var NetService = class extends Context.Service()("@p4code/shared/Net/NetService") {};
124
- const make$73 = () => {
124
+ const make$74 = () => {
125
125
  /**
126
126
  * Returns true when a TCP server can bind to {host, port}.
127
127
  * `EADDRNOTAVAIL` is treated as available so IPv6-absent hosts don't fail
@@ -230,10 +230,10 @@ const make$73 = () => {
230
230
  })
231
231
  };
232
232
  };
233
- const layer$69 = Layer.sync(NetService, make$73);
233
+ const layer$70 = Layer.sync(NetService, make$74);
234
234
  //#endregion
235
235
  //#region package.json
236
- var version = "0.1.12";
236
+ var version = "0.1.14";
237
237
  //#endregion
238
238
  //#region src/config.ts
239
239
  /**
@@ -254,8 +254,8 @@ var ServerConfig$1 = class extends Context.Service()("@p4code/cli/config/ServerC
254
254
  /** @deprecated Import and use `layerTest` from this module. */
255
255
  static layerTest = (cwd, baseDirOrPrefix) => layerTest$2(cwd, baseDirOrPrefix);
256
256
  };
257
- const make$72 = (config) => ServerConfig$1.of(config);
258
- const layer$68 = (config) => Layer.succeed(ServerConfig$1, make$72(config));
257
+ const make$73 = (config) => ServerConfig$1.of(config);
258
+ const layer$69 = (config) => Layer.succeed(ServerConfig$1, make$73(config));
259
259
  const deriveServerPaths = Effect.fn(function* (baseDir, devUrl, options = {}) {
260
260
  const { join } = yield* Path.Path;
261
261
  const stateDir = join(baseDir, devUrl !== void 0 && !options.baseDirIsExplicit ? "dev" : "userdata");
@@ -1657,6 +1657,7 @@ const CompressMode = Schema$1.Literals([
1657
1657
  "full",
1658
1658
  "ultra"
1659
1659
  ]);
1660
+ const DEFAULT_COMPRESS_MODE = "full";
1660
1661
  const ProviderRequestKind = Schema$1.Literals([
1661
1662
  "command",
1662
1663
  "file-read",
@@ -1833,7 +1834,7 @@ const OrchestrationThread = Schema$1.Struct({
1833
1834
  modelSelection: ModelSelection,
1834
1835
  runtimeMode: RuntimeMode,
1835
1836
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1836
- compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed("off"))),
1837
+ compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
1837
1838
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
1838
1839
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
1839
1840
  latestTurn: Schema$1.NullOr(OrchestrationLatestTurn),
@@ -1874,7 +1875,7 @@ const OrchestrationThreadShell = Schema$1.Struct({
1874
1875
  modelSelection: ModelSelection,
1875
1876
  runtimeMode: RuntimeMode,
1876
1877
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1877
- compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed("off"))),
1878
+ compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
1878
1879
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
1879
1880
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
1880
1881
  latestTurn: Schema$1.NullOr(OrchestrationLatestTurn),
@@ -1997,7 +1998,7 @@ const ThreadCreateCommand = Schema$1.Struct({
1997
1998
  modelSelection: ModelSelection,
1998
1999
  runtimeMode: RuntimeMode,
1999
2000
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
2000
- compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed("off"))),
2001
+ compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
2001
2002
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
2002
2003
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
2003
2004
  createdAt: IsoDateTime
@@ -2077,7 +2078,7 @@ const ThreadTurnStartBootstrapCreateThread = Schema$1.Struct({
2077
2078
  modelSelection: ModelSelection,
2078
2079
  runtimeMode: RuntimeMode,
2079
2080
  interactionMode: ProviderInteractionMode,
2080
- compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed("off"))),
2081
+ compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
2081
2082
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
2082
2083
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
2083
2084
  createdAt: IsoDateTime
@@ -2107,7 +2108,7 @@ const ThreadTurnStartCommand = Schema$1.Struct({
2107
2108
  titleSeed: Schema$1.optional(TrimmedNonEmptyString),
2108
2109
  runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
2109
2110
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
2110
- compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed("off"))),
2111
+ compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
2111
2112
  bootstrap: Schema$1.optional(ThreadTurnStartBootstrap),
2112
2113
  sourceProposedPlan: Schema$1.optional(SourceProposedPlanReference),
2113
2114
  createdAt: IsoDateTime
@@ -2126,7 +2127,7 @@ const ClientThreadTurnStartCommand = Schema$1.Struct({
2126
2127
  titleSeed: Schema$1.optional(TrimmedNonEmptyString),
2127
2128
  runtimeMode: RuntimeMode,
2128
2129
  interactionMode: ProviderInteractionMode,
2129
- compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed("off"))),
2130
+ compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
2130
2131
  bootstrap: Schema$1.optional(ThreadTurnStartBootstrap),
2131
2132
  sourceProposedPlan: Schema$1.optional(SourceProposedPlanReference),
2132
2133
  createdAt: IsoDateTime
@@ -2346,7 +2347,7 @@ const ThreadCreatedPayload$1 = Schema$1.Struct({
2346
2347
  modelSelection: ModelSelection,
2347
2348
  runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
2348
2349
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
2349
- compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed("off"))),
2350
+ compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
2350
2351
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
2351
2352
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
2352
2353
  createdAt: IsoDateTime,
@@ -2406,7 +2407,7 @@ const ThreadInteractionModeSetPayload$1 = Schema$1.Struct({
2406
2407
  });
2407
2408
  const ThreadCompressModeSetPayload$1 = Schema$1.Struct({
2408
2409
  threadId: ThreadId,
2409
- compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed("off"))),
2410
+ compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
2410
2411
  updatedAt: IsoDateTime
2411
2412
  });
2412
2413
  const ThreadMessageSentPayload = Schema$1.Struct({
@@ -2427,7 +2428,7 @@ const ThreadTurnStartRequestedPayload = Schema$1.Struct({
2427
2428
  titleSeed: Schema$1.optional(TrimmedNonEmptyString),
2428
2429
  runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
2429
2430
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
2430
- compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed("off"))),
2431
+ compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
2431
2432
  sourceProposedPlan: Schema$1.optional(SourceProposedPlanReference),
2432
2433
  createdAt: IsoDateTime
2433
2434
  });
@@ -7651,6 +7652,57 @@ const ThreadControlToolError = Schema$1.Union([
7651
7652
  ThreadRenameRejectedError,
7652
7653
  MemoryAppendFailedError
7653
7654
  ]);
7655
+ const ThreadWatchEventsInput = Schema$1.Struct({
7656
+ threadId: ThreadId.annotate({ description: "The watched thread to read events from. Must be explicitly granted." }),
7657
+ afterSequence: Schema$1.optional(NonNegativeInt.annotate({ description: "Exclusive sequence cursor. Events with a higher sequence are returned. Omit to read from the beginning." })),
7658
+ limit: Schema$1.optional(NonNegativeInt.check(Schema$1.isLessThanOrEqualTo(200)).annotate({ description: `Maximum events to return (default 50, max 200).` }))
7659
+ });
7660
+ const ThreadWatchEventsResult = Schema$1.Struct({
7661
+ threadId: ThreadId,
7662
+ /** Ordered by sequence ascending; resume with the last event's sequence. */
7663
+ events: Schema$1.Array(OrchestrationEvent),
7664
+ /**
7665
+ * The engine's latest persisted sequence at read time. Equal to the last
7666
+ * returned event's sequence when the watcher is caught up.
7667
+ */
7668
+ headSequence: NonNegativeInt,
7669
+ /** True when more events for this thread exist past the returned page. */
7670
+ hasMore: Schema$1.Boolean
7671
+ });
7672
+ var WatchToolUnavailableError = class extends Schema$1.TaggedErrorClass()("WatchToolUnavailableError", {
7673
+ capability: Schema$1.Literal("watch"),
7674
+ environmentId: EnvironmentId,
7675
+ threadId: ThreadId,
7676
+ providerSessionId: TrimmedNonEmptyString,
7677
+ providerInstanceId: ProviderInstanceId
7678
+ }) {
7679
+ get message() {
7680
+ return `MCP credential does not grant the ${this.capability} capability.`;
7681
+ }
7682
+ };
7683
+ /**
7684
+ * The capability is present but the requested thread is not in the granted
7685
+ * set. Separate from {@link WatchToolUnavailableError} so a watcher can tell
7686
+ * "you cannot watch at all" apart from "not this thread".
7687
+ */
7688
+ var ThreadWatchNotPermittedError = class extends Schema$1.TaggedErrorClass()("ThreadWatchNotPermittedError", { threadId: ThreadId }) {
7689
+ get message() {
7690
+ return `This session was not granted watch access to thread ${this.threadId}.`;
7691
+ }
7692
+ };
7693
+ var ThreadWatchFailedError = class extends Schema$1.TaggedErrorClass()("ThreadWatchFailedError", {
7694
+ threadId: ThreadId,
7695
+ detail: Schema$1.String
7696
+ }) {
7697
+ get message() {
7698
+ return `Reading events for thread ${this.threadId} failed: ${this.detail}`;
7699
+ }
7700
+ };
7701
+ const ThreadWatchToolError = Schema$1.Union([
7702
+ WatchToolUnavailableError,
7703
+ ThreadWatchNotPermittedError,
7704
+ ThreadWatchFailedError
7705
+ ]);
7654
7706
  //#endregion
7655
7707
  //#region ../../packages/contracts/src/agentAsset.ts
7656
7708
  /**
@@ -8094,6 +8146,15 @@ const McpSecretSlot = Schema$1.Struct({
8094
8146
  * have to compute the same string or the value is written where nothing looks.
8095
8147
  */
8096
8148
  const mcpSecretStorageName = (input) => `mcp.${input.server}.${input.target}.${input.key}`;
8149
+ /**
8150
+ * The storage name for a server's OAuth record — client id, endpoints, tokens.
8151
+ *
8152
+ * Beside `mcpSecretStorageName` for the same reason that one is here: the
8153
+ * server writing tokens and the registry cleaning up on remove must compute
8154
+ * the same string. Cannot collide with a slot name: slots always carry a
8155
+ * `header.` or `env.` segment where this has `oauth`.
8156
+ */
8157
+ const mcpOAuthStorageName = (server) => `mcp.${server}.oauth`;
8097
8158
  const commonFields = {
8098
8159
  name: McpServerName,
8099
8160
  /**
@@ -8135,6 +8196,20 @@ const McpRemoteServer = Schema$1.Struct({
8135
8196
  */
8136
8197
  const McpServerRegistration = Schema$1.Union([McpStdioServer, McpRemoteServer]);
8137
8198
  /**
8199
+ * OAuth state of a remote server, on this machine.
8200
+ *
8201
+ * Present only for a server that answered a readiness probe with a 401
8202
+ * challenge or that already holds a token here. Like `missingSecrets`, this is
8203
+ * per-machine truth about a registration that syncs everywhere: the token never
8204
+ * travels, so each machine reports its own `connected`.
8205
+ */
8206
+ const McpServerOAuthStatus = Schema$1.Struct({
8207
+ /** The server demanded a bearer token this machine does not send. */
8208
+ required: Schema$1.Boolean,
8209
+ /** This machine holds a token for it. */
8210
+ connected: Schema$1.Boolean
8211
+ });
8212
+ /**
8138
8213
  * What a panel needs to show, including what this machine cannot run.
8139
8214
  *
8140
8215
  * `missingSecrets` is the field that earns this type. A registration syncs
@@ -8146,7 +8221,9 @@ const McpServerStatus = Schema$1.Struct({
8146
8221
  registration: McpServerRegistration,
8147
8222
  missingSecrets: Schema$1.Array(McpSecretSlot),
8148
8223
  /** True when this machine holds everything needed to start it. */
8149
- ready: Schema$1.Boolean
8224
+ ready: Schema$1.Boolean,
8225
+ /** Absent for servers that never challenged and hold no token here. */
8226
+ oauth: Schema$1.optional(McpServerOAuthStatus)
8150
8227
  });
8151
8228
  const McpServerList = Schema$1.Struct({ servers: Schema$1.Array(McpServerStatus) });
8152
8229
  const McpServerSaveInput = Schema$1.Struct({ registration: McpServerRegistration });
@@ -8164,6 +8241,12 @@ const McpServerSecretInput = Schema$1.Struct({
8164
8241
  slot: McpSecretSlot,
8165
8242
  value: Schema$1.String
8166
8243
  });
8244
+ /**
8245
+ * The one thing the panel needs to continue a sign-in: where to send the
8246
+ * browser. Everything else the flow holds — verifier, client id, endpoints —
8247
+ * stays on the server, because the callback lands there, not on the panel.
8248
+ */
8249
+ const McpOAuthBeginResult = Schema$1.Struct({ authorizeUrl: Schema$1.String });
8167
8250
  var McpRegistryError = class extends Schema$1.TaggedErrorClass()("McpRegistryError", { detail: Schema$1.String }) {
8168
8251
  get message() {
8169
8252
  return this.detail;
@@ -8513,6 +8596,8 @@ const WS_METHODS = {
8513
8596
  mcpSave: "mcp.save",
8514
8597
  mcpRemove: "mcp.remove",
8515
8598
  mcpSetSecret: "mcp.setSecret",
8599
+ mcpOAuthBegin: "mcp.oauthBegin",
8600
+ mcpOAuthDisconnect: "mcp.oauthDisconnect",
8516
8601
  sourceControlLookupRepository: "sourceControl.lookupRepository",
8517
8602
  sourceControlCloneRepository: "sourceControl.cloneRepository",
8518
8603
  sourceControlPublishRepository: "sourceControl.publishRepository",
@@ -8782,6 +8867,21 @@ const WsMcpSetSecretRpc = Rpc.make(WS_METHODS.mcpSetSecret, {
8782
8867
  error: Schema$1.Union([McpRegistryError, EnvironmentAuthorizationError])
8783
8868
  });
8784
8869
  /**
8870
+ * Starts an OAuth sign-in for a remote server and answers with the authorize
8871
+ * URL. The panel opens it in a browser; the redirect lands on this server's
8872
+ * own `/oauth/callback`, so nothing else crosses the wire to the client.
8873
+ */
8874
+ const WsMcpOAuthBeginRpc = Rpc.make(WS_METHODS.mcpOAuthBegin, {
8875
+ payload: McpServerNameInput,
8876
+ success: McpOAuthBeginResult,
8877
+ error: Schema$1.Union([McpRegistryError, EnvironmentAuthorizationError])
8878
+ });
8879
+ const WsMcpOAuthDisconnectRpc = Rpc.make(WS_METHODS.mcpOAuthDisconnect, {
8880
+ payload: McpServerNameInput,
8881
+ success: McpServerList,
8882
+ error: Schema$1.Union([McpRegistryError, EnvironmentAuthorizationError])
8883
+ });
8884
+ /**
8785
8885
  * The board, then every change to it, for as long as the client stays
8786
8886
  * subscribed. The leading snapshot is what makes this sufficient on its own —
8787
8887
  * a client does not list first, so there is no window between the two in which
@@ -9159,7 +9259,7 @@ const WsSubscribeAuthAccessRpc = Rpc.make(WS_METHODS.subscribeAuthAccess, {
9159
9259
  error: Schema$1.Union([AuthAccessStreamError, EnvironmentAuthorizationError]),
9160
9260
  stream: true
9161
9261
  });
9162
- const WsRpcGroup = RpcGroup.make(WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetProviderUsageRpc, 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, WsTasksStartThreadRpc, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsAssetsReadRpc, WsAssetsSaveRpc, WsAssetsDeleteRpc, WsAssetsCreateLocalRpc, WsAssetsRemoveLocalRpc, WsSkillRegistrySearchRpc, WsSkillRegistryFetchRpc, WsMcpListRpc, WsMcpSaveRpc, WsMcpRemoveRpc, WsMcpSetSecretRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
9262
+ const WsRpcGroup = RpcGroup.make(WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetProviderUsageRpc, 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, WsTasksStartThreadRpc, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsAssetsReadRpc, WsAssetsSaveRpc, WsAssetsDeleteRpc, WsAssetsCreateLocalRpc, WsAssetsRemoveLocalRpc, WsSkillRegistrySearchRpc, WsSkillRegistryFetchRpc, WsMcpListRpc, WsMcpSaveRpc, WsMcpRemoveRpc, WsMcpSetSecretRpc, WsMcpOAuthBeginRpc, WsMcpOAuthDisconnectRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
9163
9263
  //#endregion
9164
9264
  //#region ../../packages/shared/src/oauthScope.ts
9165
9265
  const OAUTH_SCOPE_TOKEN = /^[\u0021\u0023-\u005b\u005d-\u007e]+$/u;
@@ -9305,7 +9405,7 @@ function deriveAuthClientMetadata(input) {
9305
9405
  //#endregion
9306
9406
  //#region src/auth/EnvironmentAuthPolicy.ts
9307
9407
  var EnvironmentAuthPolicy = class extends Context.Service()("@p4code/cli/auth/EnvironmentAuthPolicy") {};
9308
- const make$71 = Effect.gen(function* () {
9408
+ const make$72 = Effect.gen(function* () {
9309
9409
  const config = yield* ServerConfig$1;
9310
9410
  const isRemoteReachable = isRemoteReachableHost(config.host);
9311
9411
  const policy = config.mode === "desktop" ? isRemoteReachable ? "remote-reachable" : "desktop-managed-local" : isRemoteReachable ? "remote-reachable" : "loopback-browser";
@@ -9323,7 +9423,7 @@ const make$71 = Effect.gen(function* () {
9323
9423
  };
9324
9424
  return EnvironmentAuthPolicy.of({ getDescriptor: () => Effect.succeed(descriptor).pipe(Effect.withSpan("EnvironmentAuthPolicy.getDescriptor")) });
9325
9425
  });
9326
- const layer$67 = Layer.effect(EnvironmentAuthPolicy, make$71);
9426
+ const layer$68 = Layer.effect(EnvironmentAuthPolicy, make$72);
9327
9427
  //#endregion
9328
9428
  //#region src/persistence/Errors.ts
9329
9429
  function summarizeSchemaIssue(issue) {
@@ -9504,7 +9604,7 @@ function toPersistenceSqlOrDecodeError$6(sqlOperation, decodeOperation, correlat
9504
9604
  cause
9505
9605
  });
9506
9606
  }
9507
- const make$70 = Effect.gen(function* () {
9607
+ const make$71 = Effect.gen(function* () {
9508
9608
  const sql = yield* SqlClient.SqlClient;
9509
9609
  const createSessionRow = SqlSchema.void({
9510
9610
  Request: CreateAuthSessionInput,
@@ -9638,7 +9738,7 @@ const make$70 = Effect.gen(function* () {
9638
9738
  setLastConnectedAt
9639
9739
  };
9640
9740
  });
9641
- const layer$66 = Layer.effect(AuthSessionRepository, make$70);
9741
+ const layer$67 = Layer.effect(AuthSessionRepository, make$71);
9642
9742
  //#endregion
9643
9743
  //#region src/auth/ServerSecretStore.ts
9644
9744
  const secretStoreErrorContext = {
@@ -9705,7 +9805,7 @@ const isSecretStoreError = Schema$1.is(SecretStoreError);
9705
9805
  const isPlatformError = (value) => Predicate.isTagged(value, "PlatformError");
9706
9806
  const isSecretAlreadyExistsError = (error) => "cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists";
9707
9807
  var ServerSecretStore = class extends Context.Service()("@p4code/cli/auth/ServerSecretStore") {};
9708
- const make$69 = Effect.gen(function* () {
9808
+ const make$70 = Effect.gen(function* () {
9709
9809
  const crypto = yield* Crypto.Crypto;
9710
9810
  const fileSystem = yield* FileSystem.FileSystem;
9711
9811
  const path = yield* Path.Path;
@@ -9775,7 +9875,7 @@ const make$69 = Effect.gen(function* () {
9775
9875
  remove
9776
9876
  });
9777
9877
  });
9778
- const layer$65 = Layer.effect(ServerSecretStore, make$69);
9878
+ const layer$66 = Layer.effect(ServerSecretStore, make$70);
9779
9879
  //#endregion
9780
9880
  //#region src/auth/SessionStore.ts
9781
9881
  var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
@@ -10013,7 +10113,7 @@ function toAuthClientSession(input) {
10013
10113
  current: false
10014
10114
  };
10015
10115
  }
10016
- const make$68 = Effect.gen(function* () {
10116
+ const make$69 = Effect.gen(function* () {
10017
10117
  const crypto = yield* Crypto.Crypto;
10018
10118
  const serverConfig = yield* ServerConfig$1;
10019
10119
  const secretStore = yield* ServerSecretStore;
@@ -10327,7 +10427,7 @@ const make$68 = Effect.gen(function* () {
10327
10427
  markDisconnected
10328
10428
  });
10329
10429
  });
10330
- const layer$64 = Layer.effect(SessionStore, make$68).pipe(Layer.provideMerge(layer$66));
10430
+ const layer$65 = Layer.effect(SessionStore, make$69).pipe(Layer.provideMerge(layer$67));
10331
10431
  //#endregion
10332
10432
  //#region src/persistence/AuthPairingLinks.ts
10333
10433
  const AuthPairingLinkRecord = Schema$1.Struct({
@@ -10388,7 +10488,7 @@ function toPersistenceSqlOrDecodeError$5(sqlOperation, decodeOperation, correlat
10388
10488
  cause
10389
10489
  });
10390
10490
  }
10391
- const make$67 = Effect.gen(function* () {
10491
+ const make$68 = Effect.gen(function* () {
10392
10492
  const sql = yield* SqlClient.SqlClient;
10393
10493
  const createPairingLinkRow = SqlSchema.void({
10394
10494
  Request: CreateAuthPairingLinkInput,
@@ -10523,7 +10623,7 @@ const make$67 = Effect.gen(function* () {
10523
10623
  getByCredential
10524
10624
  };
10525
10625
  });
10526
- const layer$63 = Layer.effect(AuthPairingLinkRepository, make$67);
10626
+ const layer$64 = Layer.effect(AuthPairingLinkRepository, make$68);
10527
10627
  //#endregion
10528
10628
  //#region src/auth/PairingGrantStore.ts
10529
10629
  var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
@@ -10618,7 +10718,7 @@ const DEV_STARTUP_TTL_HOURS = Duration.hours(24);
10618
10718
  const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
10619
10719
  const PAIRING_TOKEN_LENGTH = 12;
10620
10720
  const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
10621
- const make$66 = Effect.gen(function* () {
10721
+ const make$67 = Effect.gen(function* () {
10622
10722
  const crypto = yield* Crypto.Crypto;
10623
10723
  const config = yield* ServerConfig$1;
10624
10724
  const pairingLinks = yield* AuthPairingLinkRepository;
@@ -10816,7 +10916,7 @@ const make$66 = Effect.gen(function* () {
10816
10916
  consume
10817
10917
  });
10818
10918
  });
10819
- const layer$62 = Layer.effect(PairingGrantStore, make$66).pipe(Layer.provideMerge(layer$63));
10919
+ const layer$63 = Layer.effect(PairingGrantStore, make$67).pipe(Layer.provideMerge(layer$64));
10820
10920
  //#endregion
10821
10921
  //#region src/persistence/Migrations/001_OrchestrationEvents.ts
10822
10922
  var _001_OrchestrationEvents_default = Effect.gen(function* () {
@@ -12671,7 +12771,7 @@ function parseBearerToken(request) {
12671
12771
  const token = header.slice(7).trim();
12672
12772
  return token.length > 0 ? token : null;
12673
12773
  }
12674
- const make$65 = Effect.gen(function* () {
12774
+ const make$66 = Effect.gen(function* () {
12675
12775
  const policy = yield* EnvironmentAuthPolicy;
12676
12776
  const bootstrapCredentials = yield* PairingGrantStore;
12677
12777
  const sessions = yield* SessionStore;
@@ -12866,9 +12966,9 @@ const make$65 = Effect.gen(function* () {
12866
12966
  issueStartupPairingUrl
12867
12967
  });
12868
12968
  });
12869
- const layer$61 = Layer.effect(EnvironmentAuth, make$65).pipe(Layer.provideMerge(layer$62), Layer.provideMerge(layer$64), Layer.provideMerge(layer$67));
12870
- const storageLayer = Layer.mergeAll(layer$65, layerConfig);
12871
- const runtimeLayer = layer$61.pipe(Layer.provideMerge(storageLayer));
12969
+ const layer$62 = Layer.effect(EnvironmentAuth, make$66).pipe(Layer.provideMerge(layer$63), Layer.provideMerge(layer$65), Layer.provideMerge(layer$68));
12970
+ const storageLayer = Layer.mergeAll(layer$66, layerConfig);
12971
+ const runtimeLayer = layer$62.pipe(Layer.provideMerge(storageLayer));
12872
12972
  //#endregion
12873
12973
  //#region src/cliAuthFormat.ts
12874
12974
  const newline = "\n";
@@ -13623,7 +13723,7 @@ const runWithEnvironmentAuth = (flags, run, options) => Effect.gen(function* ()
13623
13723
  }
13624
13724
  return yield* Effect.gen(function* () {
13625
13725
  return yield* run(yield* EnvironmentAuth);
13626
- }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$68(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
13726
+ }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$69(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
13627
13727
  });
13628
13728
  const ttlFlag = Flag.string("ttl").pipe(Flag.withSchema(DurationFromString), Flag.withDescription("TTL, for example `5m`, `1h`, `30d`, or `15 minutes`."), Flag.optional);
13629
13729
  const jsonFlag = Flag.boolean("json").pipe(Flag.withDescription("Emit JSON instead of human-readable output."), Flag.withDefault(false));
@@ -13763,7 +13863,7 @@ const DEFAULT_LIMITS = {
13763
13863
  windowMillis: FAILURE_WINDOW_MS,
13764
13864
  blockMillis: BLOCK_DURATION_MS
13765
13865
  };
13766
- const make$64 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
13866
+ const make$65 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
13767
13867
  const state = yield* Ref.make(initialThrottleState);
13768
13868
  return HubAuthThrottle.of({
13769
13869
  shouldRefuse: Effect.gen(function* () {
@@ -13777,7 +13877,7 @@ const make$64 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LI
13777
13877
  })
13778
13878
  });
13779
13879
  });
13780
- const layer$60 = Layer.effect(HubAuthThrottle, make$64());
13880
+ const layer$61 = Layer.effect(HubAuthThrottle, make$65());
13781
13881
  //#endregion
13782
13882
  //#region src/hub/HubAuth.ts
13783
13883
  /**
@@ -14576,7 +14676,7 @@ const deleteAssetRoute = HttpRouter.add("DELETE", "/assets/:kind/:name", respond
14576
14676
  const outcome = yield* (yield* AgentAssetRepository).deleteByName(requested);
14577
14677
  return outcome._tag === "conflict" ? assetConflict(Option.getOrNull(outcome.current)) : HttpServerResponse.empty({ status: 204 });
14578
14678
  })));
14579
- const layer$59 = Layer.mergeAll(healthRoute, listTasksRoute, getTaskRoute, createTaskRoute, updateTaskRoute, deleteTaskRoute, listAssetsRoute, getAssetRoute, putAssetRoute, deleteAssetRoute);
14679
+ const layer$60 = Layer.mergeAll(healthRoute, listTasksRoute, getTaskRoute, createTaskRoute, updateTaskRoute, deleteTaskRoute, listAssetsRoute, getAssetRoute, putAssetRoute, deleteAssetRoute);
14580
14680
  //#endregion
14581
14681
  //#region src/hub/Migrations/001_Tasks.ts
14582
14682
  /**
@@ -14855,7 +14955,7 @@ const announce = Layer.effectDiscard(Effect.gen(function* () {
14855
14955
  });
14856
14956
  if (isRemoteReachableHost(config.host)) yield* Effect.logWarning("Hub is reachable beyond loopback. The bearer token is the only control — repeated auth failures are not rate limited.", { host: config.host });
14857
14957
  }));
14858
- const makeHubLayer = HttpRouter.serve(layer$59).pipe(Layer.provideMerge(announce), Layer.provideMerge(HubHttpServerLive), Layer.provideMerge(layer$60), Layer.provideMerge(TaskRepositoryLive), Layer.provideMerge(AgentAssetRepositoryLive), Layer.provideMerge(HubPersistenceLive));
14958
+ const makeHubLayer = HttpRouter.serve(layer$60).pipe(Layer.provideMerge(announce), Layer.provideMerge(HubHttpServerLive), Layer.provideMerge(layer$61), Layer.provideMerge(TaskRepositoryLive), Layer.provideMerge(AgentAssetRepositoryLive), Layer.provideMerge(HubPersistenceLive));
14859
14959
  const runHub = Layer.launch(makeHubLayer);
14860
14960
  //#endregion
14861
14961
  //#region src/atomicWrite.ts
@@ -14979,7 +15079,7 @@ function stripDefaultServerSettings(current, defaults) {
14979
15079
  }
14980
15080
  return Object.is(current, defaults) ? void 0 : current;
14981
15081
  }
14982
- const make$63 = Effect.gen(function* () {
15082
+ const make$64 = Effect.gen(function* () {
14983
15083
  const { settingsPath } = yield* ServerConfig$1;
14984
15084
  const fs = yield* FileSystem.FileSystem;
14985
15085
  const pathService = yield* Path.Path;
@@ -15200,7 +15300,7 @@ const make$63 = Effect.gen(function* () {
15200
15300
  }
15201
15301
  };
15202
15302
  });
15203
- const layer$58 = Layer.effect(ServerSettingsService, make$63);
15303
+ const layer$59 = Layer.effect(ServerSettingsService, make$64);
15204
15304
  //#endregion
15205
15305
  //#region src/pathExpansion.ts
15206
15306
  /**
@@ -15409,6 +15509,464 @@ const discoverClaudeSkills = Effect.fn("discoverClaudeSkills")(function* (config
15409
15509
  }
15410
15510
  return [...skillsByName.values()].sort((left, right) => left.name.localeCompare(right.name));
15411
15511
  });
15512
+ //#endregion
15513
+ //#region src/mcp/McpOAuth.ts
15514
+ /**
15515
+ * OAuth sign-in for remote MCP servers, per the MCP authorization spec.
15516
+ *
15517
+ * The whole flow runs on this server: RFC 9728 protected-resource discovery,
15518
+ * RFC 8414 authorization-server metadata, RFC 7591 dynamic client
15519
+ * registration, PKCE, and the token exchange when the browser lands on
15520
+ * `/oauth/callback`. The panel only ever sees one string — the authorize URL —
15521
+ * because everything else in the flow is either a secret or only meaningful to
15522
+ * the machine the callback returns to.
15523
+ *
15524
+ * **Everything learned per server is one JSON secret** in `ServerSecretStore`
15525
+ * (`mcp.<server>.oauth`): client id, endpoints, tokens. Registrations sync
15526
+ * between machines; this record deliberately does not, which is what makes
15527
+ * "each machine signs in for itself" true by construction rather than policy.
15528
+ *
15529
+ * @module mcp/McpOAuth
15530
+ */
15531
+ /** How long a probe's verdict is trusted before the server is asked again. */
15532
+ const PROBE_CACHE_TTL_MS = 300 * 1e3;
15533
+ /** An authorize URL nobody returned from within this window is abandoned. */
15534
+ const PENDING_FLOW_TTL_MS = 600 * 1e3;
15535
+ /** A token this close to expiry is refreshed rather than handed to a session. */
15536
+ const TOKEN_EXPIRY_MARGIN_MS = 60 * 1e3;
15537
+ /** Outbound discovery, registration and token requests all use this cap. */
15538
+ const OAUTH_REQUEST_TIMEOUT_MS = 10 * 1e3;
15539
+ const CODE_VERIFIER_BYTES = 32;
15540
+ const STATE_BYTES = 16;
15541
+ /** Sent in the probe's initialize request; any spec revision the server accepts will do. */
15542
+ const MCP_PROBE_PROTOCOL_VERSION = "2025-06-18";
15543
+ /** What the authorization server's consent screen shows. See `registerClient`. */
15544
+ const OAUTH_CLIENT_NAME = "Claude Code (p4code)";
15545
+ /** The path the environment server answers on; also sent as the redirect URI. */
15546
+ const OAUTH_CALLBACK_PATH = "/oauth/callback";
15547
+ const StoredTokens = Schema$1.Struct({
15548
+ accessToken: Schema$1.String,
15549
+ refreshToken: Schema$1.optional(Schema$1.String),
15550
+ /** Epoch milliseconds. Absent when the server did not say. */
15551
+ expiresAt: Schema$1.optional(Schema$1.Number)
15552
+ });
15553
+ /**
15554
+ * Everything this machine holds about one server's OAuth relationship.
15555
+ *
15556
+ * Endpoints and client id live beside the tokens so that disconnect can drop
15557
+ * the tokens and a later reconnect can skip re-registration — a client id is
15558
+ * not a credential, but re-registering on every sign-in litters the
15559
+ * authorization server with one-use clients.
15560
+ */
15561
+ const StoredOAuth = Schema$1.Struct({
15562
+ clientId: Schema$1.String,
15563
+ clientSecret: Schema$1.optional(Schema$1.String),
15564
+ authorizationEndpoint: Schema$1.String,
15565
+ tokenEndpoint: Schema$1.String,
15566
+ /** RFC 8707 resource indicator: the canonical MCP server URL. */
15567
+ resource: Schema$1.optional(Schema$1.String),
15568
+ scope: Schema$1.optional(Schema$1.String),
15569
+ tokens: Schema$1.optional(StoredTokens)
15570
+ });
15571
+ const StoredOAuthFromJson = Schema$1.fromJsonString(StoredOAuth);
15572
+ const decodeStored = Schema$1.decodeUnknownExit(StoredOAuthFromJson);
15573
+ const encodeStored = Schema$1.encodeSync(StoredOAuthFromJson);
15574
+ /**
15575
+ * Parse a `WWW-Authenticate` challenge into its scheme and parameters.
15576
+ *
15577
+ * Handles the shape MCP servers actually send — `Bearer` followed by
15578
+ * comma-separated `key="value"` or bare `key=value` pairs. Exported for tests;
15579
+ * a wrong parse here turns "needs sign-in" into "broken server".
15580
+ */
15581
+ function parseWwwAuthenticate(header) {
15582
+ const trimmed = header.trim();
15583
+ const spaceIndex = trimmed.indexOf(" ");
15584
+ const scheme = (spaceIndex === -1 ? trimmed : trimmed.slice(0, spaceIndex)).toLowerCase();
15585
+ const rest = spaceIndex === -1 ? "" : trimmed.slice(spaceIndex + 1);
15586
+ const params = {};
15587
+ for (const match of rest.matchAll(/([a-zA-Z0-9_-]+)\s*=\s*(?:"([^"]*)"|([^\s,]+))/gu)) {
15588
+ const key = match[1];
15589
+ const value = match[2] ?? match[3];
15590
+ if (key !== void 0 && value !== void 0) params[key.toLowerCase()] = value;
15591
+ }
15592
+ return {
15593
+ scheme,
15594
+ params
15595
+ };
15596
+ }
15597
+ /**
15598
+ * The RFC 8414 lookup locations for an issuer, in the order worth trying.
15599
+ *
15600
+ * Path insertion first — `https://a/.well-known/oauth-authorization-server/x`
15601
+ * for issuer `https://a/x` — then the OIDC variants, because real servers are
15602
+ * split between the two specs and a miss costs one request.
15603
+ */
15604
+ function authServerMetadataUrls(issuer) {
15605
+ const url = new URL(issuer);
15606
+ const path = url.pathname === "/" ? "" : url.pathname.replace(/\/$/u, "");
15607
+ return [
15608
+ `${url.origin}/.well-known/oauth-authorization-server${path}`,
15609
+ `${url.origin}/.well-known/openid-configuration${path}`,
15610
+ ...path === "" ? [] : [`${url.origin}${path}/.well-known/openid-configuration`]
15611
+ ];
15612
+ }
15613
+ const AuthServerMetadata = Schema$1.Struct({
15614
+ authorization_endpoint: Schema$1.String,
15615
+ token_endpoint: Schema$1.String,
15616
+ registration_endpoint: Schema$1.optional(Schema$1.String),
15617
+ scopes_supported: Schema$1.optional(Schema$1.Array(Schema$1.String))
15618
+ });
15619
+ const ProtectedResourceMetadata = Schema$1.Struct({
15620
+ authorization_servers: Schema$1.optional(Schema$1.Array(Schema$1.String)),
15621
+ scopes_supported: Schema$1.optional(Schema$1.Array(Schema$1.String))
15622
+ });
15623
+ const ClientRegistrationResponse = Schema$1.Struct({
15624
+ client_id: Schema$1.String,
15625
+ client_secret: Schema$1.optional(Schema$1.String)
15626
+ });
15627
+ const TokenResponse = Schema$1.Struct({
15628
+ access_token: Schema$1.String,
15629
+ refresh_token: Schema$1.optional(Schema$1.String),
15630
+ expires_in: Schema$1.optional(Schema$1.Number)
15631
+ });
15632
+ const decodeJsonBody = Schema$1.decodeUnknownExit(Schema$1.UnknownFromJsonString);
15633
+ const decodeAuthServerMetadata = Schema$1.decodeUnknownExit(AuthServerMetadata);
15634
+ const decodeProtectedResourceMetadata = Schema$1.decodeUnknownExit(ProtectedResourceMetadata);
15635
+ const decodeClientRegistration = Schema$1.decodeUnknownExit(ClientRegistrationResponse);
15636
+ const decodeTokenResponse = Schema$1.decodeUnknownExit(TokenResponse);
15637
+ var McpOAuth = class extends Context.Service()("@p4code/cli/mcp/McpOAuth") {};
15638
+ const registryError = (detail) => new McpRegistryError({ detail });
15639
+ const make$63 = Effect.gen(function* () {
15640
+ const config = yield* ServerConfig$1;
15641
+ const secrets = yield* ServerSecretStore;
15642
+ const http = yield* HttpClient.HttpClient;
15643
+ const crypto = yield* Crypto.Crypto;
15644
+ const pendingFlows = yield* Ref.make(/* @__PURE__ */ new Map());
15645
+ const probeCache = yield* Ref.make(/* @__PURE__ */ new Map());
15646
+ const redirectUri = `http://127.0.0.1:${config.port}${OAUTH_CALLBACK_PATH}`;
15647
+ const nowMillis = Effect.map(DateTime.now, DateTime.toEpochMillis);
15648
+ const readRecord = Effect.fn("McpOAuth.readRecord")(function* (server) {
15649
+ const stored = yield* secrets.get(mcpOAuthStorageName(server)).pipe(Effect.orElseSucceed(() => Option.none()));
15650
+ if (Option.isNone(stored)) return Option.none();
15651
+ const decoded = decodeStored(new TextDecoder().decode(stored.value));
15652
+ return decoded._tag === "Success" ? Option.some(decoded.value) : Option.none();
15653
+ });
15654
+ const writeRecord = Effect.fn("McpOAuth.writeRecord")(function* (server, record) {
15655
+ yield* secrets.set(mcpOAuthStorageName(server), new TextEncoder().encode(encodeStored(record))).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth record write failed", {
15656
+ cause,
15657
+ server
15658
+ })));
15659
+ });
15660
+ const fetchJson = Effect.fn("McpOAuth.fetchJson")(function* (request) {
15661
+ const response = yield* http.execute(HttpClientRequest.setHeader(request, "accept", "application/json")).pipe(Effect.timeout(OAUTH_REQUEST_TIMEOUT_MS));
15662
+ const text = yield* response.text;
15663
+ const parsed = decodeJsonBody(text);
15664
+ return {
15665
+ status: response.status,
15666
+ json: parsed._tag === "Success" ? parsed.value : void 0
15667
+ };
15668
+ });
15669
+ /**
15670
+ * Ask the server, without credentials, whether it wants any.
15671
+ *
15672
+ * A real MCP initialize request rather than a bare GET, because the GET is
15673
+ * not part of the streamable-http contract and real servers 405 it — Figma
15674
+ * does — while answering the initialize POST with the 401 challenge this
15675
+ * probe exists to see.
15676
+ */
15677
+ const probe = Effect.fn("McpOAuth.probe")(function* (registration) {
15678
+ const now = yield* nowMillis;
15679
+ const cached = (yield* Ref.get(probeCache)).get(registration.url);
15680
+ if (cached !== void 0 && now - cached.at < PROBE_CACHE_TTL_MS) return cached;
15681
+ const verdict = yield* http.execute(HttpClientRequest.bodyJsonUnsafe(HttpClientRequest.setHeaders(HttpClientRequest.post(registration.url), {
15682
+ ...registration.headers,
15683
+ accept: "application/json, text/event-stream"
15684
+ }), {
15685
+ jsonrpc: "2.0",
15686
+ id: 0,
15687
+ method: "initialize",
15688
+ params: {
15689
+ protocolVersion: MCP_PROBE_PROTOCOL_VERSION,
15690
+ capabilities: {},
15691
+ clientInfo: {
15692
+ name: "p4code",
15693
+ version: "0.0.0"
15694
+ }
15695
+ }
15696
+ })).pipe(Effect.timeout(OAUTH_REQUEST_TIMEOUT_MS), Effect.map((response) => {
15697
+ const challengeHeader = response.headers["www-authenticate"];
15698
+ const challenge = response.status === 401 && challengeHeader !== void 0 ? parseWwwAuthenticate(challengeHeader) : void 0;
15699
+ return {
15700
+ at: now,
15701
+ requiresAuth: response.status === 401,
15702
+ resourceMetadataUrl: challenge?.params["resource_metadata"],
15703
+ scope: challenge?.params["scope"]
15704
+ };
15705
+ }), Effect.orElseSucceed(() => ({
15706
+ at: now,
15707
+ requiresAuth: false,
15708
+ resourceMetadataUrl: void 0,
15709
+ scope: void 0
15710
+ })));
15711
+ yield* Ref.update(probeCache, (cache) => {
15712
+ const next = new Map(cache);
15713
+ next.set(registration.url, verdict);
15714
+ return next;
15715
+ });
15716
+ return verdict;
15717
+ });
15718
+ const forgetProbe = (url) => Ref.update(probeCache, (cache) => {
15719
+ const next = new Map(cache);
15720
+ next.delete(url);
15721
+ return next;
15722
+ });
15723
+ const statusFor = (registration) => Effect.gen(function* () {
15724
+ const record = yield* readRecord(registration.name);
15725
+ if (Option.isSome(record) && record.value.tokens !== void 0) return Option.some({
15726
+ required: true,
15727
+ connected: true
15728
+ });
15729
+ if ((yield* probe(registration)).requiresAuth || Option.isSome(record)) return Option.some({
15730
+ required: true,
15731
+ connected: false
15732
+ });
15733
+ return Option.none();
15734
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth status failed", {
15735
+ cause,
15736
+ server: registration.name
15737
+ }).pipe(Effect.as(Option.none()))));
15738
+ /**
15739
+ * Find the authorization server for an MCP endpoint.
15740
+ *
15741
+ * RFC 9728 first: the 401 challenge names a protected-resource metadata URL,
15742
+ * which names the authorization servers. When the challenge does not say —
15743
+ * the spec's fallback — the MCP origin itself is tried as the issuer.
15744
+ */
15745
+ const discover = Effect.fn("McpOAuth.discover")(function* (registration) {
15746
+ yield* forgetProbe(registration.url);
15747
+ const verdict = yield* probe(registration);
15748
+ let issuers = [new URL(registration.url).origin];
15749
+ let resourceScopes;
15750
+ const metadataUrl = verdict.resourceMetadataUrl ?? `${new URL(registration.url).origin}/.well-known/oauth-protected-resource${new URL(registration.url).pathname.replace(/\/$/u, "")}`;
15751
+ const resourceMetadata = yield* fetchJson(HttpClientRequest.get(metadataUrl)).pipe(Effect.map(({ status, json }) => {
15752
+ if (status !== 200) return void 0;
15753
+ const decoded = decodeProtectedResourceMetadata(json);
15754
+ return decoded._tag === "Success" ? decoded.value : void 0;
15755
+ }), Effect.orElseSucceed(() => void 0));
15756
+ if (resourceMetadata !== void 0) {
15757
+ if ((resourceMetadata.authorization_servers?.length ?? 0) > 0) issuers = resourceMetadata.authorization_servers ?? issuers;
15758
+ resourceScopes = resourceMetadata.scopes_supported;
15759
+ }
15760
+ for (const issuer of issuers) for (const candidate of authServerMetadataUrls(issuer)) {
15761
+ const metadata = yield* fetchJson(HttpClientRequest.get(candidate)).pipe(Effect.map(({ status, json }) => {
15762
+ if (status !== 200) return void 0;
15763
+ const decoded = decodeAuthServerMetadata(json);
15764
+ return decoded._tag === "Success" ? decoded.value : void 0;
15765
+ }), Effect.orElseSucceed(() => void 0));
15766
+ if (metadata !== void 0) return {
15767
+ metadata,
15768
+ scope: verdict.scope ?? (resourceScopes !== void 0 && resourceScopes.length > 0 ? resourceScopes.join(" ") : void 0)
15769
+ };
15770
+ }
15771
+ return yield* registryError(`Could not discover an OAuth authorization server for ${registration.url}.`);
15772
+ });
15773
+ const registerClient = Effect.fn("McpOAuth.registerClient")(function* (metadata) {
15774
+ if (metadata.registration_endpoint === void 0) return yield* registryError("The authorization server does not support dynamic client registration.");
15775
+ const { status, json } = yield* fetchJson(HttpClientRequest.bodyJsonUnsafe(HttpClientRequest.post(metadata.registration_endpoint), {
15776
+ client_name: OAUTH_CLIENT_NAME,
15777
+ redirect_uris: [redirectUri],
15778
+ grant_types: ["authorization_code", "refresh_token"],
15779
+ response_types: ["code"],
15780
+ token_endpoint_auth_method: "none"
15781
+ })).pipe(Effect.mapError(() => registryError("Client registration request failed.")));
15782
+ if (status !== 200 && status !== 201) return yield* registryError(`Client registration was rejected with status ${status}.`);
15783
+ const decoded = decodeClientRegistration(json);
15784
+ if (decoded._tag !== "Success") return yield* registryError("Client registration answered an unexpected shape.");
15785
+ return decoded.value;
15786
+ });
15787
+ const randomUrlSafe = (bytes) => crypto.randomBytes(bytes).pipe(Effect.map(Encoding.encodeBase64Url));
15788
+ const begin = (registration) => Effect.gen(function* () {
15789
+ const { metadata, scope } = yield* discover(registration);
15790
+ const existing = yield* readRecord(registration.name);
15791
+ const reusable = Option.filter(existing, (record) => record.tokenEndpoint === metadata.token_endpoint && record.authorizationEndpoint === metadata.authorization_endpoint);
15792
+ const client = Option.isSome(reusable) ? {
15793
+ client_id: reusable.value.clientId,
15794
+ client_secret: reusable.value.clientSecret
15795
+ } : yield* registerClient(metadata);
15796
+ const codeVerifier = yield* randomUrlSafe(CODE_VERIFIER_BYTES).pipe(Effect.mapError(() => registryError("Could not generate a PKCE verifier.")));
15797
+ const challengeBytes = yield* crypto.digest("SHA-256", new TextEncoder().encode(codeVerifier)).pipe(Effect.mapError(() => registryError("Could not hash the PKCE verifier.")));
15798
+ const state = yield* randomUrlSafe(STATE_BYTES).pipe(Effect.mapError(() => registryError("Could not generate a state value.")));
15799
+ const record = {
15800
+ clientId: client.client_id,
15801
+ ...client.client_secret === void 0 ? {} : { clientSecret: client.client_secret },
15802
+ authorizationEndpoint: metadata.authorization_endpoint,
15803
+ tokenEndpoint: metadata.token_endpoint,
15804
+ resource: registration.url,
15805
+ ...scope === void 0 ? {} : { scope }
15806
+ };
15807
+ yield* writeRecord(registration.name, record);
15808
+ const startedAt = yield* nowMillis;
15809
+ yield* Ref.update(pendingFlows, (flows) => {
15810
+ const next = new Map([...flows].filter(([, flow]) => startedAt - flow.startedAt < PENDING_FLOW_TTL_MS));
15811
+ next.set(state, {
15812
+ server: registration.name,
15813
+ codeVerifier,
15814
+ redirectUri,
15815
+ record,
15816
+ startedAt
15817
+ });
15818
+ return next;
15819
+ });
15820
+ const authorizeUrl = new URL(metadata.authorization_endpoint);
15821
+ authorizeUrl.searchParams.set("response_type", "code");
15822
+ authorizeUrl.searchParams.set("client_id", client.client_id);
15823
+ authorizeUrl.searchParams.set("redirect_uri", redirectUri);
15824
+ authorizeUrl.searchParams.set("state", state);
15825
+ authorizeUrl.searchParams.set("code_challenge", Encoding.encodeBase64Url(challengeBytes));
15826
+ authorizeUrl.searchParams.set("code_challenge_method", "S256");
15827
+ authorizeUrl.searchParams.set("resource", registration.url);
15828
+ if (scope !== void 0) authorizeUrl.searchParams.set("scope", scope);
15829
+ return { authorizeUrl: authorizeUrl.toString() };
15830
+ });
15831
+ const exchangeToken = Effect.fn("McpOAuth.exchangeToken")(function* (flow, code) {
15832
+ const { status, json } = yield* fetchJson(HttpClientRequest.bodyUrlParams(HttpClientRequest.post(flow.record.tokenEndpoint), {
15833
+ grant_type: "authorization_code",
15834
+ code,
15835
+ redirect_uri: flow.redirectUri,
15836
+ client_id: flow.record.clientId,
15837
+ code_verifier: flow.codeVerifier,
15838
+ ...flow.record.clientSecret === void 0 ? {} : { client_secret: flow.record.clientSecret },
15839
+ ...flow.record.resource === void 0 ? {} : { resource: flow.record.resource }
15840
+ }));
15841
+ if (status !== 200) return yield* Effect.fail(`token endpoint answered status ${status}`);
15842
+ const decoded = decodeTokenResponse(json);
15843
+ if (decoded._tag !== "Success") return yield* Effect.fail("token endpoint answered an unexpected shape");
15844
+ return decoded.value;
15845
+ });
15846
+ const handleCallback = (params) => Effect.gen(function* () {
15847
+ if (params.state === void 0) return {
15848
+ ok: false,
15849
+ detail: "The redirect carried no state value."
15850
+ };
15851
+ const flow = (yield* Ref.get(pendingFlows)).get(params.state);
15852
+ if (flow === void 0) return {
15853
+ ok: false,
15854
+ detail: "This sign-in is unknown or has expired. Start it again from settings."
15855
+ };
15856
+ yield* Ref.update(pendingFlows, (current) => {
15857
+ const next = new Map(current);
15858
+ next.delete(params.state);
15859
+ return next;
15860
+ });
15861
+ if (params.error !== void 0) return {
15862
+ ok: false,
15863
+ detail: params.errorDescription ?? `The authorization server answered: ${params.error}.`
15864
+ };
15865
+ if (params.code === void 0) return {
15866
+ ok: false,
15867
+ detail: "The redirect carried no authorization code."
15868
+ };
15869
+ const now = yield* nowMillis;
15870
+ const exchanged = yield* exchangeToken(flow, params.code).pipe(Effect.map((response) => ({
15871
+ failed: false,
15872
+ response
15873
+ })), Effect.catch((detail) => Effect.succeed({
15874
+ failed: true,
15875
+ detail: typeof detail === "string" ? detail : "the token request failed"
15876
+ })));
15877
+ if (exchanged.failed) {
15878
+ yield* Effect.logWarning("mcp oauth token exchange failed", {
15879
+ server: flow.server,
15880
+ detail: exchanged.detail
15881
+ });
15882
+ return {
15883
+ ok: false,
15884
+ detail: `Token exchange failed: ${exchanged.detail}.`
15885
+ };
15886
+ }
15887
+ yield* writeRecord(flow.server, {
15888
+ ...flow.record,
15889
+ tokens: {
15890
+ accessToken: exchanged.response.access_token,
15891
+ ...exchanged.response.refresh_token === void 0 ? {} : { refreshToken: exchanged.response.refresh_token },
15892
+ ...exchanged.response.expires_in === void 0 ? {} : { expiresAt: now + exchanged.response.expires_in * 1e3 }
15893
+ }
15894
+ });
15895
+ return {
15896
+ ok: true,
15897
+ server: flow.server
15898
+ };
15899
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth callback failed", { cause }).pipe(Effect.as({
15900
+ ok: false,
15901
+ detail: "The sign-in failed. See the server log."
15902
+ }))));
15903
+ const disconnect = (name) => Effect.gen(function* () {
15904
+ const record = yield* readRecord(name);
15905
+ if (Option.isNone(record)) return;
15906
+ const { tokens: _dropped, ...rest } = record.value;
15907
+ yield* writeRecord(name, rest);
15908
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth disconnect failed", {
15909
+ cause,
15910
+ server: name
15911
+ }).pipe(Effect.asVoid)));
15912
+ const refreshTokens = Effect.fn("McpOAuth.refreshTokens")(function* (server, record, refreshToken) {
15913
+ const { status, json } = yield* fetchJson(HttpClientRequest.bodyUrlParams(HttpClientRequest.post(record.tokenEndpoint), {
15914
+ grant_type: "refresh_token",
15915
+ refresh_token: refreshToken,
15916
+ client_id: record.clientId,
15917
+ ...record.clientSecret === void 0 ? {} : { client_secret: record.clientSecret },
15918
+ ...record.resource === void 0 ? {} : { resource: record.resource }
15919
+ }));
15920
+ if (status !== 200) return yield* Effect.fail(`status ${status}`);
15921
+ const decoded = decodeTokenResponse(json);
15922
+ if (decoded._tag !== "Success") return yield* Effect.fail("unexpected shape");
15923
+ const response = decoded.value;
15924
+ const now = yield* nowMillis;
15925
+ const tokens = {
15926
+ accessToken: response.access_token,
15927
+ refreshToken: response.refresh_token ?? refreshToken,
15928
+ ...response.expires_in === void 0 ? {} : { expiresAt: now + response.expires_in * 1e3 }
15929
+ };
15930
+ yield* writeRecord(server, {
15931
+ ...record,
15932
+ tokens
15933
+ });
15934
+ return tokens.accessToken;
15935
+ });
15936
+ const accessTokenFor = (registration) => Effect.gen(function* () {
15937
+ const record = yield* readRecord(registration.name);
15938
+ if (Option.isNone(record) || record.value.tokens === void 0) return Option.none();
15939
+ const tokens = record.value.tokens;
15940
+ const now = yield* nowMillis;
15941
+ if (!(tokens.expiresAt !== void 0 && now >= tokens.expiresAt - TOKEN_EXPIRY_MARGIN_MS)) return Option.some(tokens.accessToken);
15942
+ if (tokens.refreshToken === void 0) return Option.none();
15943
+ return yield* refreshTokens(registration.name, record.value, tokens.refreshToken).pipe(Effect.map(Option.some), Effect.catchCause((cause) => Effect.logWarning("mcp oauth refresh failed", {
15944
+ cause,
15945
+ server: registration.name
15946
+ }).pipe(Effect.as(Option.none()))));
15947
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth token lookup failed", {
15948
+ cause,
15949
+ server: registration.name
15950
+ }).pipe(Effect.as(Option.none()))));
15951
+ return {
15952
+ statusFor,
15953
+ begin,
15954
+ handleCallback,
15955
+ disconnect,
15956
+ accessTokenFor
15957
+ };
15958
+ });
15959
+ const layer$58 = Layer.effect(McpOAuth, make$63);
15960
+ Layer.succeed(McpOAuth, {
15961
+ statusFor: () => Effect.succeed(Option.none()),
15962
+ begin: () => Effect.fail(new McpRegistryError({ detail: "OAuth sign-in is not available." })),
15963
+ handleCallback: () => Effect.succeed({
15964
+ ok: false,
15965
+ detail: "OAuth sign-in is not available."
15966
+ }),
15967
+ disconnect: () => Effect.void,
15968
+ accessTokenFor: () => Effect.succeed(Option.none())
15969
+ });
15412
15970
  /** Extension for a registration file, so the sync's asset name is `<server>.json`. */
15413
15971
  const MCP_REGISTRATION_SUFFIX = ".json";
15414
15972
  const RegistrationFromJson$1 = Schema$1.fromJsonString(McpServerRegistration);
@@ -15419,6 +15977,7 @@ const slotsOf = (registration) => registration.secrets ?? [];
15419
15977
  const make$62 = Effect.gen(function* () {
15420
15978
  const config = yield* ServerConfig$1;
15421
15979
  const secrets = yield* ServerSecretStore;
15980
+ const oauth = yield* McpOAuth;
15422
15981
  const fileSystem = yield* FileSystem.FileSystem;
15423
15982
  const path = yield* Path.Path;
15424
15983
  const services = yield* Effect.context();
@@ -15461,10 +16020,13 @@ const make$62 = Effect.gen(function* () {
15461
16020
  const value = yield* readSecret(registration.name, slot);
15462
16021
  if (Option.isNone(value)) missingSecrets.push(slot);
15463
16022
  }
16023
+ const oauthStatus = registration.transport === "stdio" || !registration.enabled ? Option.none() : yield* oauth.statusFor(registration);
16024
+ const needsSignIn = Option.isSome(oauthStatus) && !oauthStatus.value.connected;
15464
16025
  statuses.push({
15465
16026
  registration,
15466
16027
  missingSecrets,
15467
- ready: registration.enabled && missingSecrets.length === 0
16028
+ ready: registration.enabled && missingSecrets.length === 0 && !needsSignIn,
16029
+ ...Option.isSome(oauthStatus) ? { oauth: oauthStatus.value } : {}
15468
16030
  });
15469
16031
  }
15470
16032
  return statuses;
@@ -15484,6 +16046,7 @@ const make$62 = Effect.gen(function* () {
15484
16046
  target: slot.target,
15485
16047
  key: slot.key
15486
16048
  })).pipe(Effect.ignore);
16049
+ yield* secrets.remove(mcpOAuthStorageName(name)).pipe(Effect.ignore);
15487
16050
  }).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("mcp registry remove failed", { cause }).pipe(Effect.asVoid)));
15488
16051
  const setSecret = ({ name, slot, value }) => secrets.set(mcpSecretStorageName({
15489
16052
  server: name,
@@ -15513,6 +16076,11 @@ const make$62 = Effect.gen(function* () {
15513
16076
  else env[slot.key] = value.value;
15514
16077
  }
15515
16078
  if (!complete) continue;
16079
+ const hasExplicitAuthorization = Object.keys(headers).some((key) => key.toLowerCase() === "authorization");
16080
+ if (registration.transport !== "stdio" && !hasExplicitAuthorization) {
16081
+ const token = yield* oauth.accessTokenFor(registration);
16082
+ if (Option.isSome(token)) headers["Authorization"] = `Bearer ${token.value}`;
16083
+ }
15516
16084
  resolved[registration.name] = registration.transport === "stdio" ? {
15517
16085
  type: "stdio",
15518
16086
  command: registration.command,
@@ -17245,7 +17813,7 @@ const make$59 = Effect.gen(function* () {
17245
17813
  const layer$54 = Layer.effect(AssetSync, make$59);
17246
17814
  //#endregion
17247
17815
  //#region src/cli/assets.ts
17248
- const SyncServicesLive = layer$54.pipe(Layer.provideMerge(layer$55), Layer.provideMerge(layer$56), Layer.provideMerge(layer$58.pipe(Layer.provide(layer$65))), Layer.provideMerge(layer$65), Layer.provideMerge(FetchHttpClient.layer));
17816
+ const SyncServicesLive = layer$54.pipe(Layer.provideMerge(layer$55), Layer.provideMerge(layer$56), Layer.provideMerge(layer$59.pipe(Layer.provide(layer$66))), Layer.provideMerge(layer$66), Layer.provideMerge(FetchHttpClient.layer));
17249
17817
  const runWithSync = (flags, run) => Effect.gen(function* () {
17250
17818
  const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
17251
17819
  return yield* Effect.gen(function* () {
@@ -17253,7 +17821,7 @@ const runWithSync = (flags, run) => Effect.gen(function* () {
17253
17821
  sync: yield* AssetSync,
17254
17822
  link: yield* HubLink
17255
17823
  });
17256
- }).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$68(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
17824
+ }).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$69(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
17257
17825
  });
17258
17826
  const STATE_LABEL = {
17259
17827
  synced: "synced",
@@ -23936,11 +24504,66 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
23936
24504
  });
23937
24505
  const OrchestrationProjectionSnapshotQueryLive = Layer.effect(ProjectionSnapshotQuery, makeProjectionSnapshotQuery);
23938
24506
  //#endregion
24507
+ //#region src/orchestration/Services/ThreadEventStream.ts
24508
+ var ThreadEventStreamService = class extends Context.Service()("@p4code/cli/orchestration/Services/ThreadEventStream/ThreadEventStreamService") {};
24509
+ //#endregion
24510
+ //#region src/orchestration/Layers/ThreadEventStream.ts
24511
+ const makeThreadEventStream = Effect.gen(function* () {
24512
+ const engine = yield* OrchestrationEngineService;
24513
+ const isThreadEvent = (threadId) => (event) => event.aggregateKind === "thread" && event.aggregateId === threadId;
24514
+ const watch = (input) => Stream.unwrap(Effect.gen(function* () {
24515
+ const matchesThread = isThreadEvent(input.threadId);
24516
+ const liveBuffer = yield* Queue.unbounded();
24517
+ yield* Effect.forkScoped(engine.streamDomainEvents.pipe(Stream.filter(matchesThread), Stream.runForEach((event) => Queue.offer(liveBuffer, event))), { startImmediately: true });
24518
+ const afterSequence = input.afterSequence ?? 0;
24519
+ const headSequence = yield* engine.latestSequence;
24520
+ const replayGap = headSequence - afterSequence;
24521
+ const catchUp = replayGap > 0 ? engine.readEvents(afterSequence, replayGap).pipe(Stream.filter(matchesThread)) : Stream.empty;
24522
+ const caughtUp = Stream.make({
24523
+ kind: "caught-up",
24524
+ headSequence
24525
+ });
24526
+ const live = Stream.fromQueue(liveBuffer).pipe(Stream.filter((event) => event.sequence > headSequence));
24527
+ return Stream.concat(catchUp.pipe(Stream.map((event) => ({
24528
+ kind: "event",
24529
+ event
24530
+ }))), Stream.concat(caughtUp, live.pipe(Stream.map((event) => ({
24531
+ kind: "event",
24532
+ event
24533
+ })))));
24534
+ }));
24535
+ const read = (input) => Effect.gen(function* () {
24536
+ const matchesThread = isThreadEvent(input.threadId);
24537
+ const afterSequence = input.afterSequence ?? 0;
24538
+ const headSequence = yield* engine.latestSequence;
24539
+ const replayGap = headSequence - afterSequence;
24540
+ if (replayGap <= 0) return {
24541
+ events: [],
24542
+ headSequence,
24543
+ hasMore: false
24544
+ };
24545
+ const collected = yield* engine.readEvents(afterSequence, replayGap).pipe(Stream.filter(matchesThread), Stream.take(input.limit + 1), Stream.runCollect);
24546
+ const events = Array.from(collected);
24547
+ const hasMore = events.length > input.limit;
24548
+ return {
24549
+ events: hasMore ? events.slice(0, input.limit) : events,
24550
+ headSequence,
24551
+ hasMore
24552
+ };
24553
+ });
24554
+ return ThreadEventStreamService.of({
24555
+ watch,
24556
+ read
24557
+ });
24558
+ });
24559
+ const ThreadEventStreamLive = Layer.effect(ThreadEventStreamService, makeThreadEventStream);
24560
+ //#endregion
23939
24561
  //#region src/orchestration/runtimeLayer.ts
23940
24562
  const OrchestrationEventInfrastructureLayerLive = Layer.mergeAll(OrchestrationEventStoreLive, OrchestrationCommandReceiptRepositoryLive);
23941
24563
  const OrchestrationProjectionPipelineLayerLive = OrchestrationProjectionPipelineLive.pipe(Layer.provide(OrchestrationEventStoreLive));
23942
24564
  const OrchestrationInfrastructureLayerLive = Layer.mergeAll(OrchestrationProjectionSnapshotQueryLive, OrchestrationEventInfrastructureLayerLive, OrchestrationProjectionPipelineLayerLive);
23943
- const OrchestrationLayerLive = Layer.mergeAll(OrchestrationInfrastructureLayerLive, OrchestrationEngineLive.pipe(Layer.provide(OrchestrationInfrastructureLayerLive)));
24565
+ const OrchestrationEngineLayerLive = OrchestrationEngineLive.pipe(Layer.provide(OrchestrationInfrastructureLayerLive));
24566
+ const OrchestrationLayerLive = Layer.mergeAll(OrchestrationInfrastructureLayerLive, OrchestrationEngineLayerLive, ThreadEventStreamLive.pipe(Layer.provide(OrchestrationEngineLayerLive)));
23944
24567
  //#endregion
23945
24568
  //#region ../../packages/shared/src/keybindings.ts
23946
24569
  const DEFAULT_KEYBINDINGS = [
@@ -27210,7 +27833,7 @@ const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () {
27210
27833
  title: "New thread",
27211
27834
  modelSelection: nextProjectDefaultModelSelection,
27212
27835
  interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
27213
- compressMode: "off",
27836
+ compressMode: DEFAULT_COMPRESS_MODE,
27214
27837
  runtimeMode: "full-access",
27215
27838
  branch: null,
27216
27839
  worktreePath: null,
@@ -27784,7 +28407,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
27784
28407
  });
27785
28408
  yield* Console.log(output);
27786
28409
  }));
27787
- const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$68(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)));
28410
+ const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$69(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)));
27788
28411
  return yield* Effect.gen(function* () {
27789
28412
  const snapshot = yield* getOfflineSnapshot();
27790
28413
  const orchestrationEngine = yield* OrchestrationEngineService;
@@ -27795,7 +28418,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
27795
28418
  });
27796
28419
  yield* Console.log(output);
27797
28420
  }).pipe(Effect.provide(offlineRuntimeLayer));
27798
- }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$43).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$68(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
28421
+ }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$43).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$69(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
27799
28422
  });
27800
28423
  const projectAddCommand = Command.make("add", {
27801
28424
  ...projectLocationFlags,
@@ -29115,6 +29738,40 @@ const otlpTracesProxyRouteLayer = HttpRouter.add("POST", OTLP_TRACES_PROXY_PATH,
29115
29738
  EnvironmentInternalError: HttpServerRespondable.toResponse,
29116
29739
  EnvironmentScopeRequiredError: HttpServerRespondable.toResponse
29117
29740
  })));
29741
+ const escapeHtml = (input) => input.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
29742
+ const oauthResultPage = (input) => HttpServerResponse.text([
29743
+ "<!doctype html>",
29744
+ `<html><head><meta charset="utf-8"><title>${escapeHtml(input.title)}</title></head>`,
29745
+ "<body style=\"font-family: system-ui, sans-serif; max-width: 32rem; margin: 4rem auto;\">",
29746
+ `<h1 style="font-size: 1.25rem;">${escapeHtml(input.title)}</h1>`,
29747
+ `<p>${escapeHtml(input.detail)}</p>`,
29748
+ "</body></html>"
29749
+ ].join(""), { contentType: "text/html; charset=utf-8" });
29750
+ /**
29751
+ * Where the browser lands after an MCP server sign-in.
29752
+ *
29753
+ * Deliberately unauthenticated: the redirect comes from an authorization
29754
+ * server that holds no p4code credential. The `state` value — minted by
29755
+ * `McpOAuth.begin`, single-use, expiring — is what ties the request to a flow
29756
+ * this server started; a request without a live one changes nothing.
29757
+ */
29758
+ const mcpOAuthCallbackRouteLayer = HttpRouter.add("GET", OAUTH_CALLBACK_PATH, Effect.gen(function* () {
29759
+ const request = yield* HttpServerRequest.HttpServerRequest;
29760
+ const url = new URL(request.url, "http://localhost.invalid");
29761
+ const outcome = yield* (yield* McpOAuth).handleCallback({
29762
+ state: url.searchParams.get("state") ?? void 0,
29763
+ code: url.searchParams.get("code") ?? void 0,
29764
+ error: url.searchParams.get("error") ?? void 0,
29765
+ errorDescription: url.searchParams.get("error_description") ?? void 0
29766
+ });
29767
+ return outcome.ok ? oauthResultPage({
29768
+ title: "Signed in",
29769
+ detail: `${outcome.server} is connected. You can close this tab and return to p4code.`
29770
+ }) : oauthResultPage({
29771
+ title: "Sign-in failed",
29772
+ detail: outcome.detail
29773
+ });
29774
+ }));
29118
29775
  const assetRouteLayer = HttpRouter.add("GET", `${ASSET_ROUTE_PREFIX}/*`, Effect.gen(function* () {
29119
29776
  const request = yield* HttpServerRequest.HttpServerRequest;
29120
29777
  const url = HttpServerRequest.toURL(request);
@@ -33254,6 +33911,12 @@ const toTaskStoreError = (operation) => (cause) => Effect.logWarning("task store
33254
33911
  /**
33255
33912
  * The `auto` rendering: here is the task, do it.
33256
33913
  *
33914
+ * The prompt carries a reference to the task, not a copy of it. Inlining the
33915
+ * body flattened the row into plain text: labels, project and priority were
33916
+ * lost, and an edit made after the thread started was invisible to an agent
33917
+ * still reading its stale snapshot. The row stays the single source of truth
33918
+ * and the agent reads it live over MCP.
33919
+ *
33257
33920
  * Two instructions carry their weight. The agent is told its thread is already
33258
33921
  * linked to the task, because otherwise its first useful move is to guess which
33259
33922
  * board row it is on. And it is told to leave `status` alone: the server moved
@@ -33262,18 +33925,16 @@ const toTaskStoreError = (operation) => (cause) => Effect.logWarning("task store
33262
33925
  * the reason the board's columns still mean something.
33263
33926
  */
33264
33927
  const buildAutoPrompt = (task) => {
33265
- const body = task.body.trim();
33266
- return `You are picking up a task from the board. Do the work.
33928
+ const reference = task.readableId ?? task.taskId;
33929
+ return `Picking up board task \`${reference}\`: ${task.title}
33267
33930
 
33268
- ## ${task.title}
33931
+ Read the task first via \`task_current\` (or \`task_get\` with \`${reference}\`). Body, labels, project and priority live on the row, not in this message; the row may have changed since this thread started - the current row is the source of truth. Re-read it the same way whenever you need the fields again.
33269
33932
 
33270
- ${body.length === 0 ? "_The task has no description. Read enough of the repository to work out what it means, and ask if it is genuinely ambiguous._" : body}
33933
+ This thread is already linked to the task, status In progress. Do the work.
33271
33934
 
33272
- This thread is linked to task \`${task.taskId}\`, which is now In progress. \`task_current\` re-reads it if you need the fields again.
33935
+ Record findings via \`task_update\` as you go - decisions made, constraints hit, things the task turned out to be wrong about. That is what makes the row worth reading afterwards. Leave \`status\` alone: moving work across the board is the user's call.
33273
33936
 
33274
- Record what you find with \`task_update\` as you go — a decision you made, a constraint you hit, something the task turned out to be wrong about. That is what makes the row worth reading afterwards. Leave \`status\` alone: moving work across the board is the user's call.
33275
-
33276
- Start by reading the code the task concerns. If it turns out to be specified too poorly to act on, say so and stop rather than guessing at what was meant.`;
33937
+ After reading the task, read the code it concerns. If it is specified too poorly to act on, say so and stop rather than guessing.`;
33277
33938
  };
33278
33939
  /**
33279
33940
  * Render a task into the prompt that seeds its thread.
@@ -36228,6 +36889,24 @@ const requireTaskCapability = Effect.fn("mcp.requireTaskCapability")(function* (
36228
36889
  });
36229
36890
  return invocation;
36230
36891
  });
36892
+ /**
36893
+ * The watch capability's guard is two checks, not one: the capability must be
36894
+ * present AND the requested thread must be in the explicit grant set. The two
36895
+ * failures are distinct errors so a watcher can tell "this session cannot
36896
+ * watch at all" apart from "not this thread".
36897
+ */
36898
+ const requireWatchCapability = Effect.fn("mcp.requireWatchCapability")(function* (watchedThreadId) {
36899
+ const invocation = yield* McpInvocationContext;
36900
+ if (!invocation.capabilities.has("watch")) return yield* new WatchToolUnavailableError({
36901
+ capability: "watch",
36902
+ environmentId: invocation.environmentId,
36903
+ threadId: invocation.threadId,
36904
+ providerSessionId: invocation.providerSessionId,
36905
+ providerInstanceId: invocation.providerInstanceId
36906
+ });
36907
+ if (!invocation.watchThreadIds?.has(watchedThreadId)) return yield* new ThreadWatchNotPermittedError({ threadId: watchedThreadId });
36908
+ return invocation;
36909
+ });
36231
36910
  /** Third of the trio, for the same reason the second exists. */
36232
36911
  const requireThreadCapability = Effect.fn("mcp.requireThreadCapability")(function* () {
36233
36912
  const invocation = yield* McpInvocationContext;
@@ -43279,6 +43958,8 @@ const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
43279
43958
  [WS_METHODS.mcpSave, AuthOrchestrationOperateScope],
43280
43959
  [WS_METHODS.mcpRemove, AuthOrchestrationOperateScope],
43281
43960
  [WS_METHODS.mcpSetSecret, AuthOrchestrationOperateScope],
43961
+ [WS_METHODS.mcpOAuthBegin, AuthOrchestrationOperateScope],
43962
+ [WS_METHODS.mcpOAuthDisconnect, AuthOrchestrationOperateScope],
43282
43963
  [WS_METHODS.serverGetSettings, AuthOrchestrationReadScope],
43283
43964
  [WS_METHODS.serverUpdateSettings, AuthOrchestrationOperateScope],
43284
43965
  [WS_METHODS.serverDiscoverSourceControl, AuthOrchestrationReadScope],
@@ -43392,6 +44073,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
43392
44073
  const hubLink = yield* HubLink;
43393
44074
  const assetSync = yield* AssetSync;
43394
44075
  const mcpRegistry = yield* McpRegistry;
44076
+ const mcpOAuth = yield* McpOAuth;
43395
44077
  const skillRegistry = yield* SkillRegistry;
43396
44078
  const startup = yield* ServerRuntimeStartup;
43397
44079
  const workspaceEntries = yield* WorkspaceEntries;
@@ -43885,8 +44567,8 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
43885
44567
  mode: "auto",
43886
44568
  task: {
43887
44569
  taskId,
43888
- title: task.title,
43889
- body: task.body
44570
+ readableId: task.readableId,
44571
+ title: task.title
43890
44572
  }
43891
44573
  }),
43892
44574
  attachments: []
@@ -43895,14 +44577,14 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
43895
44577
  titleSeed: threadTitle,
43896
44578
  runtimeMode: DEFAULT_RUNTIME_MODE$2,
43897
44579
  interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
43898
- compressMode: "off",
44580
+ compressMode: DEFAULT_COMPRESS_MODE,
43899
44581
  bootstrap: { createThread: {
43900
44582
  projectId: plan.projectId,
43901
44583
  title: threadTitle,
43902
44584
  modelSelection,
43903
44585
  runtimeMode: DEFAULT_RUNTIME_MODE$2,
43904
44586
  interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
43905
- compressMode: "off",
44587
+ compressMode: DEFAULT_COMPRESS_MODE,
43906
44588
  branch: null,
43907
44589
  worktreePath: null,
43908
44590
  createdAt
@@ -43993,6 +44675,13 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
43993
44675
  slot,
43994
44676
  value
43995
44677
  }).pipe(Effect.andThen(mcpRegistry.list.pipe(Effect.map((servers) => ({ servers }))))), { "rpc.aggregate": "mcp" }),
44678
+ [WS_METHODS.mcpOAuthBegin]: ({ name }) => observeRpcEffect$1(WS_METHODS.mcpOAuthBegin, Effect.gen(function* () {
44679
+ const found = (yield* mcpRegistry.list).find((status) => status.registration.name === name);
44680
+ if (found === void 0) return yield* new McpRegistryError({ detail: `No server is named ${name}.` });
44681
+ if (found.registration.transport === "stdio") return yield* new McpRegistryError({ detail: "A stdio server is launched locally and has no sign-in." });
44682
+ return yield* mcpOAuth.begin(found.registration);
44683
+ }), { "rpc.aggregate": "mcp" }),
44684
+ [WS_METHODS.mcpOAuthDisconnect]: ({ name }) => observeRpcEffect$1(WS_METHODS.mcpOAuthDisconnect, mcpOAuth.disconnect(name).pipe(Effect.andThen(mcpRegistry.list.pipe(Effect.map((servers) => ({ servers }))))), { "rpc.aggregate": "mcp" }),
43996
44685
  [WS_METHODS.serverGetSettings]: (_input) => observeRpcEffect$1(WS_METHODS.serverGetSettings, serverSettings.getSettings.pipe(Effect.map(redactServerSettingsForClient)), { "rpc.aggregate": "server" }),
43997
44686
  [WS_METHODS.serverUpdateSettings]: ({ patch }) => observeRpcEffect$1(WS_METHODS.serverUpdateSettings, serverSettings.updateSettings(patch).pipe(Effect.map(redactServerSettingsForClient)), { "rpc.aggregate": "server" }),
43998
44687
  [WS_METHODS.serverDiscoverSourceControl]: (_input) => observeRpcEffect$1(WS_METHODS.serverDiscoverSourceControl, sourceControlDiscovery.discover, { "rpc.aggregate": "server" }),
@@ -45186,16 +45875,20 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
45186
45875
  const providerSessionId = yield* crypto.randomUUIDv4.pipe(Effect.orDie);
45187
45876
  const rawToken = yield* crypto.randomBytes(32).pipe(Effect.map(tokenFromBytes), Effect.orDie);
45188
45877
  const tokenHash = yield* hashToken(rawToken);
45878
+ const watchThreadIds = new Set((request.watchThreadIds ?? []).map((threadId) => ThreadId.make(threadId)));
45879
+ const capabilities = /* @__PURE__ */ new Set([
45880
+ "preview",
45881
+ "tasks",
45882
+ "threads"
45883
+ ]);
45884
+ if (watchThreadIds.size > 0) capabilities.add("watch");
45189
45885
  const scope = {
45190
45886
  environmentId,
45191
45887
  threadId: ThreadId.make(request.threadId),
45192
45888
  providerSessionId,
45193
45889
  providerInstanceId: ProviderInstanceId.make(request.providerInstanceId),
45194
- capabilities: /* @__PURE__ */ new Set([
45195
- "preview",
45196
- "tasks",
45197
- "threads"
45198
- ]),
45890
+ capabilities,
45891
+ ...watchThreadIds.size > 0 ? { watchThreadIds } : {},
45199
45892
  issuedAt
45200
45893
  };
45201
45894
  yield* SynchronizedRef.update(state, ({ records }) => {
@@ -46486,7 +47179,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
46486
47179
  };
46487
47180
  });
46488
47181
  var OpenCodeRuntime = class extends Context.Service()("@p4code/cli/provider/opencodeRuntime") {};
46489
- const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$69));
47182
+ const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$70));
46490
47183
  //#endregion
46491
47184
  //#region src/textGeneration/TextGenerationUtils.ts
46492
47185
  const isTextGenerationError$2 = Schema$1.is(TextGenerationError);
@@ -82309,7 +83002,7 @@ const invokeTargeted = (operation, input, timeoutMs) => {
82309
83002
  const { tabId, ...operationInput } = input;
82310
83003
  return invoke(operation, operationInput, timeoutMs, tabId);
82311
83004
  };
82312
- const handlers$2 = {
83005
+ const handlers$3 = {
82313
83006
  preview_status: (input) => invokeTargeted("status", input ?? {}),
82314
83007
  preview_open: (input) => invokeTargeted("open", normalizePreviewOpenInput(input)),
82315
83008
  preview_navigate: (input) => invokeTargeted("navigate", input, input.timeoutMs),
@@ -82325,10 +83018,10 @@ const handlers$2 = {
82325
83018
  preview_recording_start: (input) => invokeTargeted("recordingStart", input ?? {}),
82326
83019
  preview_recording_stop: (input) => invokeTargeted("recordingStop", input ?? {})
82327
83020
  };
82328
- const { preview_snapshot, ...standardHandlers } = handlers$2;
83021
+ const { preview_snapshot, ...standardHandlers } = handlers$3;
82329
83022
  const PreviewStandardToolkitHandlersLive = PreviewStandardToolkit.toLayer(standardHandlers);
82330
83023
  const PreviewSnapshotToolkitHandlersLive = PreviewSnapshotToolkit.toLayer({ preview_snapshot });
82331
- PreviewToolkit.toLayer(handlers$2);
83024
+ PreviewToolkit.toLayer(handlers$3);
82332
83025
  //#endregion
82333
83026
  //#region src/mcp/toolkits/tasks/tools.ts
82334
83027
  const dependencies = [McpInvocationContext, TaskRepository];
@@ -82561,6 +83254,41 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
82561
83254
  })
82562
83255
  });
82563
83256
  //#endregion
83257
+ //#region src/mcp/toolkits/watch/tools.ts
83258
+ /**
83259
+ * Cross-thread by design, which is exactly why it is the most guarded tool in
83260
+ * the registry: the handler admits only threads in the session's explicit
83261
+ * `watchThreadIds` grant, never the token's whole reach. The read is a cursor
83262
+ * poll because MCP tools are request/response; a watcher advances
83263
+ * `afterSequence` with each page and knows it is caught up when `hasMore` is
83264
+ * false and the last sequence equals `headSequence`.
83265
+ */
83266
+ const ThreadWatchEventsTool = Tool.make("thread_watch_events", {
83267
+ description: "Read a watched thread's orchestration events after a sequence cursor. Returns an ordered page plus the head sequence; pass the last event's sequence back as afterSequence to poll forward. Only threads this session was explicitly granted are readable.",
83268
+ parameters: ThreadWatchEventsInput,
83269
+ success: ThreadWatchEventsResult,
83270
+ failure: ThreadWatchToolError,
83271
+ dependencies: [McpInvocationContext, ThreadEventStreamService]
83272
+ }).annotate(Tool.Title, "Read watched thread events").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
83273
+ const WatchToolkit = Toolkit.make(ThreadWatchEventsTool);
83274
+ const WatchToolkitHandlersLive = WatchToolkit.toLayer({ thread_watch_events: (input) => Effect.gen(function* () {
83275
+ yield* requireWatchCapability(input.threadId);
83276
+ const page = yield* (yield* ThreadEventStreamService).read({
83277
+ threadId: input.threadId,
83278
+ ...input.afterSequence !== void 0 ? { afterSequence: input.afterSequence } : {},
83279
+ limit: input.limit ?? 50
83280
+ }).pipe(Effect.mapError((cause) => new ThreadWatchFailedError({
83281
+ threadId: input.threadId,
83282
+ detail: cause.message
83283
+ })));
83284
+ return {
83285
+ threadId: input.threadId,
83286
+ events: page.events,
83287
+ headSequence: page.headSequence,
83288
+ hasMore: page.hasMore
83289
+ };
83290
+ }) });
83291
+ //#endregion
82564
83292
  //#region src/mcp/McpHttpServer.ts
82565
83293
  const unauthorized = HttpServerResponse.jsonUnsafe({
82566
83294
  error: "invalid_mcp_credential",
@@ -82665,12 +83393,13 @@ const PreviewSnapshotRegistrationLive = Layer.effectDiscard(registerPreviewSnaps
82665
83393
  const PreviewToolkitRegistrationLive = Layer.mergeAll(PreviewStandardToolkitRegistrationLive, PreviewSnapshotRegistrationLive);
82666
83394
  const TaskToolkitRegistrationLive = McpServer.toolkit(TaskToolkit).pipe(Layer.provide(TaskToolkitHandlersLive));
82667
83395
  const ThreadToolkitRegistrationLive = McpServer.toolkit(ThreadToolkit).pipe(Layer.provide(ThreadToolkitHandlersLive));
83396
+ const WatchToolkitRegistrationLive = McpServer.toolkit(WatchToolkit).pipe(Layer.provide(WatchToolkitHandlersLive));
82668
83397
  const McpTransportLive = McpServer.layerHttp({
82669
83398
  name: "P4Code",
82670
83399
  version,
82671
83400
  path: "/mcp"
82672
83401
  }).pipe(Layer.provide(McpAuthMiddlewareLive));
82673
- const layer = Layer.mergeAll(PreviewToolkitRegistrationLive, TaskToolkitRegistrationLive, ThreadToolkitRegistrationLive).pipe(Layer.provideMerge(McpTransportLive));
83402
+ const layer = Layer.mergeAll(PreviewToolkitRegistrationLive, TaskToolkitRegistrationLive, ThreadToolkitRegistrationLive, WatchToolkitRegistrationLive).pipe(Layer.provideMerge(McpTransportLive));
82674
83403
  //#endregion
82675
83404
  //#region src/orchestration/Services/CheckpointReactor.ts
82676
83405
  /**
@@ -85735,9 +86464,9 @@ const WorkspaceEntriesLayerLive = layer$25.pipe(Layer.provide(layer$43));
85735
86464
  const WorkspaceFileSystemLayerLive = layer$24.pipe(Layer.provide(layer$43), Layer.provide(WorkspaceEntriesLayerLive));
85736
86465
  const WorkspaceLayerLive = Layer.mergeAll(layer$43, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
85737
86466
  const ProjectFaviconResolverLayerLive = layer$41.pipe(Layer.provide(layer$43), Layer.provide(layer$42));
85738
- const AuthLayerLive = layer$61.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$65));
86467
+ const AuthLayerLive = layer$62.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$66));
85739
86468
  const ProviderRuntimeLayerLive = ProviderSessionReaperLive.pipe(Layer.provideMerge(ProviderLayerLive), Layer.provideMerge(OrchestrationLayerLive));
85740
- 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$51), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$58.pipe(Layer.provide(layer$65))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$52), Layer.provideMerge(layer$45), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(Layer.mergeAll(layer$57, layer$31)), Layer.provideMerge(layer$65)).pipe(Layer.provideMerge(layer$8), Layer.provideMerge(layer$7), Layer.provideMerge(layer$6), Layer.provideMerge(layer$48), Layer.provideMerge(layer$50), Layer.provideMerge(layer$49), Layer.provide(layer$69));
86469
+ 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$51), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$59.pipe(Layer.provide(layer$66))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$52), Layer.provideMerge(layer$45), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(Layer.mergeAll(layer$57, layer$31).pipe(Layer.provideMerge(layer$58))), Layer.provideMerge(layer$66)).pipe(Layer.provideMerge(layer$8), Layer.provideMerge(layer$7), Layer.provideMerge(layer$6), Layer.provideMerge(layer$48), Layer.provideMerge(layer$50), Layer.provideMerge(layer$49), Layer.provide(layer$70));
85741
86470
  /**
85742
86471
  * Hub asset sync.
85743
86472
  *
@@ -85747,7 +86476,7 @@ const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(Checkpo
85747
86476
  */
85748
86477
  const AssetSyncLive = layer$54.pipe(Layer.provideMerge(layer$55), Layer.provideMerge(layer$56));
85749
86478
  const RuntimeServicesLive = layer$44.pipe(Layer.provideMerge(AssetSyncLive), Layer.provideMerge(RuntimeDependenciesLive));
85750
- const makeRoutesLayer = Layer.mergeAll(Layer.mergeAll(HttpApiBuilder.layer(EnvironmentHttpApi).pipe(Layer.provide(authHttpApiLayer), Layer.provide(orchestrationHttpApiLayer), Layer.provide(serverEnvironmentHttpApiLayer), Layer.provide(environmentAuthenticatedAuthLayer)), otlpTracesProxyRouteLayer, assetRouteLayer, staticAndDevRouteLayer, websocketRpcRouteLayer), layer.pipe(Layer.provide(layer$2))).pipe(Layer.provide(RoutedTaskRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(ProjectionProjectRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(layer$42), Layer.provide(layer$28), Layer.provide(layer$46), Layer.provide(browserApiCorsLayer), Layer.provide(httpCompressionLayer));
86479
+ const makeRoutesLayer = Layer.mergeAll(Layer.mergeAll(HttpApiBuilder.layer(EnvironmentHttpApi).pipe(Layer.provide(authHttpApiLayer), Layer.provide(orchestrationHttpApiLayer), Layer.provide(serverEnvironmentHttpApiLayer), Layer.provide(environmentAuthenticatedAuthLayer)), otlpTracesProxyRouteLayer, assetRouteLayer, mcpOAuthCallbackRouteLayer, staticAndDevRouteLayer, websocketRpcRouteLayer), layer.pipe(Layer.provide(layer$2))).pipe(Layer.provide(RoutedTaskRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(ProjectionProjectRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(layer$42), Layer.provide(layer$28), Layer.provide(layer$46), Layer.provide(browserApiCorsLayer), Layer.provide(httpCompressionLayer));
85751
86480
  const makeServerLayer = Layer.unwrap(Effect.gen(function* () {
85752
86481
  const config = yield* ServerConfig$1;
85753
86482
  yield* fixPath();
@@ -86157,7 +86886,7 @@ const serviceCommand = Command.make("service").pipe(Command.withDescription("Man
86157
86886
  ]));
86158
86887
  //#endregion
86159
86888
  //#region src/bin.ts
86160
- const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$69, FetchHttpClient.layer);
86889
+ const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$70, FetchHttpClient.layer);
86161
86890
  /**
86162
86891
  * Prints an update reminder before dispatching, at most once a day.
86163
86892
  *