@p4code/cli 0.1.17 → 0.1.19

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 (84) hide show
  1. package/dist/bin.mjs +1408 -400
  2. package/dist/client/assets/{DiffPanel-BrM3QqVj.js → DiffPanel-CseUmWvE.js} +2 -2
  3. package/dist/client/assets/{FilePreviewPanel-DXk_4dj4.js → FilePreviewPanel-Dm7l5FFh.js} +2 -2
  4. package/dist/client/assets/{PreviewPanel-DfngnmUt.js → PreviewPanel-DOyykNL9.js} +2 -2
  5. package/dist/client/assets/{angular-html-veA1EpVJ.js → angular-html-YobKPLun.js} +2 -2
  6. package/dist/client/assets/{angular-ts-CjhObYqk.js → angular-ts-Ac3I6TFV.js} +2 -2
  7. package/dist/client/assets/{apl-BaBqmioI.js → apl-Dwl73GTS.js} +2 -2
  8. package/dist/client/assets/arrow-right-BqDszij1.js +2 -0
  9. package/dist/client/assets/{astro-aZep3bUH.js → astro-KBgldnxZ.js} +2 -2
  10. package/dist/client/assets/{blade-B-PS40R8.js → blade-DTU2NGhh.js} +2 -2
  11. package/dist/client/assets/{c-Da6G9SJL.js → c-COF4kWCp.js} +2 -2
  12. package/dist/client/assets/{cobol-MFD_Q4Xa.js → cobol-BA7JsdNr.js} +2 -2
  13. package/dist/client/assets/{coffee-DOKaGG2H.js → coffee-PFN_w4qL.js} +2 -2
  14. package/dist/client/assets/{cpp-iD8OoA0X.js → cpp-CpT6w-g5.js} +2 -2
  15. package/dist/client/assets/{crystal-BgCh46iL.js → crystal-BrlaRT0B.js} +2 -2
  16. package/dist/client/assets/{css-C0AvK1Fp.js → css-C2LMO3MX.js} +2 -2
  17. package/dist/client/assets/{edge-DWC_9CO2.js → edge-DBkQ-Ajd.js} +2 -2
  18. package/dist/client/assets/{elixir-5ahlHJpU.js → elixir-EbSNDEx0.js} +2 -2
  19. package/dist/client/assets/{elm-B4b4KSk0.js → elm-CSs0Wzy2.js} +2 -2
  20. package/dist/client/assets/{erb-CNRZFKcV.js → erb-BBmMQG9b.js} +2 -2
  21. package/dist/client/assets/{fileCommentAnnotations-V3SOLaB4.js → fileCommentAnnotations-CifvTzWP.js} +2 -2
  22. package/dist/client/assets/{git-rebase-AbfQBerd.js → git-rebase-C09BpZDw.js} +2 -2
  23. package/dist/client/assets/{glimmer-js-_dx97vsR.js → glimmer-js-D4Bqn6Yg.js} +2 -2
  24. package/dist/client/assets/{glimmer-ts-BFmDDwi5.js → glimmer-ts-fLCHL8o6.js} +2 -2
  25. package/dist/client/assets/{glsl-CXroyNbj.js → glsl-17sV9oGm.js} +2 -2
  26. package/dist/client/assets/{graphql-RU5TF49j.js → graphql-BqFquulN.js} +2 -2
  27. package/dist/client/assets/{hack-S6OyQkI3.js → hack-D6Jpv-j9.js} +2 -2
  28. package/dist/client/assets/{haml-CclbQxsV.js → haml-D0eo8PEJ.js} +2 -2
  29. package/dist/client/assets/{handlebars-DFD506ku.js → handlebars-HG0XNkEg.js} +2 -2
  30. package/dist/client/assets/{html-BtV0fnqE.js → html-Bh3padJ5.js} +2 -2
  31. package/dist/client/assets/{html-derivative-oHJ5ggVN.js → html-derivative-DVHw4MNX.js} +2 -2
  32. package/dist/client/assets/{http-C8JeRqCn.js → http-CYCXaOA5.js} +2 -2
  33. package/dist/client/assets/{hurl-C1cew6hc.js → hurl-D4oK46oh.js} +2 -2
  34. package/dist/client/assets/{index-5pO5drbg.js → index-CV_dK79d.js} +217 -202
  35. package/dist/client/assets/{java-CrUEPeR7.js → java-nvFYZOCN.js} +2 -2
  36. package/dist/client/assets/{javascript-DJT7psEs.js → javascript-CMBxjf-M.js} +2 -2
  37. package/dist/client/assets/{jinja-CXxzh_AI.js → jinja-vsPdkFFj.js} +2 -2
  38. package/dist/client/assets/{jison-BEgWlYuP.js → jison-uky2pPNE.js} +2 -2
  39. package/dist/client/assets/{json-DYMLm7oI.js → json-DOSqa2F1.js} +2 -2
  40. package/dist/client/assets/{jsx-C2i-6RBR.js → jsx-Ci7CVNQe.js} +2 -2
  41. package/dist/client/assets/{julia-268B5DSC.js → julia-J2m7N3_R.js} +2 -2
  42. package/dist/client/assets/{just-DHlfSR_g.js → just-BDy3oM_N.js} +2 -2
  43. package/dist/client/assets/{latex-hZJ97DlU.js → latex-CMD49F7D.js} +2 -2
  44. package/dist/client/assets/{liquid-BXWKgP9a.js → liquid-Dq6k2YW6.js} +2 -2
  45. package/dist/client/assets/{lua-DZpMPVk1.js → lua-CohtP6J8.js} +2 -2
  46. package/dist/client/assets/{marko-C4krJn5M.js → marko-C4V2zw1t.js} +2 -2
  47. package/dist/client/assets/{mdc-DPMybKM9.js → mdc-q1ZKjwYt.js} +2 -2
  48. package/dist/client/assets/{nginx-DS7EZxTP.js → nginx-D2TdEIZ3.js} +2 -2
  49. package/dist/client/assets/{nim-DQ5EIgDG.js → nim-BtWKpYQs.js} +2 -2
  50. package/dist/client/assets/{perl-BYRfkLcm.js → perl-Ci4Pso9h.js} +2 -2
  51. package/dist/client/assets/{php-CRmJir-2.js → php-CMzpcAju.js} +2 -2
  52. package/dist/client/assets/{pug-ChlH277L.js → pug-D0sbFhP6.js} +2 -2
  53. package/dist/client/assets/{qml-D03lc83e.js → qml-BfWXHzNc.js} +2 -2
  54. package/dist/client/assets/{r-B0iWOCwM.js → r-BN4rQDnz.js} +2 -2
  55. package/dist/client/assets/{razor-jqTSj5Nf.js → razor-D5iw68yr.js} +2 -2
  56. package/dist/client/assets/{regexp-BtdW0BtX.js → regexp-Ba0WieCb.js} +2 -2
  57. package/dist/client/assets/{rst-ODlKAfCn.js → rst-DQqlRfnq.js} +2 -2
  58. package/dist/client/assets/{ruby-D3-MIdGC.js → ruby-CReh9Gta.js} +2 -2
  59. package/dist/client/assets/{sas-CHM8NNaC.js → sas-CJ-Cpd8L.js} +2 -2
  60. package/dist/client/assets/{scss-C5u0ylVF.js → scss-CZp-cH6d.js} +2 -2
  61. package/dist/client/assets/{shellscript-e38vxsyD.js → shellscript-hCSuhhJZ.js} +2 -2
  62. package/dist/client/assets/{shellsession-C2xN505I.js → shellsession-BnDNcCDQ.js} +2 -2
  63. package/dist/client/assets/{soy-Cm15Zvq3.js → soy-BXarcWJY.js} +2 -2
  64. package/dist/client/assets/{sql-BpFtjTfW.js → sql-ADQCXhYd.js} +2 -2
  65. package/dist/client/assets/{stata-BlPPmFYy.js → stata-DSS4IpqF.js} +2 -2
  66. package/dist/client/assets/{surrealql-BCULE51k.js → surrealql-DuYcftNm.js} +2 -2
  67. package/dist/client/assets/{svelte-BNRSv2pn.js → svelte-CAXOFInl.js} +2 -2
  68. package/dist/client/assets/{templ-DoGq1v8F.js → templ-BvTIEIah.js} +2 -2
  69. package/dist/client/assets/{tex--cpmM4NW.js → tex-DpnVRTMM.js} +2 -2
  70. package/dist/client/assets/textarea-etKSOtDN.js +46 -0
  71. package/dist/client/assets/{ts-tags-D5eCylEK.js → ts-tags-4wlHlovT.js} +2 -2
  72. package/dist/client/assets/{tsx-Bu8K9CNP.js → tsx-CHDgELdy.js} +2 -2
  73. package/dist/client/assets/{twig-BZHf1w2v.js → twig-DPmx2UHF.js} +2 -2
  74. package/dist/client/assets/{typescript-CdzN72UO.js → typescript-CHO8bW-H.js} +2 -2
  75. package/dist/client/assets/{vue-BZJs2XXT.js → vue-CM19I03n.js} +2 -2
  76. package/dist/client/assets/{vue-html-zt0y5fO9.js → vue-html-ginfkxGQ.js} +2 -2
  77. package/dist/client/assets/{vue-vine-SsQX7Kll.js → vue-vine-CGdZxUwf.js} +2 -2
  78. package/dist/client/assets/{xml-BKlTVdhA.js → xml-B8CY16OL.js} +2 -2
  79. package/dist/client/assets/{xsl-B00O3UQ-.js → xsl-COHh7n8v.js} +2 -2
  80. package/dist/client/assets/{yaml-Dr4fa503.js → yaml-DXx-djat.js} +2 -2
  81. package/dist/client/index.html +2 -2
  82. package/package.json +1 -1
  83. package/dist/client/assets/arrow-right-Cvlj0Cvu.js +0 -2
  84. package/dist/client/assets/textarea-M0r5voiR.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$75 = () => {
124
+ const make$76 = () => {
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$75 = () => {
230
230
  })
231
231
  };
232
232
  };
233
- const layer$71 = Layer.sync(NetService, make$75);
233
+ const layer$72 = Layer.sync(NetService, make$76);
234
234
  //#endregion
235
235
  //#region package.json
236
- var version = "0.1.17";
236
+ var version = "0.1.19";
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$74 = (config) => ServerConfig$1.of(config);
258
- const layer$70 = (config) => Layer.succeed(ServerConfig$1, make$74(config));
257
+ const make$75 = (config) => ServerConfig$1.of(config);
258
+ const layer$71 = (config) => Layer.succeed(ServerConfig$1, make$75(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");
@@ -3430,6 +3430,36 @@ const PreviewAutomationNetworkEntry = Schema.Struct({
3430
3430
  errorText: Schema.optional(Schema.String),
3431
3431
  timestamp: Schema.String
3432
3432
  });
3433
+ /** Value of an accessibility state such as `expanded`, `checked` or `level`. */
3434
+ const PreviewAccessibilityState = Schema.Union([
3435
+ Schema.String,
3436
+ Schema.Number,
3437
+ Schema.Boolean
3438
+ ]);
3439
+ /**
3440
+ * A node of the compacted accessibility tree. `parentId` points at the nearest
3441
+ * emitted ancestor, so the hierarchy is reconstructible without child lists.
3442
+ */
3443
+ const PreviewAccessibilityNode = Schema.Struct({
3444
+ id: Schema.String,
3445
+ parentId: Schema.optional(Schema.String),
3446
+ role: Schema.String,
3447
+ name: Schema.optional(Schema.String),
3448
+ description: Schema.optional(Schema.String),
3449
+ value: Schema.optional(Schema.String),
3450
+ states: Schema.optional(Schema.Record(Schema.String, PreviewAccessibilityState))
3451
+ });
3452
+ /**
3453
+ * Semantic nodes only: ignored nodes, raw text nodes and unnamed layout
3454
+ * wrappers are dropped, and the node list is capped. `totalNodes` counts the
3455
+ * nodes that survived filtering, so `truncated` distinguishes a clipped tree
3456
+ * from a small page.
3457
+ */
3458
+ const PreviewAccessibilitySnapshot = Schema.Struct({
3459
+ nodes: Schema.Array(PreviewAccessibilityNode),
3460
+ totalNodes: Schema.Int,
3461
+ truncated: Schema.Boolean
3462
+ });
3433
3463
  const PreviewAutomationActionEvent = Schema.Struct({
3434
3464
  id: Schema.String,
3435
3465
  action: Schema.String,
@@ -3449,7 +3479,7 @@ const PreviewAutomationSnapshot = Schema.Struct({
3449
3479
  loading: Schema.Boolean,
3450
3480
  visibleText: Schema.String,
3451
3481
  interactiveElements: Schema.Array(PreviewAutomationElement),
3452
- accessibilityTree: Schema.Unknown,
3482
+ accessibilityTree: PreviewAccessibilitySnapshot,
3453
3483
  consoleEntries: Schema.Array(PreviewAutomationConsoleEntry),
3454
3484
  networkEntries: Schema.Array(PreviewAutomationNetworkEntry),
3455
3485
  actionTimeline: Schema.Array(PreviewAutomationActionEvent),
@@ -4773,6 +4803,7 @@ Schema$1.Literals([
4773
4803
  "config.warning",
4774
4804
  "deprecation.notice",
4775
4805
  "files.persisted",
4806
+ "code.published",
4776
4807
  "runtime.warning",
4777
4808
  "runtime.error"
4778
4809
  ]);
@@ -4821,6 +4852,7 @@ const ModelReroutedType = Schema$1.Literal("model.rerouted");
4821
4852
  const ConfigWarningType = Schema$1.Literal("config.warning");
4822
4853
  const DeprecationNoticeType = Schema$1.Literal("deprecation.notice");
4823
4854
  const FilesPersistedType = Schema$1.Literal("files.persisted");
4855
+ const CodePublishedType = Schema$1.Literal("code.published");
4824
4856
  const ToolDeniedType = Schema$1.Literal("tool.denied");
4825
4857
  const RuntimeWarningType = Schema$1.Literal("runtime.warning");
4826
4858
  const RuntimeErrorType = Schema$1.Literal("runtime.error");
@@ -5123,6 +5155,12 @@ const FilesPersistedPayload = Schema$1.Struct({
5123
5155
  error: TrimmedNonEmptyStringSchema$1
5124
5156
  })))
5125
5157
  });
5158
+ const CodePublishedPayload = Schema$1.Struct({
5159
+ hostingProvider: TrimmedNonEmptyStringSchema$1,
5160
+ url: TrimmedNonEmptyStringSchema$1,
5161
+ repo: Schema$1.optional(TrimmedNonEmptyStringSchema$1),
5162
+ identifier: Schema$1.optional(TrimmedNonEmptyStringSchema$1)
5163
+ });
5126
5164
  const ToolDeniedPayload = Schema$1.Struct({
5127
5165
  toolName: TrimmedNonEmptyStringSchema$1,
5128
5166
  toolUseId: Schema$1.optional(TrimmedNonEmptyStringSchema$1),
@@ -5363,6 +5401,11 @@ const ProviderRuntimeFilesPersistedEvent = Schema$1.Struct({
5363
5401
  type: FilesPersistedType,
5364
5402
  payload: FilesPersistedPayload
5365
5403
  });
5404
+ const ProviderRuntimeCodePublishedEvent = Schema$1.Struct({
5405
+ ...ProviderRuntimeEventBase.fields,
5406
+ type: CodePublishedType,
5407
+ payload: CodePublishedPayload
5408
+ });
5366
5409
  const ProviderRuntimeToolDeniedEvent = Schema$1.Struct({
5367
5410
  ...ProviderRuntimeEventBase.fields,
5368
5411
  type: ToolDeniedType,
@@ -5424,6 +5467,7 @@ Schema$1.Union([
5424
5467
  ProviderRuntimeConfigWarningEvent,
5425
5468
  ProviderRuntimeDeprecationNoticeEvent,
5426
5469
  ProviderRuntimeFilesPersistedEvent,
5470
+ ProviderRuntimeCodePublishedEvent,
5427
5471
  ProviderRuntimeToolDeniedEvent,
5428
5472
  ProviderRuntimeWarningEvent,
5429
5473
  ProviderRuntimeErrorEvent
@@ -5795,6 +5839,20 @@ const ServerSettings = Schema$1.Struct({
5795
5839
  * back for sessions started by the Claude provider.
5796
5840
  */
5797
5841
  enableToolCallNarration: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
5842
+ /**
5843
+ * Whether the model may spawn subagents without being asked to.
5844
+ *
5845
+ * On by default: a bounded search or review whose result is far smaller than
5846
+ * the reading it takes to produce is exactly the work a subagent is for, and
5847
+ * an agent that has to ask first will simply do it inline instead.
5848
+ *
5849
+ * Stated in the appended prompt whichever way it is set, rather than only
5850
+ * when off. The provider's own preset prompt carries an instruction not to
5851
+ * spawn subagents unless asked, and p4code requests that preset wholesale —
5852
+ * so there is nothing to withhold, and only a later, more specific
5853
+ * instruction can override an earlier general one.
5854
+ */
5855
+ enableUnpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
5798
5856
  automaticGitFetchInterval: Schema$1.DurationFromMillis.pipe(Schema$1.withDecodingDefault(Effect.succeed(Duration.toMillis(DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL)))),
5799
5857
  defaultThreadEnvMode: ThreadEnvMode.pipe(Schema$1.withDecodingDefault(Effect.succeed("local"))),
5800
5858
  newWorktreesStartFromOrigin: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
@@ -5929,6 +5987,7 @@ const ServerSettingsPatch = Schema$1.Struct({
5929
5987
  enableAssistantStreaming: Schema$1.optionalKey(Schema$1.Boolean),
5930
5988
  enableProviderUpdateChecks: Schema$1.optionalKey(Schema$1.Boolean),
5931
5989
  enableToolCallNarration: Schema$1.optionalKey(Schema$1.Boolean),
5990
+ enableUnpromptedSubagents: Schema$1.optionalKey(Schema$1.Boolean),
5932
5991
  automaticGitFetchInterval: Schema$1.optionalKey(Schema$1.DurationFromMillis),
5933
5992
  defaultThreadEnvMode: Schema$1.optionalKey(ThreadEnvMode),
5934
5993
  newWorktreesStartFromOrigin: Schema$1.optionalKey(Schema$1.Boolean),
@@ -7189,6 +7248,21 @@ const RepositoryKeyField = TrimmedNonEmptyString.pipe(Schema$1.annotateEncoded({
7189
7248
  * placed by hand and falls back to the priority/recency order.
7190
7249
  */
7191
7250
  const OrderKeyField = TrimmedNonEmptyString.pipe(Schema$1.annotateEncoded({ description: "Manual board position key. Lexicographic: smaller sorts higher in the column. Null means no manual position." }));
7251
+ /**
7252
+ * Where a task came from when it did not originate on this board.
7253
+ *
7254
+ * The identifier is the tracker's own, not ours: `MOBILE-12262` reads the same
7255
+ * in p4code as it does in Linear, in a commit message, and in conversation,
7256
+ * which is the whole point of carrying it. `url` is what the tracker itself
7257
+ * reported, never a guess assembled from the identifier - a Linear URL embeds
7258
+ * the workspace slug, so a constructed one would 404.
7259
+ */
7260
+ const TaskExternalRef = Schema$1.Struct({
7261
+ source: Schema$1.Literals(["linear"]),
7262
+ identifier: TrimmedNonEmptyString,
7263
+ url: TrimmedNonEmptyString
7264
+ });
7265
+ const ExternalRefField = TaskExternalRef.pipe(Schema$1.annotateEncoded({ description: "The tracker issue this task mirrors, e.g. a Linear ticket. Null for a task that originated on this board." }));
7192
7266
  const LabelsField = Schema$1.Array(TaskLabel).check(Schema$1.isMaxLength(TASK_LABELS_MAX_COUNT)).pipe(Schema$1.annotateEncoded({ description: "Free-form tags used to group and filter tasks." }));
7193
7267
  const TaskIdField = TaskId.pipe(Schema$1.annotateEncoded({ description: "Id of the task to act on - the UUID or the readable id (e.g. P4-12)." }));
7194
7268
  const Task = Schema$1.Struct({
@@ -7230,6 +7304,13 @@ const Task = Schema$1.Struct({
7230
7304
  * to a history.
7231
7305
  */
7232
7306
  threadId: Schema$1.NullOr(ThreadId),
7307
+ /**
7308
+ * The tracker issue this task mirrors, or `null` for a task that was filed
7309
+ * here. Nullable-with-default for the same version-skew reason as
7310
+ * `readableId` above: a row from a store without the migration decodes to
7311
+ * `null` rather than failing the whole board.
7312
+ */
7313
+ externalRef: Schema$1.NullOr(TaskExternalRef).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
7233
7314
  title: TrimmedNonEmptyString.check(Schema$1.isMaxLength(TASK_TITLE_MAX_LENGTH)),
7234
7315
  body: Schema$1.String,
7235
7316
  status: StoredStatusField,
@@ -7243,6 +7324,7 @@ const TaskCreateInput = Schema$1.Struct({
7243
7324
  projectId: Schema$1.optional(ProjectIdField),
7244
7325
  repositoryKey: Schema$1.optional(RepositoryKeyField),
7245
7326
  threadId: Schema$1.optional(ThreadIdField),
7327
+ externalRef: Schema$1.optional(ExternalRefField),
7246
7328
  title: TitleField,
7247
7329
  body: Schema$1.optional(BodyField),
7248
7330
  status: Schema$1.optional(StatusField),
@@ -7281,6 +7363,7 @@ const TaskUpdateInput = Schema$1.Struct({
7281
7363
  projectId: Schema$1.optional(Schema$1.NullOr(ProjectIdField)),
7282
7364
  repositoryKey: Schema$1.optional(Schema$1.NullOr(RepositoryKeyField)),
7283
7365
  threadId: Schema$1.optional(Schema$1.NullOr(ThreadIdField)),
7366
+ externalRef: Schema$1.optional(Schema$1.NullOr(ExternalRefField)),
7284
7367
  title: Schema$1.optional(TitleField),
7285
7368
  body: Schema$1.optional(BodyField),
7286
7369
  status: Schema$1.optional(StatusField),
@@ -7646,6 +7729,58 @@ var ProjectWriteFileError = class extends Schema$1.TaggedErrorClass()("ProjectWr
7646
7729
  }
7647
7730
  };
7648
7731
  //#endregion
7732
+ //#region ../../packages/contracts/src/ticket.ts
7733
+ /**
7734
+ * Reading an issue out of an external tracker.
7735
+ *
7736
+ * One call, deliberately: p4code does not sync trackers, it recognizes a ticket
7737
+ * somebody pasted and asks the tracker what it is called. Everything durable
7738
+ * that comes back is stored on a task as its {@link TaskExternalRef}.
7739
+ *
7740
+ * The tracker is reached through the MCP server the user already registered for
7741
+ * it, so there is no second place to configure credentials and no p4code-owned
7742
+ * API key. That also bounds what this can ever answer: a tracker with no MCP
7743
+ * server registered, or one never signed in to on this machine, is reported as
7744
+ * unconfigured rather than silently returning nothing.
7745
+ *
7746
+ * @module contracts/ticket
7747
+ */
7748
+ /**
7749
+ * A tracker reference as it was typed, before anything has confirmed it exists.
7750
+ *
7751
+ * Deliberately not the parsed `{ source, identifier }` pair: what a user pastes
7752
+ * is a URL as often as an id, and which tracker a bare `MOBILE-12262` belongs
7753
+ * to is a question only the resolver can answer.
7754
+ */
7755
+ const TicketResolveInput = Schema$1.Struct({ reference: TrimmedNonEmptyString });
7756
+ const TicketResolveResult = Schema$1.Struct({
7757
+ externalRef: TaskExternalRef,
7758
+ /** The issue title as the tracker reports it, for the task that mirrors it. */
7759
+ title: TrimmedNonEmptyString
7760
+ });
7761
+ /**
7762
+ * Why a reference could not be turned into a ticket.
7763
+ *
7764
+ * The reason is separate from the detail because the client acts on it: an
7765
+ * unconfigured tracker is a thing the user can fix in Settings and is worth
7766
+ * saying so about, while `unavailable` is a retry and `not_found` usually means
7767
+ * a typo. `detail` is the sentence to show; the reason decides the shape.
7768
+ */
7769
+ const TicketResolveFailureReason = Schema$1.Literals([
7770
+ "not_configured",
7771
+ "not_authorized",
7772
+ "not_found",
7773
+ "unavailable"
7774
+ ]);
7775
+ var TicketResolveError = class extends Schema$1.TaggedErrorClass()("TicketResolveError", {
7776
+ reason: TicketResolveFailureReason,
7777
+ detail: Schema$1.String
7778
+ }) {
7779
+ get message() {
7780
+ return this.detail;
7781
+ }
7782
+ };
7783
+ //#endregion
7649
7784
  //#region ../../packages/contracts/src/threadControl.ts
7650
7785
  /**
7651
7786
  * Thread control over MCP - the self-scoped toolkit an agent uses to act on
@@ -7820,11 +7955,17 @@ const AGENT_ASSET_TOTAL_MAX_BYTES = 4 * 1024 * 1024;
7820
7955
  * directory rather than the provider's. The definition travels; the bearer
7821
7956
  * tokens and API keys it references do not, and stay in each machine's own
7822
7957
  * secret store.
7958
+ * - `agent` — a subagent definition, a single markdown file in
7959
+ * `<configDir>/agents`. The name is the filename, as for `memory`. This is the
7960
+ * only artifact that bounds what a subagent is handed and what it hands back,
7961
+ * because p4code observes subagent runs rather than constructing their
7962
+ * prompts — so the I/O contract can only ship as a file the provider reads.
7823
7963
  */
7824
7964
  const AgentAssetKind = Schema$1.Literals([
7825
7965
  "skill",
7826
7966
  "memory",
7827
- "mcp"
7967
+ "mcp",
7968
+ "agent"
7828
7969
  ]);
7829
7970
  /**
7830
7971
  * An asset's name, which for a skill is also its directory name.
@@ -8691,6 +8832,7 @@ const WS_METHODS = {
8691
8832
  tasksUpdate: "tasks.update",
8692
8833
  tasksDelete: "tasks.delete",
8693
8834
  tasksStartThread: "tasks.startThread",
8835
+ ticketsResolve: "tickets.resolve",
8694
8836
  hubGetSyncStatus: "hub.getSyncStatus",
8695
8837
  hubConnect: "hub.connect",
8696
8838
  hubDisconnect: "hub.disconnect",
@@ -8998,6 +9140,17 @@ const WsMcpOAuthDisconnectRpc = Rpc.make(WS_METHODS.mcpOAuthDisconnect, {
8998
9140
  error: Schema$1.Union([McpRegistryError, EnvironmentAuthorizationError])
8999
9141
  });
9000
9142
  /**
9143
+ * Asks the tracker what a pasted reference is, without writing anything.
9144
+ *
9145
+ * Read-only on purpose: the caller decides whether the answer becomes a task.
9146
+ * A resolve that filed a row would make an accidental paste a board entry.
9147
+ */
9148
+ const WsTicketsResolveRpc = Rpc.make(WS_METHODS.ticketsResolve, {
9149
+ payload: TicketResolveInput,
9150
+ success: TicketResolveResult,
9151
+ error: Schema$1.Union([TicketResolveError, EnvironmentAuthorizationError])
9152
+ });
9153
+ /**
9001
9154
  * The board, then every change to it, for as long as the client stays
9002
9155
  * subscribed. The leading snapshot is what makes this sufficient on its own —
9003
9156
  * a client does not list first, so there is no window between the two in which
@@ -9375,7 +9528,7 @@ const WsSubscribeAuthAccessRpc = Rpc.make(WS_METHODS.subscribeAuthAccess, {
9375
9528
  error: Schema$1.Union([AuthAccessStreamError, EnvironmentAuthorizationError]),
9376
9529
  stream: true
9377
9530
  });
9378
- 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);
9531
+ 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, WsTicketsResolveRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
9379
9532
  //#endregion
9380
9533
  //#region ../../packages/shared/src/oauthScope.ts
9381
9534
  const OAUTH_SCOPE_TOKEN = /^[\u0021\u0023-\u005b\u005d-\u007e]+$/u;
@@ -9521,7 +9674,7 @@ function deriveAuthClientMetadata(input) {
9521
9674
  //#endregion
9522
9675
  //#region src/auth/EnvironmentAuthPolicy.ts
9523
9676
  var EnvironmentAuthPolicy = class extends Context.Service()("@p4code/cli/auth/EnvironmentAuthPolicy") {};
9524
- const make$73 = Effect.gen(function* () {
9677
+ const make$74 = Effect.gen(function* () {
9525
9678
  const config = yield* ServerConfig$1;
9526
9679
  const isRemoteReachable = isRemoteReachableHost(config.host);
9527
9680
  const policy = config.mode === "desktop" ? isRemoteReachable ? "remote-reachable" : "desktop-managed-local" : isRemoteReachable ? "remote-reachable" : "loopback-browser";
@@ -9539,7 +9692,7 @@ const make$73 = Effect.gen(function* () {
9539
9692
  };
9540
9693
  return EnvironmentAuthPolicy.of({ getDescriptor: () => Effect.succeed(descriptor).pipe(Effect.withSpan("EnvironmentAuthPolicy.getDescriptor")) });
9541
9694
  });
9542
- const layer$69 = Layer.effect(EnvironmentAuthPolicy, make$73);
9695
+ const layer$70 = Layer.effect(EnvironmentAuthPolicy, make$74);
9543
9696
  //#endregion
9544
9697
  //#region src/persistence/Errors.ts
9545
9698
  function summarizeSchemaIssue(issue) {
@@ -9720,7 +9873,7 @@ function toPersistenceSqlOrDecodeError$6(sqlOperation, decodeOperation, correlat
9720
9873
  cause
9721
9874
  });
9722
9875
  }
9723
- const make$72 = Effect.gen(function* () {
9876
+ const make$73 = Effect.gen(function* () {
9724
9877
  const sql = yield* SqlClient.SqlClient;
9725
9878
  const createSessionRow = SqlSchema.void({
9726
9879
  Request: CreateAuthSessionInput,
@@ -9854,7 +10007,7 @@ const make$72 = Effect.gen(function* () {
9854
10007
  setLastConnectedAt
9855
10008
  };
9856
10009
  });
9857
- const layer$68 = Layer.effect(AuthSessionRepository, make$72);
10010
+ const layer$69 = Layer.effect(AuthSessionRepository, make$73);
9858
10011
  //#endregion
9859
10012
  //#region src/auth/ServerSecretStore.ts
9860
10013
  const secretStoreErrorContext = {
@@ -9921,7 +10074,7 @@ const isSecretStoreError = Schema$1.is(SecretStoreError);
9921
10074
  const isPlatformError = (value) => Predicate.isTagged(value, "PlatformError");
9922
10075
  const isSecretAlreadyExistsError = (error) => "cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists";
9923
10076
  var ServerSecretStore = class extends Context.Service()("@p4code/cli/auth/ServerSecretStore") {};
9924
- const make$71 = Effect.gen(function* () {
10077
+ const make$72 = Effect.gen(function* () {
9925
10078
  const crypto = yield* Crypto.Crypto;
9926
10079
  const fileSystem = yield* FileSystem.FileSystem;
9927
10080
  const path = yield* Path.Path;
@@ -9991,7 +10144,7 @@ const make$71 = Effect.gen(function* () {
9991
10144
  remove
9992
10145
  });
9993
10146
  });
9994
- const layer$67 = Layer.effect(ServerSecretStore, make$71);
10147
+ const layer$68 = Layer.effect(ServerSecretStore, make$72);
9995
10148
  //#endregion
9996
10149
  //#region src/auth/SessionStore.ts
9997
10150
  var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
@@ -10229,7 +10382,7 @@ function toAuthClientSession(input) {
10229
10382
  current: false
10230
10383
  };
10231
10384
  }
10232
- const make$70 = Effect.gen(function* () {
10385
+ const make$71 = Effect.gen(function* () {
10233
10386
  const crypto = yield* Crypto.Crypto;
10234
10387
  const serverConfig = yield* ServerConfig$1;
10235
10388
  const secretStore = yield* ServerSecretStore;
@@ -10543,7 +10696,7 @@ const make$70 = Effect.gen(function* () {
10543
10696
  markDisconnected
10544
10697
  });
10545
10698
  });
10546
- const layer$66 = Layer.effect(SessionStore, make$70).pipe(Layer.provideMerge(layer$68));
10699
+ const layer$67 = Layer.effect(SessionStore, make$71).pipe(Layer.provideMerge(layer$69));
10547
10700
  //#endregion
10548
10701
  //#region src/persistence/AuthPairingLinks.ts
10549
10702
  const AuthPairingLinkRecord = Schema$1.Struct({
@@ -10604,7 +10757,7 @@ function toPersistenceSqlOrDecodeError$5(sqlOperation, decodeOperation, correlat
10604
10757
  cause
10605
10758
  });
10606
10759
  }
10607
- const make$69 = Effect.gen(function* () {
10760
+ const make$70 = Effect.gen(function* () {
10608
10761
  const sql = yield* SqlClient.SqlClient;
10609
10762
  const createPairingLinkRow = SqlSchema.void({
10610
10763
  Request: CreateAuthPairingLinkInput,
@@ -10739,7 +10892,7 @@ const make$69 = Effect.gen(function* () {
10739
10892
  getByCredential
10740
10893
  };
10741
10894
  });
10742
- const layer$65 = Layer.effect(AuthPairingLinkRepository, make$69);
10895
+ const layer$66 = Layer.effect(AuthPairingLinkRepository, make$70);
10743
10896
  //#endregion
10744
10897
  //#region src/auth/PairingGrantStore.ts
10745
10898
  var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
@@ -10834,7 +10987,7 @@ const DEV_STARTUP_TTL_HOURS = Duration.hours(24);
10834
10987
  const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
10835
10988
  const PAIRING_TOKEN_LENGTH = 12;
10836
10989
  const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
10837
- const make$68 = Effect.gen(function* () {
10990
+ const make$69 = Effect.gen(function* () {
10838
10991
  const crypto = yield* Crypto.Crypto;
10839
10992
  const config = yield* ServerConfig$1;
10840
10993
  const pairingLinks = yield* AuthPairingLinkRepository;
@@ -11032,7 +11185,7 @@ const make$68 = Effect.gen(function* () {
11032
11185
  consume
11033
11186
  });
11034
11187
  });
11035
- const layer$64 = Layer.effect(PairingGrantStore, make$68).pipe(Layer.provideMerge(layer$65));
11188
+ const layer$65 = Layer.effect(PairingGrantStore, make$69).pipe(Layer.provideMerge(layer$66));
11036
11189
  //#endregion
11037
11190
  //#region src/persistence/Migrations/001_OrchestrationEvents.ts
11038
11191
  var _001_OrchestrationEvents_default = Effect.gen(function* () {
@@ -12485,6 +12638,28 @@ var _039_ProjectionThreadsCompressMode_default = Effect.gen(function* () {
12485
12638
  `;
12486
12639
  });
12487
12640
  //#endregion
12641
+ //#region src/persistence/Migrations/040_TaskExternalRef.ts
12642
+ /**
12643
+ * The tracker issue a task mirrors, for tasks that did not start on this board.
12644
+ *
12645
+ * Stored as one JSON blob rather than three columns because nothing queries
12646
+ * inside it except the uniqueness check below, and a tracker's shape is not
12647
+ * ours to normalize - a second source will bring fields Linear does not have.
12648
+ *
12649
+ * The unique index is over the identifier alone, extracted from the JSON: it is
12650
+ * what stops a second paste of the same ticket from filing a duplicate task.
12651
+ * Partial, so the many rows with no external ref do not collide on NULL.
12652
+ */
12653
+ var _040_TaskExternalRef_default = Effect.gen(function* () {
12654
+ const sql = yield* SqlClient.SqlClient;
12655
+ yield* sql`ALTER TABLE tasks ADD COLUMN external_ref_json TEXT`;
12656
+ yield* sql`
12657
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_external_ref_identifier
12658
+ ON tasks(json_extract(external_ref_json, '$.identifier'))
12659
+ WHERE external_ref_json IS NOT NULL
12660
+ `;
12661
+ });
12662
+ //#endregion
12488
12663
  //#region src/persistence/Migrations.ts
12489
12664
  /**
12490
12665
  * MigrationsLive - Migration runner with inline loader
@@ -12700,6 +12875,11 @@ const migrationEntries = [
12700
12875
  39,
12701
12876
  "ProjectionThreadsCompressMode",
12702
12877
  _039_ProjectionThreadsCompressMode_default
12878
+ ],
12879
+ [
12880
+ 40,
12881
+ "TaskExternalRef",
12882
+ _040_TaskExternalRef_default
12703
12883
  ]
12704
12884
  ];
12705
12885
  const makeMigrationLoader = (throughId) => Migrator.fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -12887,7 +13067,7 @@ function parseBearerToken(request) {
12887
13067
  const token = header.slice(7).trim();
12888
13068
  return token.length > 0 ? token : null;
12889
13069
  }
12890
- const make$67 = Effect.gen(function* () {
13070
+ const make$68 = Effect.gen(function* () {
12891
13071
  const policy = yield* EnvironmentAuthPolicy;
12892
13072
  const bootstrapCredentials = yield* PairingGrantStore;
12893
13073
  const sessions = yield* SessionStore;
@@ -13082,9 +13262,9 @@ const make$67 = Effect.gen(function* () {
13082
13262
  issueStartupPairingUrl
13083
13263
  });
13084
13264
  });
13085
- const layer$63 = Layer.effect(EnvironmentAuth, make$67).pipe(Layer.provideMerge(layer$64), Layer.provideMerge(layer$66), Layer.provideMerge(layer$69));
13086
- const storageLayer = Layer.mergeAll(layer$67, layerConfig);
13087
- const runtimeLayer = layer$63.pipe(Layer.provideMerge(storageLayer));
13265
+ const layer$64 = Layer.effect(EnvironmentAuth, make$68).pipe(Layer.provideMerge(layer$65), Layer.provideMerge(layer$67), Layer.provideMerge(layer$70));
13266
+ const storageLayer = Layer.mergeAll(layer$68, layerConfig);
13267
+ const runtimeLayer = layer$64.pipe(Layer.provideMerge(storageLayer));
13088
13268
  //#endregion
13089
13269
  //#region src/cliAuthFormat.ts
13090
13270
  const newline = "\n";
@@ -13839,7 +14019,7 @@ const runWithEnvironmentAuth = (flags, run, options) => Effect.gen(function* ()
13839
14019
  }
13840
14020
  return yield* Effect.gen(function* () {
13841
14021
  return yield* run(yield* EnvironmentAuth);
13842
- }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$70(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
14022
+ }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$71(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
13843
14023
  });
13844
14024
  const ttlFlag = Flag.string("ttl").pipe(Flag.withSchema(DurationFromString), Flag.withDescription("TTL, for example `5m`, `1h`, `30d`, or `15 minutes`."), Flag.optional);
13845
14025
  const jsonFlag = Flag.boolean("json").pipe(Flag.withDescription("Emit JSON instead of human-readable output."), Flag.withDefault(false));
@@ -13979,7 +14159,7 @@ const DEFAULT_LIMITS = {
13979
14159
  windowMillis: FAILURE_WINDOW_MS,
13980
14160
  blockMillis: BLOCK_DURATION_MS
13981
14161
  };
13982
- const make$66 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
14162
+ const make$67 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
13983
14163
  const state = yield* Ref.make(initialThrottleState);
13984
14164
  return HubAuthThrottle.of({
13985
14165
  shouldRefuse: Effect.gen(function* () {
@@ -13993,7 +14173,7 @@ const make$66 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LI
13993
14173
  })
13994
14174
  });
13995
14175
  });
13996
- const layer$62 = Layer.effect(HubAuthThrottle, make$66());
14176
+ const layer$63 = Layer.effect(HubAuthThrottle, make$67());
13997
14177
  //#endregion
13998
14178
  //#region src/hub/HubAuth.ts
13999
14179
  /**
@@ -14307,7 +14487,10 @@ const DeleteTaskInput = Schema$1.Struct({ taskId: TaskId });
14307
14487
  var TaskRepository = class extends Context.Service()("@p4code/cli/persistence/Services/Tasks/TaskRepository") {};
14308
14488
  //#endregion
14309
14489
  //#region src/persistence/Layers/Tasks.ts
14310
- const TaskDbRow = Task.mapFields(Struct.assign({ labels: Schema$1.fromJsonString(Schema$1.Array(TaskLabel)) }));
14490
+ const TaskDbRow = Task.mapFields(Struct.assign({
14491
+ labels: Schema$1.fromJsonString(Schema$1.Array(TaskLabel)),
14492
+ externalRef: Schema$1.NullOr(Schema$1.fromJsonString(TaskExternalRef))
14493
+ }));
14311
14494
  /**
14312
14495
  * The `list` filter with every field normalized to a bound `null` rather than
14313
14496
  * an absent key. Each predicate is then `(:param IS NULL OR column = :param)`,
@@ -14327,6 +14510,7 @@ const TASK_COLUMNS = `
14327
14510
  project_id AS "projectId",
14328
14511
  repository_key AS "repositoryKey",
14329
14512
  thread_id AS "threadId",
14513
+ external_ref_json AS "externalRef",
14330
14514
  title,
14331
14515
  body,
14332
14516
  status,
@@ -14350,6 +14534,7 @@ const makeTaskRepository = Effect.gen(function* () {
14350
14534
  project_id,
14351
14535
  repository_key,
14352
14536
  thread_id,
14537
+ external_ref_json,
14353
14538
  title,
14354
14539
  body,
14355
14540
  status,
@@ -14366,6 +14551,7 @@ const makeTaskRepository = Effect.gen(function* () {
14366
14551
  ${row.projectId},
14367
14552
  ${row.repositoryKey},
14368
14553
  ${row.threadId},
14554
+ ${row.externalRef === null ? null : JSON.stringify(row.externalRef)},
14369
14555
  ${row.title},
14370
14556
  ${row.body},
14371
14557
  ${row.status},
@@ -14382,6 +14568,7 @@ const makeTaskRepository = Effect.gen(function* () {
14382
14568
  project_id = excluded.project_id,
14383
14569
  repository_key = excluded.repository_key,
14384
14570
  thread_id = excluded.thread_id,
14571
+ external_ref_json = excluded.external_ref_json,
14385
14572
  title = excluded.title,
14386
14573
  body = excluded.body,
14387
14574
  status = excluded.status,
@@ -14480,6 +14667,7 @@ const makeTaskRepository = Effect.gen(function* () {
14480
14667
  ...input.projectId !== void 0 ? { projectId: input.projectId } : {},
14481
14668
  ...input.repositoryKey !== void 0 ? { repositoryKey: input.repositoryKey } : {},
14482
14669
  ...input.threadId !== void 0 ? { threadId: input.threadId } : {},
14670
+ ...input.externalRef !== void 0 ? { externalRef: input.externalRef } : {},
14483
14671
  ...input.title !== void 0 ? { title: input.title } : {},
14484
14672
  ...input.body !== void 0 ? { body: input.body } : {},
14485
14673
  ...input.status !== void 0 ? { status: input.status } : {},
@@ -14651,6 +14839,7 @@ const createTaskRoute = HttpRouter.add("POST", "/tasks", respondToHubFailures(Ef
14651
14839
  projectId: input.projectId ?? null,
14652
14840
  repositoryKey: input.repositoryKey ?? null,
14653
14841
  threadId: input.threadId ?? null,
14842
+ externalRef: input.externalRef ?? null,
14654
14843
  title: input.title,
14655
14844
  body: input.body ?? "",
14656
14845
  status: input.status ?? "backlog",
@@ -14792,7 +14981,7 @@ const deleteAssetRoute = HttpRouter.add("DELETE", "/assets/:kind/:name", respond
14792
14981
  const outcome = yield* (yield* AgentAssetRepository).deleteByName(requested);
14793
14982
  return outcome._tag === "conflict" ? assetConflict(Option.getOrNull(outcome.current)) : HttpServerResponse.empty({ status: 204 });
14794
14983
  })));
14795
- const layer$61 = Layer.mergeAll(healthRoute, listTasksRoute, getTaskRoute, createTaskRoute, updateTaskRoute, deleteTaskRoute, listAssetsRoute, getAssetRoute, putAssetRoute, deleteAssetRoute);
14984
+ const layer$62 = Layer.mergeAll(healthRoute, listTasksRoute, getTaskRoute, createTaskRoute, updateTaskRoute, deleteTaskRoute, listAssetsRoute, getAssetRoute, putAssetRoute, deleteAssetRoute);
14796
14985
  //#endregion
14797
14986
  //#region src/hub/Migrations/001_Tasks.ts
14798
14987
  /**
@@ -14954,6 +15143,22 @@ var _005_TaskOrderKeys_default = Effect.gen(function* () {
14954
15143
  yield* (yield* SqlClient.SqlClient)`ALTER TABLE tasks ADD COLUMN order_key TEXT`;
14955
15144
  });
14956
15145
  //#endregion
15146
+ //#region src/hub/Migrations/006_TaskExternalRef.ts
15147
+ /**
15148
+ * The tracker issue a task mirrors, mirroring the environment server's
15149
+ * migration 040. The hub carries the column so a task filed against a Linear
15150
+ * ticket on one machine still reads as that ticket on another.
15151
+ */
15152
+ var _006_TaskExternalRef_default = Effect.gen(function* () {
15153
+ const sql = yield* SqlClient.SqlClient;
15154
+ yield* sql`ALTER TABLE tasks ADD COLUMN external_ref_json TEXT`;
15155
+ yield* sql`
15156
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_external_ref_identifier
15157
+ ON tasks(json_extract(external_ref_json, '$.identifier'))
15158
+ WHERE external_ref_json IS NOT NULL
15159
+ `;
15160
+ });
15161
+ //#endregion
14957
15162
  //#region src/hub/Migrations.ts
14958
15163
  /**
14959
15164
  * Hub migrations.
@@ -14989,6 +15194,11 @@ const hubMigrationEntries = [
14989
15194
  5,
14990
15195
  "TaskOrderKeys",
14991
15196
  _005_TaskOrderKeys_default
15197
+ ],
15198
+ [
15199
+ 6,
15200
+ "TaskExternalRef",
15201
+ _006_TaskExternalRef_default
14992
15202
  ]
14993
15203
  ];
14994
15204
  const hubMigrationLoader = Migrator.fromRecord(Object.fromEntries(hubMigrationEntries.map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -15071,7 +15281,7 @@ const announce = Layer.effectDiscard(Effect.gen(function* () {
15071
15281
  });
15072
15282
  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 });
15073
15283
  }));
15074
- const makeHubLayer = HttpRouter.serve(layer$61).pipe(Layer.provideMerge(announce), Layer.provideMerge(HubHttpServerLive), Layer.provideMerge(layer$62), Layer.provideMerge(TaskRepositoryLive), Layer.provideMerge(AgentAssetRepositoryLive), Layer.provideMerge(HubPersistenceLive));
15284
+ const makeHubLayer = HttpRouter.serve(layer$62).pipe(Layer.provideMerge(announce), Layer.provideMerge(HubHttpServerLive), Layer.provideMerge(layer$63), Layer.provideMerge(TaskRepositoryLive), Layer.provideMerge(AgentAssetRepositoryLive), Layer.provideMerge(HubPersistenceLive));
15075
15285
  const runHub = Layer.launch(makeHubLayer);
15076
15286
  //#endregion
15077
15287
  //#region src/atomicWrite.ts
@@ -15195,7 +15405,7 @@ function stripDefaultServerSettings(current, defaults) {
15195
15405
  }
15196
15406
  return Object.is(current, defaults) ? void 0 : current;
15197
15407
  }
15198
- const make$65 = Effect.gen(function* () {
15408
+ const make$66 = Effect.gen(function* () {
15199
15409
  const { settingsPath } = yield* ServerConfig$1;
15200
15410
  const fs = yield* FileSystem.FileSystem;
15201
15411
  const pathService = yield* Path.Path;
@@ -15416,7 +15626,7 @@ const make$65 = Effect.gen(function* () {
15416
15626
  }
15417
15627
  };
15418
15628
  });
15419
- const layer$60 = Layer.effect(ServerSettingsService, make$65);
15629
+ const layer$61 = Layer.effect(ServerSettingsService, make$66);
15420
15630
  //#endregion
15421
15631
  //#region src/pathExpansion.ts
15422
15632
  /**
@@ -15532,7 +15742,15 @@ function buildSkillOverrides(disabled, discovered) {
15532
15742
  * @module provider/Drivers/ClaudeSkills
15533
15743
  */
15534
15744
  const FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
15535
- function parseSkillFrontmatter(contents) {
15745
+ /**
15746
+ * The `name`/`description` header a skill and a subagent definition both carry.
15747
+ *
15748
+ * Exported because agent definitions are the same header in a different file,
15749
+ * and the sync validates them before publishing one. A second copy of this
15750
+ * would be a second answer to "does this load in the provider", which is the
15751
+ * one question both callers are asking.
15752
+ */
15753
+ function parseMarkdownFrontmatter(contents) {
15536
15754
  const match = FRONTMATTER_PATTERN.exec(contents);
15537
15755
  if (!match) return { kind: "missing" };
15538
15756
  let parsed;
@@ -15583,6 +15801,19 @@ const resolveClaudeUserSkillsDir = Effect.fn("resolveClaudeUserSkillsDir")(funct
15583
15801
  return path.join(configDirPath, "skills");
15584
15802
  });
15585
15803
  /**
15804
+ * The user-scope subagent directory the CLI would read.
15805
+ *
15806
+ * Same resolution as the skills directory beside it, for the same reason: the
15807
+ * sync materializes into exactly the directory the provider loads from, and
15808
+ * deriving it a second time is how the two come to disagree about where an
15809
+ * agent definition lives.
15810
+ */
15811
+ const resolveClaudeUserAgentsDir = Effect.fn("resolveClaudeUserAgentsDir")(function* (config, environment, cwd) {
15812
+ const path = yield* Path.Path;
15813
+ const configDirPath = yield* resolveClaudeConfigDirPath(config, environment ?? process.env, cwd);
15814
+ return path.join(configDirPath, "agents");
15815
+ });
15816
+ /**
15586
15817
  * Enumerate Claude Code skills from the user config dir and the workspace.
15587
15818
  * Discovery is best-effort: unreadable roots and malformed skill entries are
15588
15819
  * skipped so a broken skill never degrades the provider snapshot. On name
@@ -15607,7 +15838,7 @@ const discoverClaudeSkills = Effect.fn("discoverClaudeSkills")(function* (config
15607
15838
  const skillPath = path.join(root.directory, entry, "SKILL.md");
15608
15839
  const contents = yield* fileSystem.readFileString(skillPath).pipe(Effect.orElseSucceed(() => void 0));
15609
15840
  if (contents === void 0) continue;
15610
- const frontmatter = parseSkillFrontmatter(contents);
15841
+ const frontmatter = parseMarkdownFrontmatter(contents);
15611
15842
  if (frontmatter.kind === "malformed") continue;
15612
15843
  const name = (frontmatter.kind === "parsed" ? frontmatter.name : void 0) ?? entry.trim();
15613
15844
  if (!name) continue;
@@ -15737,7 +15968,7 @@ function claudeEntryFromRegistration(registration) {
15737
15968
  };
15738
15969
  }
15739
15970
  var ClaudeMcpFiles = class extends Context.Service()("@p4code/cli/mcp/ClaudeMcpFiles") {};
15740
- const make$64 = Effect.gen(function* () {
15971
+ const make$65 = Effect.gen(function* () {
15741
15972
  const fileSystem = yield* FileSystem.FileSystem;
15742
15973
  const path = yield* Path.Path;
15743
15974
  const services = yield* Effect.context();
@@ -15802,7 +16033,7 @@ const make$64 = Effect.gen(function* () {
15802
16033
  removeProject: (projectDir, name) => removeAt(Effect.succeed(projectFile(projectDir)))(name)
15803
16034
  };
15804
16035
  });
15805
- const layer$59 = Layer.effect(ClaudeMcpFiles, make$64);
16036
+ const layer$60 = Layer.effect(ClaudeMcpFiles, make$65);
15806
16037
  Layer.succeed(ClaudeMcpFiles, {
15807
16038
  readUser: Effect.succeed([]),
15808
16039
  upsertUser: () => Effect.fail(new McpRegistryError({ detail: "No Claude config in tests." })),
@@ -15938,7 +16169,7 @@ const decodeClientRegistration = Schema$1.decodeUnknownExit(ClientRegistrationRe
15938
16169
  const decodeTokenResponse = Schema$1.decodeUnknownExit(TokenResponse);
15939
16170
  var McpOAuth = class extends Context.Service()("@p4code/cli/mcp/McpOAuth") {};
15940
16171
  const registryError = (detail) => new McpRegistryError({ detail });
15941
- const make$63 = Effect.gen(function* () {
16172
+ const make$64 = Effect.gen(function* () {
15942
16173
  const config = yield* ServerConfig$1;
15943
16174
  const secrets = yield* ServerSecretStore;
15944
16175
  const http = yield* HttpClient.HttpClient;
@@ -16258,7 +16489,7 @@ const make$63 = Effect.gen(function* () {
16258
16489
  accessTokenFor
16259
16490
  };
16260
16491
  });
16261
- const layer$58 = Layer.effect(McpOAuth, make$63);
16492
+ const layer$59 = Layer.effect(McpOAuth, make$64);
16262
16493
  Layer.succeed(McpOAuth, {
16263
16494
  statusFor: () => Effect.succeed(Option.none()),
16264
16495
  begin: () => Effect.fail(new McpRegistryError({ detail: "OAuth sign-in is not available." })),
@@ -16276,7 +16507,7 @@ const decodeRegistration$1 = Schema$1.decodeUnknownExit(RegistrationFromJson$1);
16276
16507
  const encodeRegistration = Schema$1.encodeSync(RegistrationFromJson$1);
16277
16508
  var McpRegistry = class extends Context.Service()("@p4code/cli/mcp/McpRegistry") {};
16278
16509
  const slotsOf = (registration) => registration.secrets ?? [];
16279
- const make$62 = Effect.gen(function* () {
16510
+ const make$63 = Effect.gen(function* () {
16280
16511
  const config = yield* ServerConfig$1;
16281
16512
  const secrets = yield* ServerSecretStore;
16282
16513
  const oauth = yield* McpOAuth;
@@ -16427,7 +16658,289 @@ const make$62 = Effect.gen(function* () {
16427
16658
  }).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("mcp registry resolve failed", { cause }).pipe(Effect.as({}))))
16428
16659
  };
16429
16660
  });
16430
- const layer$57 = Layer.effect(McpRegistry, make$62);
16661
+ const layer$58 = Layer.effect(McpRegistry, make$63);
16662
+ //#endregion
16663
+ //#region src/sync/skillDirectory.ts
16664
+ /**
16665
+ * Reading and writing skill directories on disk.
16666
+ *
16667
+ * Kept apart from the sync itself so the filesystem shape has its own tests,
16668
+ * and so the rules about what is refusable — binaries, oversized files, deep
16669
+ * trees — live in one place rather than being rediscovered at each call site.
16670
+ *
16671
+ * @module sync/skillDirectory
16672
+ */
16673
+ /** Bounded so a stray symlink or a vendored `node_modules` cannot walk forever. */
16674
+ const MAX_SKILL_DEPTH = 4;
16675
+ /**
16676
+ * Entries that are never part of a skill, skipped rather than refused.
16677
+ *
16678
+ * The distinction matters. Refusing an asset because it cannot be represented
16679
+ * faithfully is right — a half-materialized skill loads and misbehaves. But a
16680
+ * `.DS_Store` is not part of the skill at all, and macOS drops one into any
16681
+ * directory that has been opened in Finder, so refusing on its account rejects
16682
+ * a perfectly good skill for a reason its author cannot see.
16683
+ *
16684
+ * These could not sync in any case: `AgentAssetFilePath` rejects a leading dot,
16685
+ * so a dotfile has no representable path even if it were text.
16686
+ */
16687
+ const IGNORED_ENTRIES = /* @__PURE__ */ new Set([
16688
+ ".DS_Store",
16689
+ "Thumbs.db",
16690
+ ".git",
16691
+ ".svn",
16692
+ ".hg",
16693
+ "node_modules",
16694
+ "__pycache__",
16695
+ ".venv",
16696
+ ".pytest_cache",
16697
+ ".ruff_cache",
16698
+ ".mypy_cache"
16699
+ ]);
16700
+ /**
16701
+ * Anything else with a leading dot is skipped too — an editor's `.idea`, a
16702
+ * `.gitignore` that belongs to the checkout rather than the skill. The rule is
16703
+ * the same one the path schema already enforces, applied earlier so it reads as
16704
+ * "not part of the skill" instead of "this skill is broken".
16705
+ */
16706
+ const isIgnoredEntry = (entry) => IGNORED_ENTRIES.has(entry) || entry.startsWith(".");
16707
+ /**
16708
+ * A file is binary if decoding it yields a NUL.
16709
+ *
16710
+ * Cheap, and right for what is being asked: the question is not "is this valid
16711
+ * UTF-8" but "can this round-trip through a JSON text column", and a NUL is
16712
+ * what actually breaks that on the way back out of SQLite.
16713
+ *
16714
+ * Exported so the single-file kinds ask it the same way this one does, rather
16715
+ * than each keeping a copy that could drift into a different answer.
16716
+ */
16717
+ const looksBinary$1 = (content) => content.includes("\0");
16718
+ /**
16719
+ * Read one directory as an asset's files, or say why it cannot be one.
16720
+ *
16721
+ * Exported because memory's `global-memory/` is the same shape by a different
16722
+ * name: a directory whose whole contents are the asset. Sharing this keeps the
16723
+ * refusal rules — depth, file count, binaries, size — identical for both,
16724
+ * rather than letting a second copy drift into accepting what the first
16725
+ * rejects.
16726
+ */
16727
+ const readSkillFiles = Effect.fn("skillDirectory.readSkillFiles")(function* (root) {
16728
+ const fileSystem = yield* FileSystem.FileSystem;
16729
+ const path = yield* Path.Path;
16730
+ const files = [];
16731
+ const walk = Effect.fn("skillDirectory.walk")(function* (directory, prefix, depth) {
16732
+ if (depth > MAX_SKILL_DEPTH) return "too-deep";
16733
+ const entries = yield* fileSystem.readDirectory(directory).pipe(Effect.orElseSucceed(() => []));
16734
+ for (const entry of [...entries].sort()) {
16735
+ if (isIgnoredEntry(entry)) continue;
16736
+ const absolute = path.join(directory, entry);
16737
+ const relative = prefix === "" ? entry : `${prefix}/${entry}`;
16738
+ const info = yield* fileSystem.stat(absolute).pipe(Effect.orElseSucceed(() => void 0));
16739
+ if (info === void 0) return "unreadable";
16740
+ if (info.type === "Directory") {
16741
+ const nested = yield* walk(absolute, relative, depth + 1);
16742
+ if (nested !== null) return nested;
16743
+ continue;
16744
+ }
16745
+ if (info.type !== "File") continue;
16746
+ if (files.length >= 512) return "too-many-files";
16747
+ const content = yield* fileSystem.readFileString(absolute).pipe(Effect.orElseSucceed(() => void 0));
16748
+ if (content === void 0) return "unreadable";
16749
+ if (looksBinary$1(content)) return "binary-file";
16750
+ if (content.length > 524288) return "file-too-large";
16751
+ files.push({
16752
+ path: relative,
16753
+ content
16754
+ });
16755
+ }
16756
+ return null;
16757
+ });
16758
+ const refusal = yield* walk(root, "", 1);
16759
+ if (refusal !== null) return { refusal };
16760
+ if (agentAssetTotalBytes(files) > 4194304) return { refusal: "asset-too-large" };
16761
+ return { files };
16762
+ });
16763
+ /**
16764
+ * Read every skill directory under a user-scope `skills/` root.
16765
+ *
16766
+ * A directory with no `SKILL.md` is not a skill and is skipped silently —
16767
+ * Claude Code would ignore it too, so reporting it would be noise about
16768
+ * something that was never a skill in the first place.
16769
+ */
16770
+ const readSkillDirectory = Effect.fn("skillDirectory.readSkillDirectory")(function* (skillsRoot) {
16771
+ const fileSystem = yield* FileSystem.FileSystem;
16772
+ const path = yield* Path.Path;
16773
+ const entries = yield* fileSystem.readDirectory(skillsRoot).pipe(Effect.orElseSucceed(() => []));
16774
+ const skills = [];
16775
+ const refused = [];
16776
+ for (const entry of [...entries].sort()) {
16777
+ if (isIgnoredEntry(entry)) continue;
16778
+ const directory = path.join(skillsRoot, entry);
16779
+ if ((yield* fileSystem.stat(directory).pipe(Effect.orElseSucceed(() => void 0)))?.type !== "Directory") continue;
16780
+ if (!(yield* fileSystem.exists(path.join(directory, "SKILL.md")).pipe(Effect.orElseSucceed(() => false)))) continue;
16781
+ const read = yield* readSkillFiles(directory);
16782
+ if ("refusal" in read) {
16783
+ refused.push({
16784
+ name: entry,
16785
+ reason: read.refusal
16786
+ });
16787
+ continue;
16788
+ }
16789
+ skills.push({
16790
+ name: entry,
16791
+ files: read.files
16792
+ });
16793
+ }
16794
+ return {
16795
+ skills,
16796
+ refused
16797
+ };
16798
+ });
16799
+ /**
16800
+ * Replace a skill directory with exactly the asset's files.
16801
+ *
16802
+ * Written to a sibling and then swapped, so a crash mid-write leaves the old
16803
+ * skill intact rather than half of the new one. A skill is loaded as a unit and
16804
+ * a partially written one is instructions with the middle missing, which an
16805
+ * agent will happily follow.
16806
+ */
16807
+ const writeSkillDirectory = Effect.fn("skillDirectory.writeSkillDirectory")(function* (input) {
16808
+ const fileSystem = yield* FileSystem.FileSystem;
16809
+ const path = yield* Path.Path;
16810
+ const target = path.join(input.skillsRoot, input.name);
16811
+ const staging = path.join(input.skillsRoot, `.${input.name}.p4code-staging`);
16812
+ yield* fileSystem.makeDirectory(input.skillsRoot, { recursive: true });
16813
+ yield* fileSystem.remove(staging, {
16814
+ recursive: true,
16815
+ force: true
16816
+ });
16817
+ yield* fileSystem.makeDirectory(staging, { recursive: true });
16818
+ for (const file of input.files) {
16819
+ const destination = path.join(staging, file.path);
16820
+ yield* fileSystem.makeDirectory(path.dirname(destination), { recursive: true });
16821
+ yield* fileSystem.writeFileString(destination, file.content);
16822
+ }
16823
+ yield* fileSystem.remove(target, {
16824
+ recursive: true,
16825
+ force: true
16826
+ });
16827
+ yield* fileSystem.rename(staging, target);
16828
+ });
16829
+ const removeSkillDirectory = Effect.fn("skillDirectory.removeSkillDirectory")(function* (input) {
16830
+ const fileSystem = yield* FileSystem.FileSystem;
16831
+ const path = yield* Path.Path;
16832
+ yield* fileSystem.remove(path.join(input.skillsRoot, input.name), {
16833
+ recursive: true,
16834
+ force: true
16835
+ });
16836
+ });
16837
+ /**
16838
+ * Whether this text is a definition the provider would actually load.
16839
+ *
16840
+ * Both fields, not just the header. `name` is how a subagent is addressed and
16841
+ * `description` is how the orchestrator decides to address it, so a definition
16842
+ * missing either is present on disk and unreachable in practice.
16843
+ */
16844
+ const isLoadableAgentDefinition = (content) => {
16845
+ const frontmatter = parseMarkdownFrontmatter(content);
16846
+ return frontmatter.kind === "parsed" && frontmatter.name !== void 0 && frontmatter.description !== void 0;
16847
+ };
16848
+ /**
16849
+ * Every definition in the directory, as syncable assets.
16850
+ *
16851
+ * A file that will not load is refused rather than skipped, unlike a stray
16852
+ * `.DS_Store`: a `.md` in this directory is meant to be an agent, so a broken
16853
+ * header is a fault worth reporting instead of noise to ignore.
16854
+ */
16855
+ const readAgentDefinitions = Effect.fn("agentDefinitionFiles.read")(function* (agentsRoot) {
16856
+ const fileSystem = yield* FileSystem.FileSystem;
16857
+ const path = yield* Path.Path;
16858
+ const entries = yield* fileSystem.readDirectory(agentsRoot).pipe(Effect.orElseSucceed(() => []));
16859
+ const skills = [];
16860
+ const refused = [];
16861
+ for (const entry of [...entries].sort()) {
16862
+ if (!entry.endsWith(".md") || entry.startsWith(".")) continue;
16863
+ const absolute = path.join(agentsRoot, entry);
16864
+ if ((yield* fileSystem.stat(absolute).pipe(Effect.orElseSucceed(() => void 0)))?.type !== "File") continue;
16865
+ const content = yield* fileSystem.readFileString(absolute).pipe(Effect.orElseSucceed(() => void 0));
16866
+ if (content === void 0) {
16867
+ refused.push({
16868
+ name: entry,
16869
+ reason: "unreadable"
16870
+ });
16871
+ continue;
16872
+ }
16873
+ if (looksBinary$1(content)) {
16874
+ refused.push({
16875
+ name: entry,
16876
+ reason: "binary-file"
16877
+ });
16878
+ continue;
16879
+ }
16880
+ if (content.length > 524288) {
16881
+ refused.push({
16882
+ name: entry,
16883
+ reason: "file-too-large"
16884
+ });
16885
+ continue;
16886
+ }
16887
+ if (!isLoadableAgentDefinition(content)) {
16888
+ refused.push({
16889
+ name: entry,
16890
+ reason: "unreadable"
16891
+ });
16892
+ continue;
16893
+ }
16894
+ const files = [{
16895
+ path: entry,
16896
+ content
16897
+ }];
16898
+ if (agentAssetTotalBytes(files) > 4194304) {
16899
+ refused.push({
16900
+ name: entry,
16901
+ reason: "asset-too-large"
16902
+ });
16903
+ continue;
16904
+ }
16905
+ skills.push({
16906
+ name: entry,
16907
+ files
16908
+ });
16909
+ }
16910
+ return {
16911
+ skills,
16912
+ refused
16913
+ };
16914
+ });
16915
+ /**
16916
+ * Write a definition that arrived from the hub.
16917
+ *
16918
+ * Refused unless it is exactly one markdown file whose path is the asset's own
16919
+ * name and whose frontmatter would load. The last check is the one that matters
16920
+ * across machines: it is what stops a hub row edited by hand from installing a
16921
+ * file the provider quietly ignores on every machine that syncs it.
16922
+ *
16923
+ * Written atomically because a definition is read whole when a subagent starts,
16924
+ * and half of one is instructions with the contract missing.
16925
+ */
16926
+ const writeAgentDefinition = Effect.fn("agentDefinitionFiles.write")(function* (input) {
16927
+ const fileSystem = yield* FileSystem.FileSystem;
16928
+ const path = yield* Path.Path;
16929
+ const file = input.files[0];
16930
+ if (!input.name.endsWith(".md") || input.files.length !== 1 || file === void 0 || file.path !== input.name) return "refused";
16931
+ if (!isLoadableAgentDefinition(file.content)) return "refused";
16932
+ yield* fileSystem.makeDirectory(input.agentsRoot, { recursive: true }).pipe(Effect.ignore);
16933
+ return yield* writeFileStringAtomically({
16934
+ filePath: path.join(input.agentsRoot, input.name),
16935
+ contents: file.content
16936
+ }).pipe(Effect.as("written"), Effect.orElseSucceed(() => "failed"));
16937
+ });
16938
+ const removeAgentDefinition = Effect.fn("agentDefinitionFiles.remove")(function* (input) {
16939
+ if (!input.name.endsWith(".md")) return;
16940
+ const fileSystem = yield* FileSystem.FileSystem;
16941
+ const path = yield* Path.Path;
16942
+ yield* fileSystem.remove(path.join(input.agentsRoot, input.name), { force: true });
16943
+ });
16431
16944
  //#endregion
16432
16945
  //#region src/sync/HubLink.ts
16433
16946
  /**
@@ -16524,7 +17037,7 @@ const formatHubLink = (input) => encodeStoredHubLink({
16524
17037
  shareMode: input.shareMode
16525
17038
  });
16526
17039
  const fromEnvironment = (environment) => validateHubLink(environment.P4CODE_HUB_URL ?? "", environment.P4CODE_HUB_TOKEN ?? "");
16527
- const make$61 = Effect.fn("HubLink.make")(function* (environment) {
17040
+ const make$62 = Effect.fn("HubLink.make")(function* (environment) {
16528
17041
  const secrets = yield* ServerSecretStore;
16529
17042
  const env = environment ?? process.env;
16530
17043
  const fromEnv = fromEnvironment(env);
@@ -16590,7 +17103,7 @@ const make$61 = Effect.fn("HubLink.make")(function* (environment) {
16590
17103
  })
16591
17104
  };
16592
17105
  });
16593
- const layer$56 = Layer.effect(HubLink, make$61());
17106
+ const layer$57 = Layer.effect(HubLink, make$62());
16594
17107
  //#endregion
16595
17108
  //#region src/sync/HubAssetClient.ts
16596
17109
  /**
@@ -16623,7 +17136,7 @@ const decodeAssetListPage = Schema$1.decodeUnknownEffect(AssetListPage);
16623
17136
  const decodeConflictBody$1 = Schema$1.decodeUnknownEffect(ConflictBody$1);
16624
17137
  const decodeAsset = Schema$1.decodeUnknownEffect(AgentAsset);
16625
17138
  var HubAssetClient = class extends Context.Service()("@p4code/cli/sync/HubAssetClient") {};
16626
- const make$60 = Effect.gen(function* () {
17139
+ const make$61 = Effect.gen(function* () {
16627
17140
  const http = yield* HttpClient.HttpClient;
16628
17141
  const link = yield* HubLink;
16629
17142
  const requireSettings = Effect.gen(function* () {
@@ -16705,7 +17218,7 @@ const make$60 = Effect.gen(function* () {
16705
17218
  remove
16706
17219
  };
16707
17220
  });
16708
- const layer$55 = Layer.effect(HubAssetClient, make$60);
17221
+ const layer$56 = Layer.effect(HubAssetClient, make$61);
16709
17222
  //#endregion
16710
17223
  //#region src/sync/mcpRegistrationFiles.ts
16711
17224
  /**
@@ -16802,178 +17315,6 @@ const removeMcpRegistration = Effect.fn("mcpRegistrationFiles.remove")(function*
16802
17315
  yield* fileSystem.remove(path.join(input.registrationsRoot, input.name), { force: true });
16803
17316
  });
16804
17317
  //#endregion
16805
- //#region src/sync/skillDirectory.ts
16806
- /**
16807
- * Reading and writing skill directories on disk.
16808
- *
16809
- * Kept apart from the sync itself so the filesystem shape has its own tests,
16810
- * and so the rules about what is refusable — binaries, oversized files, deep
16811
- * trees — live in one place rather than being rediscovered at each call site.
16812
- *
16813
- * @module sync/skillDirectory
16814
- */
16815
- /** Bounded so a stray symlink or a vendored `node_modules` cannot walk forever. */
16816
- const MAX_SKILL_DEPTH = 4;
16817
- /**
16818
- * Entries that are never part of a skill, skipped rather than refused.
16819
- *
16820
- * The distinction matters. Refusing an asset because it cannot be represented
16821
- * faithfully is right — a half-materialized skill loads and misbehaves. But a
16822
- * `.DS_Store` is not part of the skill at all, and macOS drops one into any
16823
- * directory that has been opened in Finder, so refusing on its account rejects
16824
- * a perfectly good skill for a reason its author cannot see.
16825
- *
16826
- * These could not sync in any case: `AgentAssetFilePath` rejects a leading dot,
16827
- * so a dotfile has no representable path even if it were text.
16828
- */
16829
- const IGNORED_ENTRIES = /* @__PURE__ */ new Set([
16830
- ".DS_Store",
16831
- "Thumbs.db",
16832
- ".git",
16833
- ".svn",
16834
- ".hg",
16835
- "node_modules",
16836
- "__pycache__",
16837
- ".venv",
16838
- ".pytest_cache",
16839
- ".ruff_cache",
16840
- ".mypy_cache"
16841
- ]);
16842
- /**
16843
- * Anything else with a leading dot is skipped too — an editor's `.idea`, a
16844
- * `.gitignore` that belongs to the checkout rather than the skill. The rule is
16845
- * the same one the path schema already enforces, applied earlier so it reads as
16846
- * "not part of the skill" instead of "this skill is broken".
16847
- */
16848
- const isIgnoredEntry = (entry) => IGNORED_ENTRIES.has(entry) || entry.startsWith(".");
16849
- /**
16850
- * A file is binary if decoding it yields a NUL.
16851
- *
16852
- * Cheap, and right for what is being asked: the question is not "is this valid
16853
- * UTF-8" but "can this round-trip through a JSON text column", and a NUL is
16854
- * what actually breaks that on the way back out of SQLite.
16855
- */
16856
- const looksBinary$1 = (content) => content.includes("\0");
16857
- /**
16858
- * Read one directory as an asset's files, or say why it cannot be one.
16859
- *
16860
- * Exported because memory's `global-memory/` is the same shape by a different
16861
- * name: a directory whose whole contents are the asset. Sharing this keeps the
16862
- * refusal rules — depth, file count, binaries, size — identical for both,
16863
- * rather than letting a second copy drift into accepting what the first
16864
- * rejects.
16865
- */
16866
- const readSkillFiles = Effect.fn("skillDirectory.readSkillFiles")(function* (root) {
16867
- const fileSystem = yield* FileSystem.FileSystem;
16868
- const path = yield* Path.Path;
16869
- const files = [];
16870
- const walk = Effect.fn("skillDirectory.walk")(function* (directory, prefix, depth) {
16871
- if (depth > MAX_SKILL_DEPTH) return "too-deep";
16872
- const entries = yield* fileSystem.readDirectory(directory).pipe(Effect.orElseSucceed(() => []));
16873
- for (const entry of [...entries].sort()) {
16874
- if (isIgnoredEntry(entry)) continue;
16875
- const absolute = path.join(directory, entry);
16876
- const relative = prefix === "" ? entry : `${prefix}/${entry}`;
16877
- const info = yield* fileSystem.stat(absolute).pipe(Effect.orElseSucceed(() => void 0));
16878
- if (info === void 0) return "unreadable";
16879
- if (info.type === "Directory") {
16880
- const nested = yield* walk(absolute, relative, depth + 1);
16881
- if (nested !== null) return nested;
16882
- continue;
16883
- }
16884
- if (info.type !== "File") continue;
16885
- if (files.length >= 512) return "too-many-files";
16886
- const content = yield* fileSystem.readFileString(absolute).pipe(Effect.orElseSucceed(() => void 0));
16887
- if (content === void 0) return "unreadable";
16888
- if (looksBinary$1(content)) return "binary-file";
16889
- if (content.length > 524288) return "file-too-large";
16890
- files.push({
16891
- path: relative,
16892
- content
16893
- });
16894
- }
16895
- return null;
16896
- });
16897
- const refusal = yield* walk(root, "", 1);
16898
- if (refusal !== null) return { refusal };
16899
- if (agentAssetTotalBytes(files) > 4194304) return { refusal: "asset-too-large" };
16900
- return { files };
16901
- });
16902
- /**
16903
- * Read every skill directory under a user-scope `skills/` root.
16904
- *
16905
- * A directory with no `SKILL.md` is not a skill and is skipped silently —
16906
- * Claude Code would ignore it too, so reporting it would be noise about
16907
- * something that was never a skill in the first place.
16908
- */
16909
- const readSkillDirectory = Effect.fn("skillDirectory.readSkillDirectory")(function* (skillsRoot) {
16910
- const fileSystem = yield* FileSystem.FileSystem;
16911
- const path = yield* Path.Path;
16912
- const entries = yield* fileSystem.readDirectory(skillsRoot).pipe(Effect.orElseSucceed(() => []));
16913
- const skills = [];
16914
- const refused = [];
16915
- for (const entry of [...entries].sort()) {
16916
- if (isIgnoredEntry(entry)) continue;
16917
- const directory = path.join(skillsRoot, entry);
16918
- if ((yield* fileSystem.stat(directory).pipe(Effect.orElseSucceed(() => void 0)))?.type !== "Directory") continue;
16919
- if (!(yield* fileSystem.exists(path.join(directory, "SKILL.md")).pipe(Effect.orElseSucceed(() => false)))) continue;
16920
- const read = yield* readSkillFiles(directory);
16921
- if ("refusal" in read) {
16922
- refused.push({
16923
- name: entry,
16924
- reason: read.refusal
16925
- });
16926
- continue;
16927
- }
16928
- skills.push({
16929
- name: entry,
16930
- files: read.files
16931
- });
16932
- }
16933
- return {
16934
- skills,
16935
- refused
16936
- };
16937
- });
16938
- /**
16939
- * Replace a skill directory with exactly the asset's files.
16940
- *
16941
- * Written to a sibling and then swapped, so a crash mid-write leaves the old
16942
- * skill intact rather than half of the new one. A skill is loaded as a unit and
16943
- * a partially written one is instructions with the middle missing, which an
16944
- * agent will happily follow.
16945
- */
16946
- const writeSkillDirectory = Effect.fn("skillDirectory.writeSkillDirectory")(function* (input) {
16947
- const fileSystem = yield* FileSystem.FileSystem;
16948
- const path = yield* Path.Path;
16949
- const target = path.join(input.skillsRoot, input.name);
16950
- const staging = path.join(input.skillsRoot, `.${input.name}.p4code-staging`);
16951
- yield* fileSystem.makeDirectory(input.skillsRoot, { recursive: true });
16952
- yield* fileSystem.remove(staging, {
16953
- recursive: true,
16954
- force: true
16955
- });
16956
- yield* fileSystem.makeDirectory(staging, { recursive: true });
16957
- for (const file of input.files) {
16958
- const destination = path.join(staging, file.path);
16959
- yield* fileSystem.makeDirectory(path.dirname(destination), { recursive: true });
16960
- yield* fileSystem.writeFileString(destination, file.content);
16961
- }
16962
- yield* fileSystem.remove(target, {
16963
- recursive: true,
16964
- force: true
16965
- });
16966
- yield* fileSystem.rename(staging, target);
16967
- });
16968
- const removeSkillDirectory = Effect.fn("skillDirectory.removeSkillDirectory")(function* (input) {
16969
- const fileSystem = yield* FileSystem.FileSystem;
16970
- const path = yield* Path.Path;
16971
- yield* fileSystem.remove(path.join(input.skillsRoot, input.name), {
16972
- recursive: true,
16973
- force: true
16974
- });
16975
- });
16976
- //#endregion
16977
17318
  //#region src/sync/memoryFile.ts
16978
17319
  /**
16979
17320
  * Reading and writing user-scope memory files.
@@ -17254,12 +17595,20 @@ const PROCEED = { _tag: "proceed" };
17254
17595
  * - A skill without `SKILL.md` would be invisible to discovery: the picker
17255
17596
  * skips directories without one, so the create would look like it silently
17256
17597
  * failed. Refused with the reason instead.
17598
+ * - An agent definition is one markdown file whose name is the asset's own, and
17599
+ * anything else is invisible in the same way — the provider reads `.md` in
17600
+ * that directory and nothing else. The frontmatter check is the writer's,
17601
+ * because it needs a YAML parse this module deliberately does without.
17257
17602
  * - An existing local name is never overwritten. Create means create; editing
17258
17603
  * is a different operation with a different surface.
17259
17604
  */
17260
17605
  function decideCreateLocal(input) {
17261
17606
  if (input.asset.kind === "mcp") return refuse("refused", "MCP registrations are created through mcp.save, not as files.");
17262
17607
  if (input.asset.kind === "skill" && !input.asset.files.some((file) => file.path === "SKILL.md")) return refuse("refused", "A skill needs a SKILL.md at its root; without one, discovery skips the directory.");
17608
+ if (input.asset.kind === "agent") {
17609
+ const [file, ...rest] = input.asset.files;
17610
+ if (!input.asset.name.endsWith(".md") || rest.length > 0 || file?.path !== input.asset.name) return refuse("refused", "An agent definition is a single markdown file named after the asset, for example 'reviewer.md' holding 'reviewer.md'.");
17611
+ }
17263
17612
  if (input.localNames.has(input.asset.name)) return refuse("exists", `An asset named '${input.asset.name}' already exists locally.`);
17264
17613
  return PROCEED;
17265
17614
  }
@@ -17444,7 +17793,7 @@ const EMPTY_REPORT = {
17444
17793
  unavailable: null
17445
17794
  };
17446
17795
  var AssetSync = class extends Context.Service()("@p4code/cli/sync/AssetSync") {};
17447
- const make$59 = Effect.gen(function* () {
17796
+ const make$60 = Effect.gen(function* () {
17448
17797
  const client = yield* HubAssetClient;
17449
17798
  const link = yield* HubLink;
17450
17799
  const settingsStore = yield* ServerSettingsService;
@@ -17502,6 +17851,22 @@ const make$59 = Effect.gen(function* () {
17502
17851
  name
17503
17852
  }).pipe(Effect.ignore)
17504
17853
  },
17854
+ {
17855
+ kind: "agent",
17856
+ resolveRoot: Effect.gen(function* () {
17857
+ return yield* resolveClaudeUserAgentsDir(yield* claudeHome());
17858
+ }),
17859
+ read: (root) => readAgentDefinitions(root),
17860
+ write: ({ root, name, files }) => writeAgentDefinition({
17861
+ agentsRoot: root,
17862
+ name,
17863
+ files
17864
+ }),
17865
+ remove: ({ root, name }) => removeAgentDefinition({
17866
+ agentsRoot: root,
17867
+ name
17868
+ }).pipe(Effect.ignore)
17869
+ },
17505
17870
  {
17506
17871
  kind: "mcp",
17507
17872
  resolveRoot: Effect.succeed(path.join(config.stateDir, "mcp")),
@@ -18141,10 +18506,266 @@ const make$59 = Effect.gen(function* () {
18141
18506
  removeLocal
18142
18507
  };
18143
18508
  });
18144
- const layer$54 = Layer.effect(AssetSync, make$59);
18509
+ const layer$55 = Layer.effect(AssetSync, make$60);
18510
+ //#endregion
18511
+ //#region src/provider/CompressPrompts.ts
18512
+ /**
18513
+ * Caveman-style response compression rulesets, adapted from the caveman
18514
+ * plugin's SKILL.md. Pre-baked per level so injection is a lookup, not a
18515
+ * runtime template. Applies to assistant prose only: code blocks, commits,
18516
+ * PRs, error strings, and safety-critical text stay uncompressed.
18517
+ */
18518
+ const COMPRESS_SHARED_RULES = `Respond terse. All technical substance stays. Only fluff dies.
18519
+
18520
+ ## Persistence
18521
+
18522
+ Active every response. No revert after many turns. No filler drift. Still active if unsure.
18523
+
18524
+ ## Rules
18525
+
18526
+ Drop filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging. Short synonyms (big not extensive, fix not "implement a solution for"). No decorative tables/emoji, no dumping long raw error logs unless asked - quote shortest decisive line. Standard well-known tech acronyms OK (DB/API/HTTP); never invent new abbreviations (cfg/impl/req/res/fn) - tokenizers split them same as the full word: zero tokens saved, reader still decodes. No causal arrows (->) in prose. Technical terms exact. Code blocks unchanged. Errors quoted exact.
18527
+
18528
+ Never drop not/never/no/only/except: flipping the meaning costs more than any token saved. Numbers and units exact.
18529
+
18530
+ Tool calls: fire direct. No plan, no progress note between calls, never announce the next call. If the harness asks for a short lead-in before tool calls, write that one line and nothing more. Otherwise text before a call only to clarify, warn about a security or irreversible action, or resolve ambiguity.
18531
+
18532
+ Preserve the user's dominant language: reply in the language the user writes, whatever language the examples or the surrounding context use. Compress the style, not the language. Every emitted line in that language, lead-in lines included, not just the final reply. Always keep technical terms, code, API names, CLI commands, commit-type keywords (feat/fix/...), and exact error strings verbatim unless the user explicitly asks for translation.
18533
+
18534
+ Dropping articles applies to article languages only. Where small markers carry case or role (particles, postpositions), keep them: grammar, not filler. Compress politeness and filler instead.
18535
+
18536
+ No self-reference. Never name or announce the style. No compressed answer plus normal recap - compressed output only. Exception: user explicitly asks what the mode is.
18537
+
18538
+ ## Auto-Clarity
18539
+
18540
+ Drop compression and write normally when:
18541
+ - Security warnings
18542
+ - Irreversible action confirmations
18543
+ - Multi-step sequences where fragment order or omitted conjunctions risk misread
18544
+ - Compression itself creates technical ambiguity (e.g. "migrate table drop column backup first" - order unclear without articles and conjunctions)
18545
+ - User asks to clarify or repeats a question
18546
+
18547
+ Resume compression after the clear part is done. The example below shows the format only: write the warning in the session's language, not the example's.
18548
+
18549
+ Example - destructive operation:
18550
+ > **Warning:** This will permanently delete all rows in the \`users\` table and cannot be undone.
18551
+ > \`\`\`sql
18552
+ > DROP TABLE users;
18553
+ > \`\`\`
18554
+ > Compression resumes. Verify a backup exists first.
18555
+
18556
+ ## Boundaries
18557
+
18558
+ Anything persisted outside the chat: write normal, uncompressed prose. Covers code, code comments, commit messages, PR and issue text, docs, memory files, and messages to third parties.`;
18559
+ const COMPRESS_RULESETS = {
18560
+ lite: `## Response compression: lite
18561
+
18562
+ ${COMPRESS_SHARED_RULES}
18563
+
18564
+ ## Intensity: lite
18565
+
18566
+ No filler or hedging. Keep articles and full sentences. Professional but tight.
18567
+
18568
+ Example - "Why does my React component re-render?"
18569
+ "Your component re-renders because you create a new object reference each render. Wrap it in \`useMemo\`."
18570
+
18571
+ Example - "Explain database connection pooling."
18572
+ "Connection pooling reuses open connections instead of creating a new one per request. Avoids repeated handshake overhead."`,
18573
+ full: `## Response compression: full
18574
+
18575
+ ${COMPRESS_SHARED_RULES}
18576
+
18577
+ ## Intensity: full
18578
+
18579
+ Drop articles (a/an/the), fragments OK, short synonyms. Pattern: [thing] [action] [reason]. [next step].
18580
+
18581
+ Not: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..."
18582
+ Yes: "Bug in auth middleware. Token expiry check use \`<\` not \`<=\`. Fix:"
18583
+
18584
+ Example - "Why does my React component re-render?"
18585
+ "New object ref each render. Inline object prop = new ref = re-render. Wrap in \`useMemo\`."
18586
+
18587
+ Example - "Explain database connection pooling."
18588
+ "Pool reuses open DB connections. No new connection per request. Skips handshake overhead."`,
18589
+ ultra: `## Response compression: ultra
18590
+
18591
+ ${COMPRESS_SHARED_RULES}
18592
+
18593
+ ## Intensity: ultra
18594
+
18595
+ Drop articles, fragments OK, short synonyms. Also strip conjunctions when cause-then-effect stays unambiguous. One word when one word is enough. State each fact once. Still no invented abbreviations (cfg/impl/req/res/fn/auth) and still no arrows (X -> Y): both measure as zero token saving and cost decode clarity. Code symbols, function names, API names, error strings: never touch.
18596
+
18597
+ Example - "Why does my React component re-render?"
18598
+ "Inline object prop, new ref, re-render. \`useMemo\`."
18599
+
18600
+ Example - "Explain database connection pooling."
18601
+ "Pool reuses open DB connections. No per-request handshake."`
18602
+ };
18603
+ const COMPRESS_TURN_REMINDERS = {
18604
+ lite: "[response style: compressed lite - no filler/hedging, full sentences; keep negations and numbers exact; code/commits/security text normal]",
18605
+ full: "[response style: compressed full - terse fragments, drop articles/filler; keep negations and numbers exact; code/commits/security text normal]",
18606
+ ultra: "[response style: compressed ultra - maximum terseness, one word when enough; keep negations and numbers exact; code/commits/security text normal]"
18607
+ };
18608
+ function compressRulesetFor(mode) {
18609
+ return mode === "off" ? void 0 : COMPRESS_RULESETS[mode];
18610
+ }
18611
+ function compressTurnReminderFor(mode) {
18612
+ return mode === "off" ? void 0 : COMPRESS_TURN_REMINDERS[mode];
18613
+ }
18614
+ //#endregion
18615
+ //#region src/sync/agentPresets.ts
18616
+ /**
18617
+ * The row ceiling both presets impose.
18618
+ *
18619
+ * A cap rather than "be brief": an uncapped list is how a subagent turns a
18620
+ * bounded answer back into a page, and a number is the only form of that
18621
+ * instruction a model cannot negotiate with.
18622
+ */
18623
+ const PRESET_MAX_RESULT_ROWS = 20;
18624
+ /**
18625
+ * Named `p4-*` on purpose.
18626
+ *
18627
+ * The directory is shared with whatever the user has written by hand, and
18628
+ * installing a preset must never quietly replace their own `reviewer`. The
18629
+ * prefix also makes provenance obvious in the settings list, where every kind
18630
+ * of agent definition appears in one column.
18631
+ */
18632
+ const PRESET_NAME_PREFIX = "p4-";
18633
+ /**
18634
+ * What every preset says about what it is handed.
18635
+ *
18636
+ * Shared because it is the same contract whatever the subagent does: no
18637
+ * inherited history, exact paths, and a refusal instead of a guess. The last
18638
+ * clause is the one that earns its place — a subagent that invents the missing
18639
+ * half of an under-specified prompt returns something confident and wrong,
18640
+ * which costs more than the turn that asked for it.
18641
+ */
18642
+ const INPUT_CONTRACT = `## What you are given
18643
+
18644
+ You inherit no session history. The prompt is the whole of what you know: one
18645
+ request, plus whatever exact paths the orchestrator has already established.
18646
+ Treat those paths as authoritative and start from them rather than searching for
18647
+ them again.
18648
+
18649
+ What the prompt does not say, you do not assume. If it names no request, or
18650
+ names a path that is not there, answer with the BLOCKED line below. Guessing at
18651
+ the missing half of a request produces an answer that reads as confident and is
18652
+ not checkable, which is worse than no answer.`;
18653
+ /**
18654
+ * What every preset says about the answer.
18655
+ *
18656
+ * The two sentinels matter as much as the table. Without `NONE` a clean run
18657
+ * comes back as a paragraph explaining that everything looked fine; without
18658
+ * `BLOCKED` an under-specified request comes back as an apology. Both are the
18659
+ * narrative this contract exists to remove, and both are cheap to name.
18660
+ */
18661
+ const outputContract = (row, extra) => [
18662
+ "## What you return",
18663
+ "",
18664
+ "Your final message is the result. It is the table and nothing else: no",
18665
+ "preamble, no restatement of the request, no summary underneath it.",
18666
+ "",
18667
+ `One row per finding, at most ${PRESET_MAX_RESULT_ROWS}, most important first:`,
18668
+ "",
18669
+ ` ${row}`,
18670
+ "",
18671
+ "Nothing to report: the single word NONE.",
18672
+ "Request insufficient: BLOCKED - <what is missing>, on one line.",
18673
+ "",
18674
+ "Paths are repository-relative and carry a line number. A row without one is",
18675
+ "a row you have not verified: open the file and get it.",
18676
+ ...extra
18677
+ ].join("\n");
18678
+ /**
18679
+ * Assemble one definition.
18680
+ *
18681
+ * The compression ruleset goes last so the contract above it is read first.
18682
+ * Both are instructions to the same model and neither overrides the other:
18683
+ * the contract fixes the shape, the ruleset fixes the prose inside it.
18684
+ */
18685
+ const buildPreset = (input) => {
18686
+ const name = `${PRESET_NAME_PREFIX}${input.name}`;
18687
+ const content = [
18688
+ "---",
18689
+ `name: ${name}`,
18690
+ `description: ${JSON.stringify(input.description)}`,
18691
+ `tools: ${input.tools}`,
18692
+ "---",
18693
+ "",
18694
+ input.body.trim(),
18695
+ "",
18696
+ compressRulesetFor("full") ?? "",
18697
+ ""
18698
+ ].join("\n");
18699
+ const fileName = `${name}.md`;
18700
+ return {
18701
+ name: fileName,
18702
+ summary: input.summary,
18703
+ files: [{
18704
+ path: fileName,
18705
+ content
18706
+ }]
18707
+ };
18708
+ };
18709
+ /** Every preset, in the order `p4c agents presets` lists them. */
18710
+ const AGENT_PRESETS = [buildPreset({
18711
+ name: "investigator",
18712
+ description: "Find where something lives in the codebase. Use when the answer is a set of locations rather than a change. Returns a fixed table of path:line rows and nothing else.",
18713
+ tools: "Read, Grep, Glob, Bash",
18714
+ summary: "Locates code and answers with a path:line table.",
18715
+ body: `You locate code. You do not change it, and you say no more about any location
18716
+ than the one clause that explains why it is in the list.
18717
+
18718
+ ${INPUT_CONTRACT}
18719
+
18720
+ ${outputContract("path:line - symbol - one clause on why it matters", [
18721
+ "",
18722
+ "Symbol is the function, type or constant the line belongs to, not a paraphrase",
18723
+ "of the line. Where there is genuinely no symbol, name the block: a route table,",
18724
+ "a config literal."
18725
+ ])}`
18726
+ }), buildPreset({
18727
+ name: "reviewer",
18728
+ description: "Review named files or a diff for defects. Use when the answer is a list of findings rather than a change. Returns a fixed table of path:line findings and nothing else.",
18729
+ tools: "Read, Grep, Glob, Bash",
18730
+ summary: "Reviews named files and answers with a severity-tagged finding table.",
18731
+ body: `You review code you did not write. You do not change it, and you do not report
18732
+ what it does well - the orchestrator asked what is wrong, and a list padded with
18733
+ approval is a list that has to be re-read to find the three rows that matter.
18734
+
18735
+ ${INPUT_CONTRACT}
18736
+
18737
+ ${outputContract("path:line - severity - what is wrong, and what it breaks", [
18738
+ "",
18739
+ "Severity is one of three words:",
18740
+ "",
18741
+ " bug - wrong behaviour reachable in ordinary use. Name the input that reaches it.",
18742
+ " risk - correct today, and depends on something the code does not state.",
18743
+ " nit - style or clarity. Include only when the file is otherwise clean.",
18744
+ "",
18745
+ "A finding you cannot reach with a concrete input is a risk, not a bug. Saying",
18746
+ "so is what keeps the bug rows worth reading."
18747
+ ])}`
18748
+ })];
18749
+ /**
18750
+ * Look one up by the name someone typed.
18751
+ *
18752
+ * Both spellings are accepted - `investigator` and `p4-investigator.md` - because
18753
+ * the listing shows the filename and the sentence people write is the bare word,
18754
+ * and refusing one of the two would be refusing whichever the person happened to
18755
+ * read most recently.
18756
+ */
18757
+ const findAgentPreset = (requested) => {
18758
+ const wanted = requested.trim().toLowerCase();
18759
+ if (wanted.length === 0) return void 0;
18760
+ return AGENT_PRESETS.find((preset) => {
18761
+ const fileName = preset.name.toLowerCase();
18762
+ const bare = fileName.slice(3, -3);
18763
+ return wanted === fileName || wanted === bare || wanted === `${PRESET_NAME_PREFIX}${bare}`;
18764
+ });
18765
+ };
18145
18766
  //#endregion
18146
18767
  //#region src/cli/assets.ts
18147
- const SyncServicesLive = layer$54.pipe(Layer.provideMerge(layer$55), Layer.provideMerge(layer$56), Layer.provideMerge(layer$60.pipe(Layer.provide(layer$67))), Layer.provideMerge(layer$67), Layer.provideMerge(FetchHttpClient.layer));
18768
+ const SyncServicesLive = layer$55.pipe(Layer.provideMerge(layer$56), Layer.provideMerge(layer$57), Layer.provideMerge(layer$61.pipe(Layer.provide(layer$68))), Layer.provideMerge(layer$68), Layer.provideMerge(FetchHttpClient.layer));
18148
18769
  const runWithSync = (flags, run) => Effect.gen(function* () {
18149
18770
  const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
18150
18771
  return yield* Effect.gen(function* () {
@@ -18152,7 +18773,7 @@ const runWithSync = (flags, run) => Effect.gen(function* () {
18152
18773
  sync: yield* AssetSync,
18153
18774
  link: yield* HubLink
18154
18775
  });
18155
- }).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$70(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
18776
+ }).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$71(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
18156
18777
  });
18157
18778
  const STATE_LABEL = {
18158
18779
  synced: "synced",
@@ -18186,6 +18807,14 @@ const MCP_WORDS = {
18186
18807
  rootLabel: "MCP",
18187
18808
  nameDescription: "Registration filename, for example linear.json."
18188
18809
  };
18810
+ const AGENT_WORDS = {
18811
+ kind: "agent",
18812
+ command: "agents",
18813
+ singular: "agent definition",
18814
+ plural: "agent definitions",
18815
+ rootLabel: "Agents",
18816
+ nameDescription: "Definition filename, for example p4-reviewer.md."
18817
+ };
18189
18818
  const rootFor = (status, kind) => status.roots.find((root) => root.kind === kind)?.path ?? null;
18190
18819
  /**
18191
18820
  * Exported for tests. These two are the only place a wrong report becomes a
@@ -18248,7 +18877,8 @@ const formatReport = (report) => {
18248
18877
  const ALL_WORDS = [
18249
18878
  SKILL_WORDS,
18250
18879
  MEMORY_WORDS,
18251
- MCP_WORDS
18880
+ MCP_WORDS,
18881
+ AGENT_WORDS
18252
18882
  ];
18253
18883
  /**
18254
18884
  * The whole picture, in one page.
@@ -18380,6 +19010,67 @@ const makeAssetCommands = (words) => {
18380
19010
  const skillCommands = makeAssetCommands(SKILL_WORDS);
18381
19011
  const memoryCommands = makeAssetCommands(MEMORY_WORDS);
18382
19012
  const mcpCommands = makeAssetCommands(MCP_WORDS);
19013
+ const agentCommands = makeAssetCommands(AGENT_WORDS);
19014
+ /**
19015
+ * What one install attempt did, in a sentence.
19016
+ *
19017
+ * Exported for tests: `exists` in particular must never read as success. The
19018
+ * whole point of refusing to overwrite is that someone who has edited a preset
19019
+ * keeps their edits, and a line saying "installed" would tell them the opposite.
19020
+ */
19021
+ const formatPresetInstall = (input) => {
19022
+ if (input.outcome === "created") return `Installed ${input.name}${input.path === null ? "" : ` at ${input.path}`}`;
19023
+ if (input.outcome === "exists") return `Skipped ${input.name}: it already exists here, and installing would overwrite your copy.`;
19024
+ return `Could not install ${input.name}: ${input.detail ?? input.outcome}`;
19025
+ };
19026
+ /**
19027
+ * `p4c agents presets` — what is on offer, before anything is written.
19028
+ *
19029
+ * Its own command rather than a flag on `install`, because reading the list is
19030
+ * what people do first and a listing that requires naming an install target is
19031
+ * not a listing.
19032
+ */
19033
+ const presetsCommand = Command.make("presets", { ...authLocationFlags }).pipe(Command.withDescription("List the agent definitions p4code ships, without installing any."), Command.withHandler(() => Console.log([
19034
+ ...AGENT_PRESETS.map((preset) => ` ${preset.name.padEnd(28)} ${preset.summary}`),
19035
+ "",
19036
+ "Install one with: p4c agents install <name>"
19037
+ ].join("\n"))));
19038
+ /**
19039
+ * Install a shipped definition onto this machine, and only this machine.
19040
+ *
19041
+ * `createLocal` rather than a hub write, so an installed preset starts out
19042
+ * exactly like one written by hand: local, unmanaged, and shared only when
19043
+ * something explicitly publishes it. It also means this works with no hub
19044
+ * configured, which is when a fresh machine most wants it.
19045
+ */
19046
+ const presetInstallCommand = Command.make("install", {
19047
+ ...authLocationFlags,
19048
+ name: Argument.string("name").pipe(Argument.withDescription("Preset to install, for example investigator."), Argument.optional),
19049
+ all: Flag.boolean("all").pipe(Flag.withDescription("Install every preset p4code ships."))
19050
+ }).pipe(Command.withDescription("Install a p4code agent definition into this machine's agents directory."), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => Effect.gen(function* () {
19051
+ const requested = Option.getOrUndefined(flags.name);
19052
+ if (!flags.all && requested === void 0) return yield* Console.error("Name a preset, or pass --all to install every one.\nSee them with: p4c agents presets");
19053
+ const chosen = flags.all ? AGENT_PRESETS : (() => {
19054
+ const found = findAgentPreset(requested ?? "");
19055
+ return found === void 0 ? [] : [found];
19056
+ })();
19057
+ if (chosen.length === 0) return yield* Console.error(`No preset named ${requested ?? ""}. See them with: p4c agents presets`);
19058
+ const lines = [];
19059
+ for (const preset of chosen) {
19060
+ const result = yield* sync.createLocal({
19061
+ kind: AGENT_WORDS.kind,
19062
+ name: preset.name,
19063
+ files: preset.files
19064
+ });
19065
+ lines.push(formatPresetInstall({
19066
+ name: preset.name,
19067
+ outcome: result.outcome,
19068
+ path: result.path,
19069
+ detail: result.detail
19070
+ }));
19071
+ }
19072
+ return yield* Console.log(lines.join("\n"));
19073
+ }))));
18383
19074
  const shareAllCommand = Command.make("share-all", { ...authLocationFlags }).pipe(Command.withDescription("Share everything, including new items, automatically. The default."), Command.withHandler((flags) => runWithSync(flags, ({ sync, link }) => Effect.gen(function* () {
18384
19075
  yield* link.setShareMode("all");
18385
19076
  yield* Console.log(formatStatus(yield* sync.status));
@@ -18432,6 +19123,23 @@ const mcpCommand = Command.make("mcp", {}).pipe(Command.withDescription("Share M
18432
19123
  mcpCommands.showCommand,
18433
19124
  mcpCommands.removeCommand
18434
19125
  ]));
19126
+ /**
19127
+ * `p4c agents` — the subagent definitions this machine runs.
19128
+ *
19129
+ * The same six subcommands every kind gets, plus the two that are particular to
19130
+ * this one: p4code ships definitions, and a definition is the only place a
19131
+ * subagent's input and output contract can be stated.
19132
+ */
19133
+ const agentsCommand = Command.make("agents", {}).pipe(Command.withDescription("Share subagent definitions across your machines, and install p4code's."), Command.withSubcommands([
19134
+ agentCommands.statusCommand,
19135
+ agentCommands.syncCommand,
19136
+ agentCommands.publishCommand,
19137
+ agentCommands.unpublishCommand,
19138
+ agentCommands.showCommand,
19139
+ agentCommands.removeCommand,
19140
+ presetsCommand,
19141
+ presetInstallCommand
19142
+ ]));
18435
19143
  /** Exported for the hub command, which owns `connect`/`disconnect`. */
18436
19144
  const hubLinkCommands = [hubConnectCommand, hubDisconnectCommand];
18437
19145
  //#endregion
@@ -23417,12 +24125,12 @@ const runProcessCore = Effect.fn("processRunner.runProcessCore")(function* (spaw
23417
24125
  stderrTruncated: stderr.truncated
23418
24126
  };
23419
24127
  });
23420
- const make$58 = Effect.fn("ProcessRunner.make")(function* () {
24128
+ const make$59 = Effect.fn("ProcessRunner.make")(function* () {
23421
24129
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
23422
24130
  const run = (input) => finalizeRunProcess(runProcessCore(spawner, input), input);
23423
24131
  return ProcessRunner.of({ run });
23424
24132
  });
23425
- const layer$53 = Layer.effect(ProcessRunner, make$58());
24133
+ const layer$54 = Layer.effect(ProcessRunner, make$59());
23426
24134
  //#endregion
23427
24135
  //#region src/project/RepositoryIdentityResolver.ts
23428
24136
  const DEFAULT_REPOSITORY_IDENTITY_CACHE_CAPACITY = 512;
@@ -23513,7 +24221,7 @@ const resolveRepositoryIdentityFromCacheKey = Effect.fn("RepositoryIdentityResol
23513
24221
  rootPath: cacheKey
23514
24222
  }) : null;
23515
24223
  });
23516
- const make$57 = Effect.fn("RepositoryIdentityResolver.make")(function* (options = {}) {
24224
+ const make$58 = Effect.fn("RepositoryIdentityResolver.make")(function* (options = {}) {
23517
24225
  const processRunner = yield* ProcessRunner;
23518
24226
  const repositoryIdentityCache = yield* Cache.makeWith((cacheKey) => resolveRepositoryIdentityFromCacheKey(cacheKey).pipe(Effect.provideService(ProcessRunner, processRunner)), {
23519
24227
  capacity: options.cacheCapacity ?? DEFAULT_REPOSITORY_IDENTITY_CACHE_CAPACITY,
@@ -23528,7 +24236,7 @@ const make$57 = Effect.fn("RepositoryIdentityResolver.make")(function* (options
23528
24236
  });
23529
24237
  return RepositoryIdentityResolver.of({ resolve });
23530
24238
  });
23531
- const layer$52 = Layer.effect(RepositoryIdentityResolver, make$57()).pipe(Layer.provide(layer$53));
24239
+ const layer$53 = Layer.effect(RepositoryIdentityResolver, make$58()).pipe(Layer.provide(layer$54));
23532
24240
  //#endregion
23533
24241
  //#region src/orchestration/Layers/ProjectionSnapshotQuery.ts
23534
24242
  const decodeReadModel = Schema$1.decodeUnknownEffect(OrchestrationReadModel);
@@ -25348,7 +26056,7 @@ function mergeWithDefaultKeybindings(custom) {
25348
26056
  * Keybindings - Service tag for keybinding configuration operations.
25349
26057
  */
25350
26058
  var Keybindings = class extends Context.Service()("@p4code/cli/keybindings") {};
25351
- const make$56 = Effect.gen(function* () {
26059
+ const make$57 = Effect.gen(function* () {
25352
26060
  const { keybindingsConfigPath } = yield* ServerConfig$1;
25353
26061
  const fs = yield* FileSystem.FileSystem;
25354
26062
  const path = yield* Path.Path;
@@ -25609,7 +26317,7 @@ const make$56 = Effect.gen(function* () {
25609
26317
  }))
25610
26318
  };
25611
26319
  });
25612
- const layer$51 = Layer.effect(Keybindings, make$56);
26320
+ const layer$52 = Layer.effect(Keybindings, make$57);
25613
26321
  //#endregion
25614
26322
  //#region src/process/externalLauncher.ts
25615
26323
  /**
@@ -25836,7 +26544,7 @@ const launchEditorProcess = Effect.fn("externalLauncher.launchEditorProcess")(fu
25836
26544
  cause
25837
26545
  }));
25838
26546
  });
25839
- const make$55 = Effect.gen(function* () {
26547
+ const make$56 = Effect.gen(function* () {
25840
26548
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
25841
26549
  const fileSystem = yield* FileSystem.FileSystem;
25842
26550
  const path = yield* Path.Path;
@@ -25847,7 +26555,7 @@ const make$55 = Effect.gen(function* () {
25847
26555
  launchEditor: (input) => provideCommandResolutionServices(Effect.flatMap(resolveEditorLaunch(input), (launch) => launchEditorProcess(launch).pipe(Effect.provideService(ChildProcessSpawner$1.ChildProcessSpawner, spawner))))
25848
26556
  });
25849
26557
  });
25850
- const layer$50 = Layer.effect(ExternalLauncher, make$55);
26558
+ const layer$51 = Layer.effect(ExternalLauncher, make$56);
25851
26559
  //#endregion
25852
26560
  //#region src/orchestration/Services/OrchestrationReactor.ts
25853
26561
  /**
@@ -25865,7 +26573,7 @@ var OrchestrationReactor = class extends Context.Service()("@p4code/cli/orchestr
25865
26573
  //#endregion
25866
26574
  //#region src/serverLifecycleEvents.ts
25867
26575
  var ServerLifecycleEvents = class extends Context.Service()("@p4code/cli/serverLifecycleEvents") {};
25868
- const make$54 = Effect.gen(function* () {
26576
+ const make$55 = Effect.gen(function* () {
25869
26577
  const pubsub = yield* PubSub.unbounded();
25870
26578
  const state = yield* Ref.make({
25871
26579
  sequence: 0,
@@ -25889,7 +26597,7 @@ const make$54 = Effect.gen(function* () {
25889
26597
  }
25890
26598
  };
25891
26599
  });
25892
- const layer$49 = Layer.effect(ServerLifecycleEvents, make$54);
26600
+ const layer$50 = Layer.effect(ServerLifecycleEvents, make$55);
25893
26601
  //#endregion
25894
26602
  //#region src/telemetry/Identify.ts
25895
26603
  const CodexAuthJsonSchema = Schema$1.Struct({ tokens: Schema$1.Struct({ account_id: Schema$1.String }) });
@@ -26062,7 +26770,7 @@ var AnalyticsService = class AnalyticsService extends Context.Service()("@p4code
26062
26770
  /** No-op layer for callers that intentionally disable telemetry. */
26063
26771
  static layerTest = Layer.succeed(AnalyticsService, inert);
26064
26772
  };
26065
- const make$53 = Effect.gen(function* () {
26773
+ const make$54 = Effect.gen(function* () {
26066
26774
  const telemetryConfig = yield* TelemetryEnvConfig;
26067
26775
  const posthogKey = telemetryConfig.posthogKey.trim();
26068
26776
  if (!telemetryConfig.enabled || posthogKey === "") return inert;
@@ -26132,7 +26840,7 @@ const make$53 = Effect.gen(function* () {
26132
26840
  flush
26133
26841
  });
26134
26842
  });
26135
- const layer$48 = Layer.effect(AnalyticsService, make$53);
26843
+ const layer$49 = Layer.effect(AnalyticsService, make$54);
26136
26844
  AnalyticsService.layerTest;
26137
26845
  //#endregion
26138
26846
  //#region src/service/pinnedRuntime.ts
@@ -26479,7 +27187,7 @@ var BootServiceInstallError = class extends Schema$1.TaggedErrorClass()("BootSer
26479
27187
  }
26480
27188
  };
26481
27189
  var BootService = class extends Context.Service()("@p4code/cli/service/bootService") {};
26482
- const make$52 = Effect.fn("cloud.boot_service.make")(function* (input) {
27190
+ const make$53 = Effect.fn("cloud.boot_service.make")(function* (input) {
26483
27191
  const hostExecPath = yield* HostProcessExecutablePath;
26484
27192
  const hostArguments = yield* HostProcessArguments;
26485
27193
  const host = input.host ?? {
@@ -26698,7 +27406,7 @@ const make$52 = Effect.fn("cloud.boot_service.make")(function* (input) {
26698
27406
  logPath
26699
27407
  });
26700
27408
  });
26701
- const layer$47 = (input) => Layer.effect(BootService, make$52(input));
27409
+ const layer$48 = (input) => Layer.effect(BootService, make$53(input));
26702
27410
  //#endregion
26703
27411
  //#region src/service/selfUpdate.ts
26704
27412
  /**
@@ -26773,7 +27481,7 @@ const resolveServerSelfUpdateCapability = Effect.fn("cloud.server_self_update.re
26773
27481
  return null;
26774
27482
  });
26775
27483
  var ServerSelfUpdate = class extends Context.Service()("@p4code/cli/service/selfUpdate/ServerSelfUpdate") {};
26776
- const make$51 = Effect.fn("cloud.server_self_update.make")(function* (options) {
27484
+ const make$52 = Effect.fn("cloud.server_self_update.make")(function* (options) {
26777
27485
  const serverConfig = yield* ServerConfig$1;
26778
27486
  const fs = yield* FileSystem.FileSystem;
26779
27487
  const path = yield* Path.Path;
@@ -26923,7 +27631,7 @@ const make$51 = Effect.fn("cloud.server_self_update.make")(function* (options) {
26923
27631
  });
26924
27632
  return ServerSelfUpdate.of({ update });
26925
27633
  });
26926
- const layer$46 = Layer.effect(ServerSelfUpdate, make$51()).pipe(Layer.provide(layer$53));
27634
+ const layer$47 = Layer.effect(ServerSelfUpdate, make$52()).pipe(Layer.provide(layer$54));
26927
27635
  //#endregion
26928
27636
  //#region src/environment/ServerEnvironmentLabel.ts
26929
27637
  const ServerEnvironmentLabelCommandProbe = Schema$1.Literals(["macos-computer-name", "linux-pretty-hostname"]);
@@ -27055,7 +27763,7 @@ function platformArch(architecture) {
27055
27763
  default: return "other";
27056
27764
  }
27057
27765
  }
27058
- const make$50 = Effect.gen(function* () {
27766
+ const make$51 = Effect.gen(function* () {
27059
27767
  const fileSystem = yield* FileSystem.FileSystem;
27060
27768
  const path = yield* Path.Path;
27061
27769
  const serverConfig = yield* ServerConfig$1;
@@ -27118,7 +27826,7 @@ const make$50 = Effect.gen(function* () {
27118
27826
  * state. It intentionally has no fallback Layer.succeed value: callers must
27119
27827
  * provide the external platform services and a ServerConfig.
27120
27828
  */
27121
- const layer$45 = Layer.effect(ServerEnvironment, make$50).pipe(Layer.provide(layer$53));
27829
+ const layer$46 = Layer.effect(ServerEnvironment, make$51).pipe(Layer.provide(layer$54));
27122
27830
  //#endregion
27123
27831
  //#region src/provider/Services/ProviderSessionReaper.ts
27124
27832
  var ProviderSessionReaper = class extends Context.Service()("@p4code/cli/provider/Services/ProviderSessionReaper") {};
@@ -28195,7 +28903,7 @@ const maybeOpenBrowser = (target) => Effect.gen(function* () {
28195
28903
  yield* (yield* ExternalLauncher).launchBrowser(target).pipe(Effect.catch(() => Effect.logInfo("browser auto-open unavailable", { hint: `Open ${target} in your browser.` })));
28196
28904
  });
28197
28905
  const runStartupPhase = (phase, effect) => effect.pipe(Effect.annotateSpans({ "startup.phase": phase }), Effect.withSpan(`server.startup.${phase}`));
28198
- const make$49 = Effect.gen(function* () {
28906
+ const make$50 = Effect.gen(function* () {
28199
28907
  const serverConfig = yield* ServerConfig$1;
28200
28908
  const keybindings = yield* Keybindings;
28201
28909
  const orchestrationReactor = yield* OrchestrationReactor;
@@ -28336,7 +29044,7 @@ const make$49 = Effect.gen(function* () {
28336
29044
  enqueueCommand: commandGate.enqueueCommand
28337
29045
  };
28338
29046
  });
28339
- const layer$44 = Layer.effect(ServerRuntimeStartup, make$49);
29047
+ const layer$45 = Layer.effect(ServerRuntimeStartup, make$50);
28340
29048
  //#endregion
28341
29049
  //#region src/serverRuntimeState.ts
28342
29050
  const PersistedServerRuntimeState = Schema$1.Struct({
@@ -28484,7 +29192,7 @@ function expandHomePath$2(input, path) {
28484
29192
  if (input.startsWith("~/") || input.startsWith("~\\")) return path.join(NodeOS.homedir(), input.slice(2));
28485
29193
  return input;
28486
29194
  }
28487
- const make$48 = Effect.gen(function* () {
29195
+ const make$49 = Effect.gen(function* () {
28488
29196
  const fileSystem = yield* FileSystem.FileSystem;
28489
29197
  const path = yield* Path.Path;
28490
29198
  const statWorkspaceRoot = Effect.fn("WorkspacePaths.statWorkspaceRoot")(function* (workspaceRoot, normalizedWorkspaceRoot, phase) {
@@ -28541,7 +29249,7 @@ const make$48 = Effect.gen(function* () {
28541
29249
  resolveRelativePathWithinRoot
28542
29250
  });
28543
29251
  });
28544
- const layer$43 = Layer.effect(WorkspacePaths, make$48);
29252
+ const layer$44 = Layer.effect(WorkspacePaths, make$49);
28545
29253
  //#endregion
28546
29254
  //#region src/cli/project.ts
28547
29255
  const isEnvironmentHttpCommonError = Schema$1.is(EnvironmentHttpCommonError);
@@ -28647,7 +29355,7 @@ const projectCommandUuid = Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.
28647
29355
  operation: "generateProjectCommandId",
28648
29356
  cause
28649
29357
  })));
28650
- const ProjectCliRuntimeLive = Layer.mergeAll(layer$43, OrchestrationLayerLive.pipe(Layer.provideMerge(layer$52), Layer.provideMerge(layerConfig)));
29358
+ const ProjectCliRuntimeLive = Layer.mergeAll(layer$44, OrchestrationLayerLive.pipe(Layer.provideMerge(layer$53), Layer.provideMerge(layerConfig)));
28651
29359
  const PROJECT_CLI_LIVE_SERVER_TIMEOUT = Duration.seconds(1);
28652
29360
  const withProjectCliSessionToken = (environmentAuth, run) => Effect.acquireUseRelease(environmentAuth.issueSession({
28653
29361
  scopes: AuthAdministrativeScopes,
@@ -28738,7 +29446,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
28738
29446
  });
28739
29447
  yield* Console.log(output);
28740
29448
  }));
28741
- const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$70(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)));
29449
+ const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$71(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)));
28742
29450
  return yield* Effect.gen(function* () {
28743
29451
  const snapshot = yield* getOfflineSnapshot();
28744
29452
  const orchestrationEngine = yield* OrchestrationEngineService;
@@ -28749,7 +29457,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
28749
29457
  });
28750
29458
  yield* Console.log(output);
28751
29459
  }).pipe(Effect.provide(offlineRuntimeLayer));
28752
- }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$43).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$70(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
29460
+ }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$44).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$71(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
28753
29461
  });
28754
29462
  const projectAddCommand = Command.make("add", {
28755
29463
  ...projectLocationFlags,
@@ -29356,7 +30064,7 @@ const logP4ProjectFileLoadError = (error) => Effect.logWarning(error).pipe(Effec
29356
30064
  filePath: error.filePath,
29357
30065
  errorTag: error._tag
29358
30066
  }));
29359
- const make$47 = Effect.gen(function* () {
30067
+ const make$48 = Effect.gen(function* () {
29360
30068
  const fileSystem = yield* FileSystem.FileSystem;
29361
30069
  const path = yield* Path.Path;
29362
30070
  const load = Effect.fn("P4ProjectFileLoader.load")(function* (workspaceRoot) {
@@ -29377,7 +30085,7 @@ const make$47 = Effect.gen(function* () {
29377
30085
  });
29378
30086
  return P4ProjectFileLoader.of({ load });
29379
30087
  });
29380
- const layer$42 = Layer.effect(P4ProjectFileLoader, make$47);
30088
+ const layer$43 = Layer.effect(P4ProjectFileLoader, make$48);
29381
30089
  //#endregion
29382
30090
  //#region src/project/ProjectFaviconResolver.ts
29383
30091
  /**
@@ -29448,7 +30156,7 @@ function extractIconHref(source) {
29448
30156
  return null;
29449
30157
  }
29450
30158
  const optionOnNotFound$1 = (effect) => effect.pipe(Effect.map(Option.some), Effect.catchTags({ PlatformError: (error) => error.reason._tag === "NotFound" ? Effect.succeed(Option.none()) : Effect.fail(error) }));
29451
- const make$46 = Effect.gen(function* () {
30159
+ const make$47 = Effect.gen(function* () {
29452
30160
  const fileSystem = yield* FileSystem.FileSystem;
29453
30161
  const path = yield* Path.Path;
29454
30162
  const workspacePaths = yield* WorkspacePaths;
@@ -29517,7 +30225,7 @@ const make$46 = Effect.gen(function* () {
29517
30225
  });
29518
30226
  return ProjectFaviconResolver.of({ resolvePath });
29519
30227
  });
29520
- const layer$41 = Layer.effect(ProjectFaviconResolver, make$46);
30228
+ const layer$42 = Layer.effect(ProjectFaviconResolver, make$47);
29521
30229
  //#endregion
29522
30230
  //#region src/assets/AssetAccess.ts
29523
30231
  const ASSET_ROUTE_PREFIX = "/api/assets";
@@ -29770,10 +30478,10 @@ const resolveAsset = Effect.fn("AssetAccess.resolveAsset")(function* (token, rel
29770
30478
  //#endregion
29771
30479
  //#region src/observability/BrowserTraceCollector.ts
29772
30480
  var BrowserTraceCollector = class extends Context.Service()("@p4code/cli/observability/BrowserTraceCollector") {};
29773
- const make$45 = (sink) => BrowserTraceCollector.of({ record: (records) => Effect.sync(() => {
30481
+ const make$46 = (sink) => BrowserTraceCollector.of({ record: (records) => Effect.sync(() => {
29774
30482
  for (const record of records) sink.push(record);
29775
30483
  }) });
29776
- const layer$40 = (sink) => Layer.succeed(BrowserTraceCollector, make$45(sink));
30484
+ const layer$41 = (sink) => Layer.succeed(BrowserTraceCollector, make$46(sink));
29777
30485
  //#endregion
29778
30486
  //#region src/auth/http.ts
29779
30487
  const CREDENTIAL_RESPONSE_HEADERS = {
@@ -30736,7 +31444,7 @@ const NESTED_PAYLOAD_KEYS = [
30736
31444
  "operations"
30737
31445
  ];
30738
31446
  const MAX_COLLECT_DEPTH = 4;
30739
- function asRecord$3(value) {
31447
+ function asRecord$4(value) {
30740
31448
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
30741
31449
  }
30742
31450
  function pushChangedFilePath(target, value) {
@@ -30751,7 +31459,7 @@ function collectChangedFilePaths(value, target, depth) {
30751
31459
  for (const entry of value) collectChangedFilePaths(entry, target, depth + 1);
30752
31460
  return;
30753
31461
  }
30754
- const record = asRecord$3(value);
31462
+ const record = asRecord$4(value);
30755
31463
  if (!record) return;
30756
31464
  for (const field of CHANGED_FILE_FIELDS) pushChangedFilePath(target, record[field]);
30757
31465
  for (const nestedKey of NESTED_PAYLOAD_KEYS) if (nestedKey in record) collectChangedFilePaths(record[nestedKey], target, depth + 1);
@@ -30762,7 +31470,7 @@ function collectChangedFilePaths(value, target, depth) {
30762
31470
  */
30763
31471
  function collectActivityChangedFilePaths(payload) {
30764
31472
  const target = /* @__PURE__ */ new Set();
30765
- collectChangedFilePaths(asRecord$3(asRecord$3(payload)?.data), target, 0);
31473
+ collectChangedFilePaths(asRecord$4(asRecord$4(payload)?.data), target, 0);
30766
31474
  return target;
30767
31475
  }
30768
31476
  /**
@@ -32667,7 +33375,7 @@ const classifyNonZeroExit = (command, stderr) => {
32667
33375
  if (command === "gh" && (normalized.includes("could not resolve to a pullrequest") || normalized.includes("repository.pullrequest") || normalized.includes("no pull requests found for branch") || normalized.includes("pull request not found")) || command === "glab" && (normalized.includes("merge request not found") || normalized.includes("not found") || normalized.includes("404")) || command === "az" && normalized.includes("pull request") && (normalized.includes("not found") || normalized.includes("does not exist"))) return "not-found";
32668
33376
  return "command-failed";
32669
33377
  };
32670
- const make$44 = Effect.gen(function* () {
33378
+ const make$45 = Effect.gen(function* () {
32671
33379
  const processRunner = yield* ProcessRunner;
32672
33380
  const run = Effect.fn("VcsProcess.run")(function* (input) {
32673
33381
  const baseError = {
@@ -32724,7 +33432,7 @@ const make$44 = Effect.gen(function* () {
32724
33432
  });
32725
33433
  return VcsProcess.of({ run });
32726
33434
  });
32727
- const layer$39 = Layer.effect(VcsProcess, make$44).pipe(Layer.provide(layer$53));
33435
+ const layer$40 = Layer.effect(VcsProcess, make$45).pipe(Layer.provide(layer$54));
32728
33436
  //#endregion
32729
33437
  //#region src/vcs/VcsDriver.ts
32730
33438
  var VcsDriver = class extends Context.Service()("@p4code/cli/vcs/VcsDriver") {};
@@ -33175,12 +33883,12 @@ const makeVcsDriver = Effect.gen(function* () {
33175
33883
  const driver = yield* makeVcsDriverShape();
33176
33884
  return VcsDriver.of(driver);
33177
33885
  });
33178
- const make$43 = Effect.gen(function* () {
33886
+ const make$44 = Effect.gen(function* () {
33179
33887
  const git = yield* makeGitVcsDriverCore();
33180
33888
  return GitVcsDriver.of(git);
33181
33889
  });
33182
33890
  Layer.effect(VcsDriver, makeVcsDriver);
33183
- const layer$38 = Layer.effect(GitVcsDriver, make$43);
33891
+ const layer$39 = Layer.effect(GitVcsDriver, make$44);
33184
33892
  //#endregion
33185
33893
  //#region src/vcs/VcsProjectConfig.ts
33186
33894
  const ProjectVcsConfigJson = fromLenientJson(Schema$1.Struct({
@@ -33212,7 +33920,7 @@ const logVcsProjectConfigError = (error) => Effect.logWarning(error).pipe(Effect
33212
33920
  configPath: error.configPath,
33213
33921
  errorTag: error._tag
33214
33922
  }));
33215
- const make$42 = Effect.gen(function* () {
33923
+ const make$43 = Effect.gen(function* () {
33216
33924
  const fileSystem = yield* FileSystem.FileSystem;
33217
33925
  const path = yield* Path.Path;
33218
33926
  const findConfigPath = Effect.fn("VcsProjectConfig.findConfigPath")(function* (cwd) {
@@ -33253,7 +33961,7 @@ const make$42 = Effect.gen(function* () {
33253
33961
  });
33254
33962
  return VcsProjectConfig.of({ resolveKind });
33255
33963
  });
33256
- const layer$37 = Layer.effect(VcsProjectConfig, make$42);
33964
+ const layer$38 = Layer.effect(VcsProjectConfig, make$43);
33257
33965
  //#endregion
33258
33966
  //#region src/vcs/VcsDriverRegistry.ts
33259
33967
  const DETECTION_CACHE_CAPACITY = 2048;
@@ -33273,7 +33981,7 @@ function parseDetectionCacheKey(key) {
33273
33981
  cwd: key.slice(separatorIndex + 1)
33274
33982
  };
33275
33983
  }
33276
- const make$41 = Effect.gen(function* () {
33984
+ const make$42 = Effect.gen(function* () {
33277
33985
  const projectConfig = yield* VcsProjectConfig;
33278
33986
  const git = yield* makeVcsDriver;
33279
33987
  const drivers = { git };
@@ -33330,7 +34038,7 @@ const make$41 = Effect.gen(function* () {
33330
34038
  resolve
33331
34039
  });
33332
34040
  });
33333
- const layer$36 = Layer.effect(VcsDriverRegistry, make$41).pipe(Layer.provide(layer$37));
34041
+ const layer$37 = Layer.effect(VcsDriverRegistry, make$42).pipe(Layer.provide(layer$38));
33334
34042
  //#endregion
33335
34043
  //#region src/checkpointing/CheckpointStore.ts
33336
34044
  /**
@@ -33350,7 +34058,7 @@ const layer$36 = Layer.effect(VcsDriverRegistry, make$41).pipe(Layer.provide(lay
33350
34058
  */
33351
34059
  /** Service tag for checkpoint persistence and restore operations. */
33352
34060
  var CheckpointStore = class extends Context.Service()("@p4code/cli/checkpointing/CheckpointStore") {};
33353
- const make$40 = Effect.gen(function* () {
34061
+ const make$41 = Effect.gen(function* () {
33354
34062
  const vcsRegistry = yield* VcsDriverRegistry;
33355
34063
  const resolveCheckpoints = Effect.fn("CheckpointStore.resolveCheckpoints")(function* (operation, cwd) {
33356
34064
  const handle = yield* vcsRegistry.resolve({ cwd });
@@ -33389,7 +34097,7 @@ const make$40 = Effect.gen(function* () {
33389
34097
  deleteCheckpointRefs
33390
34098
  });
33391
34099
  });
33392
- const layer$35 = Layer.effect(CheckpointStore, make$40);
34100
+ const layer$36 = Layer.effect(CheckpointStore, make$41);
33393
34101
  //#endregion
33394
34102
  //#region src/checkpointing/CheckpointDiffQuery.ts
33395
34103
  /**
@@ -33411,7 +34119,7 @@ function buildTurnDiffResult(input, diff) {
33411
34119
  diff
33412
34120
  };
33413
34121
  }
33414
- const make$39 = Effect.gen(function* () {
34122
+ const make$40 = Effect.gen(function* () {
33415
34123
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
33416
34124
  const checkpointStore = yield* CheckpointStore;
33417
34125
  const threadActivities = yield* ProjectionThreadActivityRepository;
@@ -33577,7 +34285,7 @@ const make$39 = Effect.gen(function* () {
33577
34285
  getFullThreadDiff
33578
34286
  });
33579
34287
  });
33580
- const layer$34 = Layer.effect(CheckpointDiffQuery, make$39);
34288
+ const layer$35 = Layer.effect(CheckpointDiffQuery, make$40);
33581
34289
  //#endregion
33582
34290
  //#region ../../packages/shared/src/toolCategory.ts
33583
34291
  const TOOL_CATEGORY_TITLES = {
@@ -33846,22 +34554,22 @@ function classifyToolCategory(input) {
33846
34554
  if (normalized.includes("image")) return "image_view";
33847
34555
  return "tool";
33848
34556
  }
33849
- function asRecord$2(value) {
34557
+ function asRecord$3(value) {
33850
34558
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
33851
34559
  }
33852
34560
  /** Classify from a runtime item payload's `data` (`{ toolName, input }`). */
33853
34561
  function classifyToolCategoryFromToolData(data) {
33854
- const record = asRecord$2(data);
34562
+ const record = asRecord$3(data);
33855
34563
  const toolName = record?.toolName;
33856
34564
  if (typeof toolName !== "string" || toolName.trim().length === 0) return;
33857
34565
  return classifyToolCategory({
33858
34566
  toolName,
33859
- toolInput: asRecord$2(record?.input)
34567
+ toolInput: asRecord$3(record?.input)
33860
34568
  });
33861
34569
  }
33862
34570
  //#endregion
33863
34571
  //#region src/orchestration/ActivityPayloadProjection.ts
33864
- function asRecord$1(value) {
34572
+ function asRecord$2(value) {
33865
34573
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
33866
34574
  }
33867
34575
  function asTrimmedString$1(value) {
@@ -33884,7 +34592,7 @@ function collectChangedFiles(value, target, seen, depth) {
33884
34592
  }
33885
34593
  return;
33886
34594
  }
33887
- const record = asRecord$1(value);
34595
+ const record = asRecord$2(value);
33888
34596
  if (!record) return;
33889
34597
  pushChangedFile(target, seen, record.path);
33890
34598
  pushChangedFile(target, seen, record.filePath);
@@ -33910,13 +34618,13 @@ function collectChangedFiles(value, target, seen, depth) {
33910
34618
  }
33911
34619
  }
33912
34620
  function projectCommandData(data) {
33913
- const item = asRecord$1(data.item);
34621
+ const item = asRecord$2(data.item);
33914
34622
  if (!item) return;
33915
34623
  const projectedItem = {};
33916
34624
  if ("command" in item) projectedItem.command = item.command;
33917
- const input = asRecord$1(item.input);
34625
+ const input = asRecord$2(item.input);
33918
34626
  if (input && "command" in input) projectedItem.input = { command: input.command };
33919
- const result = asRecord$1(item.result);
34627
+ const result = asRecord$2(item.result);
33920
34628
  if (result && "command" in result) projectedItem.result = { command: result.command };
33921
34629
  return Object.keys(projectedItem).length > 0 ? projectedItem : void 0;
33922
34630
  }
@@ -33932,7 +34640,7 @@ function summarizeToolTextOutput(value) {
33932
34640
  return null;
33933
34641
  }
33934
34642
  function projectRawOutput(value) {
33935
- const rawOutput = asRecord$1(value);
34643
+ const rawOutput = asRecord$2(value);
33936
34644
  if (!rawOutput) return;
33937
34645
  if (typeof rawOutput.totalFiles === "number" && Number.isFinite(rawOutput.totalFiles)) return {
33938
34646
  totalFiles: rawOutput.totalFiles,
@@ -33954,14 +34662,14 @@ function projectRawOutput(value) {
33954
34662
  * the full payload in persistence and the event store.
33955
34663
  */
33956
34664
  function projectActivityPayload(activity) {
33957
- const payload = asRecord$1(activity.payload);
33958
- const data = asRecord$1(payload?.data);
34665
+ const payload = asRecord$2(activity.payload);
34666
+ const data = asRecord$2(payload?.data);
33959
34667
  if (!payload || !data || payload.itemType === "mcp_tool_call") return activity;
33960
34668
  const projectedData = {};
33961
34669
  const item = projectCommandData(data);
33962
34670
  if (item) projectedData.item = item;
33963
34671
  if ("command" in data) projectedData.command = data.command;
33964
- const input = asRecord$1(data.input);
34672
+ const input = asRecord$2(data.input);
33965
34673
  if (input && "command" in input) projectedData.input = { command: input.command };
33966
34674
  const changedFiles = [];
33967
34675
  collectChangedFiles(data, changedFiles, /* @__PURE__ */ new Set(), 0);
@@ -33988,7 +34696,7 @@ function projectActivityPayload(activity) {
33988
34696
  */
33989
34697
  function isResolvableContextWindowActivity(activity) {
33990
34698
  if (activity.kind !== "context-window.updated") return false;
33991
- const usedTokens = asRecord$1(activity.payload)?.usedTokens;
34699
+ const usedTokens = asRecord$2(activity.payload)?.usedTokens;
33992
34700
  return typeof usedTokens === "number" && Number.isFinite(usedTokens) && usedTokens >= 0;
33993
34701
  }
33994
34702
  /**
@@ -34004,7 +34712,7 @@ function isResolvableContextWindowActivity(activity) {
34004
34712
  * client.
34005
34713
  */
34006
34714
  function withoutContextWindowBreakdown$1(activity) {
34007
- const payload = asRecord$1(activity.payload);
34715
+ const payload = asRecord$2(activity.payload);
34008
34716
  if (!payload || payload.breakdown === void 0) return activity;
34009
34717
  const { breakdown: _breakdown, ...rest } = payload;
34010
34718
  return {
@@ -34019,7 +34727,7 @@ function dropStaleContextWindowActivities(activities) {
34019
34727
  const retainedIndexes = new Set(latestIndexByTurn.values());
34020
34728
  let breakdownIndex = null;
34021
34729
  for (const index of retainedIndexes) {
34022
- if (asRecord$1(activities[index].payload)?.breakdown === void 0) continue;
34730
+ if (asRecord$2(activities[index].payload)?.breakdown === void 0) continue;
34023
34731
  if (breakdownIndex === null || index > breakdownIndex) breakdownIndex = index;
34024
34732
  }
34025
34733
  return activities.flatMap((activity, index) => {
@@ -34147,6 +34855,7 @@ const buildTaskRow = (input, taskId, timestamp, repositoryKey = null) => ({
34147
34855
  projectId: input.projectId ?? null,
34148
34856
  repositoryKey: repositoryKey ?? input.repositoryKey ?? null,
34149
34857
  threadId: input.threadId ?? null,
34858
+ externalRef: input.externalRef ?? null,
34150
34859
  title: input.title,
34151
34860
  body: input.body ?? "",
34152
34861
  status: input.status ?? "backlog",
@@ -34739,7 +35448,7 @@ function makeUpdateState(input) {
34739
35448
  output: input.output ?? null
34740
35449
  };
34741
35450
  }
34742
- const make$38 = Effect.fn("ProviderMaintenanceRunner.make")(function* () {
35451
+ const make$39 = Effect.fn("ProviderMaintenanceRunner.make")(function* () {
34743
35452
  const providerRegistry = yield* ProviderRegistry;
34744
35453
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
34745
35454
  const httpClient = yield* HttpClient.HttpClient;
@@ -34854,7 +35563,7 @@ const make$38 = Effect.fn("ProviderMaintenanceRunner.make")(function* () {
34854
35563
  });
34855
35564
  return ProviderMaintenanceRunner.of({ updateProvider });
34856
35565
  });
34857
- const layer$33 = Layer.effect(ProviderMaintenanceRunner, make$38());
35566
+ const layer$34 = Layer.effect(ProviderMaintenanceRunner, make$39());
34858
35567
  //#endregion
34859
35568
  //#region src/provider/Services/ProviderInstanceRegistry.ts
34860
35569
  var ProviderInstanceRegistry = class extends Context.Service()("@p4code/cli/provider/Services/ProviderInstanceRegistry") {};
@@ -34878,11 +35587,11 @@ const makeTextGenerationFromRegistry = (registry) => TextGeneration.of({
34878
35587
  detail: "This provider does not report account usage."
34879
35588
  }))))
34880
35589
  });
34881
- const make$37 = Effect.gen(function* () {
35590
+ const make$38 = Effect.gen(function* () {
34882
35591
  const registry = yield* ProviderInstanceRegistry;
34883
35592
  return makeTextGenerationFromRegistry(registry);
34884
35593
  });
34885
- const layer$32 = Layer.effect(TextGeneration, make$37);
35594
+ const layer$33 = Layer.effect(TextGeneration, make$38);
34886
35595
  const SKILL_MANIFEST_FILENAME = "SKILL.md";
34887
35596
  /**
34888
35597
  * Split a catalogue id (`owner/repo/skill-name`) into its parts.
@@ -34991,7 +35700,7 @@ function toAssetFilePath(repositoryPath, root) {
34991
35700
  const REGISTRY_SEARCH_URL = "https://www.skills.sh/api/search";
34992
35701
  const GITHUB_API_URL = "https://api.github.com";
34993
35702
  const GITHUB_RAW_URL = "https://raw.githubusercontent.com";
34994
- const REQUEST_TIMEOUT_MS = 15e3;
35703
+ const REQUEST_TIMEOUT_MS$1 = 15e3;
34995
35704
  /** More than a person reads, few enough that the panel is a list and not a feed. */
34996
35705
  const MAX_SEARCH_RESULTS = 25;
34997
35706
  /**
@@ -35028,10 +35737,10 @@ const emptyFetch = (id, unavailable) => ({
35028
35737
  skipped: [],
35029
35738
  unavailable
35030
35739
  });
35031
- const make$36 = Effect.gen(function* () {
35740
+ const make$37 = Effect.gen(function* () {
35032
35741
  const http = yield* HttpClient.HttpClient;
35033
35742
  const request = Effect.fn("SkillRegistry.request")(function* (url) {
35034
- return yield* http.execute(HttpClientRequest.get(url).pipe(HttpClientRequest.setHeader("accept", "application/json"), HttpClientRequest.setHeader("user-agent", "p4code"))).pipe(Effect.timeout(REQUEST_TIMEOUT_MS));
35743
+ return yield* http.execute(HttpClientRequest.get(url).pipe(HttpClientRequest.setHeader("accept", "application/json"), HttpClientRequest.setHeader("user-agent", "p4code"))).pipe(Effect.timeout(REQUEST_TIMEOUT_MS$1));
35035
35744
  });
35036
35745
  const search = (query) => Effect.gen(function* () {
35037
35746
  const response = yield* request(`${REGISTRY_SEARCH_URL}?q=${encodeURIComponent(query)}`).pipe(Effect.orElseSucceed(() => null));
@@ -35099,7 +35808,309 @@ const make$36 = Effect.gen(function* () {
35099
35808
  fetch
35100
35809
  };
35101
35810
  });
35102
- const layer$31 = Layer.effect(SkillRegistry, make$36);
35811
+ const layer$32 = Layer.effect(SkillRegistry, make$37);
35812
+ //#endregion
35813
+ //#region ../../packages/shared/src/ticketReference.ts
35814
+ /**
35815
+ * Recognizing a tracker reference in text somebody pasted.
35816
+ *
35817
+ * Shared because both ends need the same answer: the composer decides whether a
35818
+ * paste is worth a round trip, and the server decides what to ask the tracker.
35819
+ * Two copies of this rule would differ, and the difference would read as "it
35820
+ * works on my machine but the paste does nothing".
35821
+ *
35822
+ * @module ticketReference
35823
+ */
35824
+ /**
35825
+ * A Linear issue URL, e.g.
35826
+ * `https://linear.app/omnicasa/issue/MOBILE-12262/haptic-feedback`.
35827
+ *
35828
+ * The workspace slug is what makes the URL unguessable from the identifier
35829
+ * alone, which is why a resolved ticket keeps the tracker's own URL rather than
35830
+ * rebuilding one.
35831
+ */
35832
+ const LINEAR_ISSUE_URL_PATTERN = /https?:\/\/(?:www\.)?linear\.app\/[^/\s]+\/issue\/([A-Za-z][A-Za-z0-9]*-\d+)/;
35833
+ /**
35834
+ * A bare identifier, e.g. `MOBILE-12262`.
35835
+ *
35836
+ * Anchored: only a paste that is *nothing but* an identifier counts. Scanning
35837
+ * prose for this shape would claim things that are not tickets - a date range,
35838
+ * a version, a p4code readable id like `P4-40` - and each false positive costs
35839
+ * a request and, worse, a wrong link.
35840
+ */
35841
+ const BARE_IDENTIFIER_PATTERN = /^([A-Za-z][A-Za-z0-9]*-\d+)$/;
35842
+ /**
35843
+ * The issue identifier a paste refers to, or `null` when it refers to none.
35844
+ *
35845
+ * Case is normalized up, the way every tracker prints it, so the same ticket
35846
+ * pasted two ways compares equal.
35847
+ */
35848
+ function parseTicketReference(text) {
35849
+ const trimmed = text.trim();
35850
+ if (trimmed.length === 0) return null;
35851
+ const url = LINEAR_ISSUE_URL_PATTERN.exec(trimmed);
35852
+ if (url?.[1] !== void 0) return url[1].toUpperCase();
35853
+ const bare = BARE_IDENTIFIER_PATTERN.exec(trimmed);
35854
+ if (bare?.[1] !== void 0) return bare[1].toUpperCase();
35855
+ return null;
35856
+ }
35857
+ //#endregion
35858
+ //#region src/mcp/McpToolClient.ts
35859
+ /**
35860
+ * Calling a tool on somebody else's MCP server.
35861
+ *
35862
+ * The rest of `apps/server/src/mcp` points the other way: it *exposes* p4code's
35863
+ * toolkit to an agent. This is the only place p4code is the client, and it
35864
+ * exists because one feature - recognizing a pasted ticket - needs an answer
35865
+ * before any agent turn runs, from a server the user has already registered.
35866
+ *
35867
+ * Deliberately not a general MCP client. There is no session reuse, no tool
35868
+ * listing, no notifications, no SSE stream held open: one request/response pair
35869
+ * per call, which is all streamable HTTP requires for a plain `tools/call`. A
35870
+ * real client belongs here only once something needs one.
35871
+ *
35872
+ * @module mcp/McpToolClient
35873
+ */
35874
+ /**
35875
+ * The version p4code speaks. Sent on `initialize` and echoed back as a header
35876
+ * on the follow-up calls, which is what the spec asks of a client that has
35877
+ * already negotiated.
35878
+ */
35879
+ const MCP_PROTOCOL_VERSION = "2025-06-18";
35880
+ const REQUEST_TIMEOUT_MS = 15e3;
35881
+ var McpToolCallError = class extends Schema$1.TaggedErrorClass()("McpToolCallError", {
35882
+ detail: Schema$1.String,
35883
+ status: Schema$1.NullOr(Schema$1.Number)
35884
+ }) {
35885
+ get message() {
35886
+ return this.detail;
35887
+ }
35888
+ };
35889
+ const unauthorized$1 = (status) => status === 401 || status === 403;
35890
+ /**
35891
+ * Read a JSON-RPC envelope out of a response that may be either JSON or SSE.
35892
+ *
35893
+ * A streamable-http server picks the encoding, and both are legal answers to a
35894
+ * single call. The SSE branch keeps the last `data:` payload that parses: a
35895
+ * server is free to emit progress notifications ahead of the result, and the
35896
+ * result is what a caller of this asked for.
35897
+ */
35898
+ const readEnvelope = (body) => {
35899
+ const direct = parseJsonObject(body);
35900
+ if (direct !== void 0) return direct;
35901
+ let latest;
35902
+ for (const line of body.split("\n")) {
35903
+ const trimmed = line.trim();
35904
+ if (!trimmed.startsWith("data:")) continue;
35905
+ const parsed = parseJsonObject(trimmed.slice(5).trim());
35906
+ if (parsed !== void 0) latest = parsed;
35907
+ }
35908
+ return latest;
35909
+ };
35910
+ const parseJsonObject = (text) => {
35911
+ if (text.length === 0) return void 0;
35912
+ try {
35913
+ const parsed = JSON.parse(text);
35914
+ return typeof parsed === "object" && parsed !== null ? parsed : void 0;
35915
+ } catch {
35916
+ return;
35917
+ }
35918
+ };
35919
+ const textOf = (content) => {
35920
+ if (!Array.isArray(content)) return "";
35921
+ const parts = [];
35922
+ for (const block of content) {
35923
+ if (typeof block !== "object" || block === null) continue;
35924
+ const record = block;
35925
+ if (record["type"] === "text" && typeof record["text"] === "string") parts.push(record["text"]);
35926
+ }
35927
+ return parts.join("\n");
35928
+ };
35929
+ /**
35930
+ * One `tools/call`, preceded by the `initialize` handshake the transport
35931
+ * requires.
35932
+ *
35933
+ * The handshake is not optional and not cacheable here: a server may hand out a
35934
+ * session id, and this holds no session state between calls. Two round trips
35935
+ * for one answer is the price of not keeping a connection open for a feature
35936
+ * that fires when somebody pastes something.
35937
+ */
35938
+ const callMcpTool = Effect.fn("McpToolClient.callMcpTool")(function* (input) {
35939
+ const http = yield* HttpClient$1.HttpClient;
35940
+ const post = (body, extraHeaders) => http.execute(HttpClientRequest$1.bodyJsonUnsafe(HttpClientRequest$1.setHeaders(HttpClientRequest$1.post(input.url), {
35941
+ ...input.headers,
35942
+ ...extraHeaders,
35943
+ accept: "application/json, text/event-stream"
35944
+ }), body)).pipe(Effect.timeout(REQUEST_TIMEOUT_MS), Effect.catchCause((cause) => Effect.fail(new McpToolCallError({
35945
+ detail: `Could not reach ${input.url}: ${String(cause)}`,
35946
+ status: null
35947
+ }))));
35948
+ const initialize = yield* post({
35949
+ jsonrpc: "2.0",
35950
+ id: 1,
35951
+ method: "initialize",
35952
+ params: {
35953
+ protocolVersion: MCP_PROTOCOL_VERSION,
35954
+ capabilities: {},
35955
+ clientInfo: {
35956
+ name: "p4code",
35957
+ version: "0.0.0"
35958
+ }
35959
+ }
35960
+ }, {});
35961
+ if (initialize.status >= 400) return yield* new McpToolCallError({
35962
+ detail: unauthorized$1(initialize.status) ? "The server rejected p4code's credentials." : `The server answered ${initialize.status} to initialize.`,
35963
+ status: initialize.status
35964
+ });
35965
+ const sessionId = initialize.headers["mcp-session-id"];
35966
+ const sessionHeaders = {
35967
+ "mcp-protocol-version": MCP_PROTOCOL_VERSION,
35968
+ ...sessionId === void 0 ? {} : { "mcp-session-id": sessionId }
35969
+ };
35970
+ yield* post({
35971
+ jsonrpc: "2.0",
35972
+ method: "notifications/initialized"
35973
+ }, sessionHeaders).pipe(Effect.ignore);
35974
+ const response = yield* post({
35975
+ jsonrpc: "2.0",
35976
+ id: 2,
35977
+ method: "tools/call",
35978
+ params: {
35979
+ name: input.toolName,
35980
+ arguments: input.arguments
35981
+ }
35982
+ }, sessionHeaders);
35983
+ const body = yield* response.text.pipe(Effect.catchCause(() => Effect.succeed("")));
35984
+ if (response.status >= 400) return yield* new McpToolCallError({
35985
+ detail: unauthorized$1(response.status) ? "The server rejected p4code's credentials." : `The server answered ${response.status} to ${input.toolName}.`,
35986
+ status: response.status
35987
+ });
35988
+ const envelope = readEnvelope(body);
35989
+ if (envelope === void 0) return yield* new McpToolCallError({
35990
+ detail: `The server's answer to ${input.toolName} was not JSON-RPC.`,
35991
+ status: response.status
35992
+ });
35993
+ const error = envelope["error"];
35994
+ if (typeof error === "object" && error !== null) {
35995
+ const detail = error["message"];
35996
+ return yield* new McpToolCallError({
35997
+ detail: typeof detail === "string" ? detail : `${input.toolName} failed.`,
35998
+ status: response.status
35999
+ });
36000
+ }
36001
+ const result = envelope["result"];
36002
+ if (typeof result !== "object" || result === null) return yield* new McpToolCallError({
36003
+ detail: `${input.toolName} returned no result.`,
36004
+ status: response.status
36005
+ });
36006
+ const resultRecord = result;
36007
+ const text = textOf(resultRecord["content"]);
36008
+ if (resultRecord["isError"] === true) return yield* new McpToolCallError({
36009
+ detail: text.length > 0 ? text : `${input.toolName} reported an error.`,
36010
+ status: response.status
36011
+ });
36012
+ return {
36013
+ structuredContent: resultRecord["structuredContent"],
36014
+ text
36015
+ };
36016
+ });
36017
+ //#endregion
36018
+ //#region src/mcp/TicketResolver.ts
36019
+ /**
36020
+ * The registered server name a Linear ticket is resolved through.
36021
+ *
36022
+ * Matched by name rather than by URL: the name is what the user typed in
36023
+ * Settings and what every other surface calls it, and a workspace may reach
36024
+ * Linear through a proxy URL that no pattern here would recognize.
36025
+ */
36026
+ const LINEAR_SERVER_NAME = "linear";
36027
+ /** Linear's own tool for reading one issue. */
36028
+ const LINEAR_GET_ISSUE_TOOL = "get_issue";
36029
+ var TicketResolver = class extends Context.Service()("@p4code/cli/mcp/TicketResolver") {};
36030
+ const stringField$1 = (record, key) => {
36031
+ const value = record[key];
36032
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
36033
+ };
36034
+ const asRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
36035
+ /**
36036
+ * Find the issue inside whatever the tool returned.
36037
+ *
36038
+ * MCP leaves the shape of a tool result to the tool, so this accepts the three
36039
+ * arrangements in the wild - the structured payload itself, that payload with
36040
+ * the issue nested one level down, or a text block that happens to be JSON -
36041
+ * rather than assuming the one Linear happens to send today.
36042
+ */
36043
+ const readIssue = (result) => {
36044
+ const structured = asRecord$1(result.structuredContent);
36045
+ if (structured !== void 0) {
36046
+ const nested = asRecord$1(structured["issue"]);
36047
+ if (nested !== void 0) return nested;
36048
+ if (stringField$1(structured, "url") !== void 0) return structured;
36049
+ }
36050
+ try {
36051
+ const parsed = JSON.parse(result.text);
36052
+ const record = asRecord$1(parsed);
36053
+ if (record === void 0) return structured;
36054
+ return asRecord$1(record["issue"]) ?? record;
36055
+ } catch {
36056
+ return structured;
36057
+ }
36058
+ };
36059
+ const make$36 = Effect.gen(function* () {
36060
+ const registry = yield* McpRegistry;
36061
+ const oauth = yield* McpOAuth;
36062
+ const http = yield* HttpClient$1.HttpClient;
36063
+ return { resolve: Effect.fn("TicketResolver.resolve")(function* (reference) {
36064
+ const identifier = parseTicketReference(reference);
36065
+ if (identifier === null) return yield* new TicketResolveError({
36066
+ reason: "not_found",
36067
+ detail: `"${reference}" does not look like a ticket reference.`
36068
+ });
36069
+ const server = (yield* registry.list).find((candidate) => candidate.registration.name.toLowerCase() === LINEAR_SERVER_NAME && candidate.registration.enabled);
36070
+ if (server === void 0 || server.registration.transport === "stdio") return yield* new TicketResolveError({
36071
+ reason: "not_configured",
36072
+ detail: "No Linear MCP server is registered on this machine. Add it in Settings to link tickets."
36073
+ });
36074
+ const registration = server.registration;
36075
+ const token = yield* oauth.accessTokenFor(registration);
36076
+ const headers = {
36077
+ ...registration.headers,
36078
+ ...Option.isSome(token) ? { authorization: `Bearer ${token.value}` } : {}
36079
+ };
36080
+ if (Option.isNone(token) && Object.keys(registration.headers ?? {}).length === 0) return yield* new TicketResolveError({
36081
+ reason: "not_authorized",
36082
+ detail: "Linear is registered but not signed in on this machine. Sign in from Settings."
36083
+ });
36084
+ const result = yield* callMcpTool({
36085
+ url: registration.url,
36086
+ headers,
36087
+ toolName: LINEAR_GET_ISSUE_TOOL,
36088
+ arguments: { id: identifier }
36089
+ }).pipe(Effect.provideService(HttpClient$1.HttpClient, http), Effect.mapError((error) => error.status === 401 || error.status === 403 ? new TicketResolveError({
36090
+ reason: "not_authorized",
36091
+ detail: "Linear rejected p4code's sign-in. Sign in again from Settings."
36092
+ }) : new TicketResolveError({
36093
+ reason: "unavailable",
36094
+ detail: error.detail
36095
+ })));
36096
+ const issue = readIssue(result);
36097
+ const url = issue === void 0 ? void 0 : stringField$1(issue, "url");
36098
+ const title = issue === void 0 ? void 0 : stringField$1(issue, "title");
36099
+ if (issue === void 0 || url === void 0 || title === void 0) return yield* new TicketResolveError({
36100
+ reason: "not_found",
36101
+ detail: `Linear returned nothing usable for ${identifier}.`
36102
+ });
36103
+ return {
36104
+ externalRef: {
36105
+ source: "linear",
36106
+ identifier: stringField$1(issue, "identifier") ?? identifier,
36107
+ url
36108
+ },
36109
+ title
36110
+ };
36111
+ }) };
36112
+ });
36113
+ const layer$31 = Layer.effect(TicketResolver, make$36);
35103
36114
  //#endregion
35104
36115
  //#region ../../packages/shared/src/KeyedCoalescingWorker.ts
35105
36116
  const makeKeyedCoalescingWorker = (options) => Effect.gen(function* () {
@@ -37189,7 +38200,7 @@ const makeWithOptions$1 = Effect.fn("TerminalManager.makeWithOptions")(function*
37189
38200
  subscribeMetadata
37190
38201
  });
37191
38202
  });
37192
- const layer$29 = Layer.effect(TerminalManager, make$34()).pipe(Layer.provide(layer$53));
38203
+ const layer$29 = Layer.effect(TerminalManager, make$34()).pipe(Layer.provide(layer$54));
37193
38204
  //#endregion
37194
38205
  //#region src/mcp/McpInvocationContext.ts
37195
38206
  var McpInvocationContext = class extends Context.Service()("@p4code/cli/mcp/McpInvocationContext") {};
@@ -44269,6 +45280,7 @@ const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
44269
45280
  [WS_METHODS.tasksUpdate, AuthOrchestrationOperateScope],
44270
45281
  [WS_METHODS.tasksDelete, AuthOrchestrationOperateScope],
44271
45282
  [WS_METHODS.tasksStartThread, AuthOrchestrationOperateScope],
45283
+ [WS_METHODS.ticketsResolve, AuthOrchestrationReadScope],
44272
45284
  [WS_METHODS.subscribeTasks, AuthOrchestrationReadScope],
44273
45285
  [WS_METHODS.hubGetSyncStatus, AuthOrchestrationReadScope],
44274
45286
  [WS_METHODS.hubConnect, AuthOrchestrationOperateScope],
@@ -44423,6 +45435,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
44423
45435
  const mcpOAuth = yield* McpOAuth;
44424
45436
  const claudeMcpFiles = yield* ClaudeMcpFiles;
44425
45437
  const skillRegistry = yield* SkillRegistry;
45438
+ const ticketResolver = yield* TicketResolver;
44426
45439
  const listMcpServersEverywhere = Effect.gen(function* () {
44427
45440
  const servers = [...yield* mcpRegistry.list];
44428
45441
  const projectRows = yield* projectionProjects.listAll().pipe(Effect.map((rows) => rows.filter((row) => row.deletedAt === null)), Effect.orElseSucceed(() => []));
@@ -44969,6 +45982,13 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
44969
45982
  threadId
44970
45983
  };
44971
45984
  }), { "rpc.aggregate": "tasks" }),
45985
+ /**
45986
+ * What a pasted reference is, straight from the tracker.
45987
+ *
45988
+ * Nothing is written here: the client decides whether the answer
45989
+ * becomes a task, so an accidental paste costs one request and no row.
45990
+ */
45991
+ [WS_METHODS.ticketsResolve]: ({ reference }) => observeRpcEffect$1(WS_METHODS.ticketsResolve, ticketResolver.resolve(reference), { "rpc.aggregate": "tickets" }),
44972
45992
  [WS_METHODS.subscribeTasks]: (_input) => observeRpcStreamEffect$1(WS_METHODS.subscribeTasks, Effect.succeed(taskRepository.streamWithSnapshot.pipe(Stream.catch((cause) => Stream.fromEffect(toTaskStoreError("tasks.subscribe")(cause))))), { "rpc.aggregate": "tasks" }),
44973
45993
  [WS_METHODS.hubGetSyncStatus]: (_input) => observeRpcEffect$1(WS_METHODS.hubGetSyncStatus, assetSync.status, { "rpc.aggregate": "hub" }),
44974
45994
  [WS_METHODS.hubConnect]: (input) => observeRpcEffect$1(WS_METHODS.hubConnect, hubLink.connect(input).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(assetSync.status)), { "rpc.aggregate": "hub" }),
@@ -45236,7 +46256,7 @@ const websocketRpcRouteLayer = Layer.unwrap(Effect.gen(function* () {
45236
46256
  const serverAuth = yield* EnvironmentAuth;
45237
46257
  const sessions = yield* SessionStore;
45238
46258
  const session = yield* serverAuth.authenticateWebSocketUpgrade(request).pipe(Effect.catchIf(isServerAuthCredentialError, (error) => failEnvironmentAuthInvalid(serverAuthCredentialReason(error))), Effect.catchIf(isServerAuthInternalError, (error) => failEnvironmentInternal("internal_error", error)));
45239
- const rpcWebSocketHttpEffect = yield* RpcServer.toHttpEffectWebsocket(WsRpcGroup, { disableTracing: true }).pipe(Effect.provide(makeWsRpcLayer(session, previewAutomationBroker).pipe(Layer.provideMerge(RpcSerialization.layerJson), Layer.provide(layer$33), Layer.provide(Layer.succeed(ServerSelfUpdate, serverSelfUpdate)), Layer.provide(Layer.succeed(TaskRepository, taskRepository)), Layer.provide(Layer.succeed(ProjectionProjectRepository, projectionProjects)), Layer.provide(Layer.succeed(P4ProjectFileLoader, p4ProjectFileLoader)), Layer.provide(layer$5.pipe(Layer.provide(layer$14.pipe(Layer.provide(Layer.mergeAll(layer$22, layer$20, layer$18, layer$16)), Layer.provideMerge(layer$38), Layer.provide(layer$36.pipe(Layer.provide(layer$37))))), Layer.provide(layer$39))))));
46259
+ const rpcWebSocketHttpEffect = yield* RpcServer.toHttpEffectWebsocket(WsRpcGroup, { disableTracing: true }).pipe(Effect.provide(makeWsRpcLayer(session, previewAutomationBroker).pipe(Layer.provideMerge(RpcSerialization.layerJson), Layer.provide(layer$34), Layer.provide(Layer.succeed(ServerSelfUpdate, serverSelfUpdate)), Layer.provide(Layer.succeed(TaskRepository, taskRepository)), Layer.provide(Layer.succeed(ProjectionProjectRepository, projectionProjects)), Layer.provide(Layer.succeed(P4ProjectFileLoader, p4ProjectFileLoader)), Layer.provide(layer$5.pipe(Layer.provide(layer$14.pipe(Layer.provide(Layer.mergeAll(layer$22, layer$20, layer$18, layer$16)), Layer.provideMerge(layer$39), Layer.provide(layer$37.pipe(Layer.provide(layer$38))))), Layer.provide(layer$40))))));
45240
46260
  return yield* Effect.acquireUseRelease(sessions.markConnected(session.sessionId), () => rpcWebSocketHttpEffect, () => sessions.markDisconnected(session.sessionId));
45241
46261
  }).pipe(Effect.catchTags({
45242
46262
  EnvironmentAuthInvalidError: HttpServerRespondable.toResponse,
@@ -47560,7 +48580,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
47560
48580
  };
47561
48581
  });
47562
48582
  var OpenCodeRuntime = class extends Context.Service()("@p4code/cli/provider/opencodeRuntime") {};
47563
- const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$71));
48583
+ const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$72));
47564
48584
  //#endregion
47565
48585
  //#region src/textGeneration/TextGenerationUtils.ts
47566
48586
  const isTextGenerationError$3 = Schema$1.is(TextGenerationError);
@@ -49044,88 +50064,6 @@ function formatAskUserQuestionAnswers(answers) {
49044
50064
  return formatted;
49045
50065
  }
49046
50066
  //#endregion
49047
- //#region src/provider/CompressPrompts.ts
49048
- /**
49049
- * Caveman-style response compression rulesets, adapted from the caveman
49050
- * plugin's SKILL.md. Pre-baked per level so injection is a lookup, not a
49051
- * runtime template. Applies to assistant prose only: code blocks, commits,
49052
- * PRs, error strings, and safety-critical text stay uncompressed.
49053
- */
49054
- const COMPRESS_SHARED_RULES = `Respond terse. All technical substance stays. Only fluff dies.
49055
-
49056
- ## Persistence
49057
-
49058
- Active every response. No revert after many turns. No filler drift. Still active if unsure.
49059
-
49060
- ## Rules
49061
-
49062
- Drop filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging. Short synonyms (big not extensive, fix not "implement a solution for"). No tool-call narration, no decorative tables/emoji, no dumping long raw error logs unless asked - quote shortest decisive line. Standard well-known tech acronyms OK (DB/API/HTTP); never invent new abbreviations (cfg/impl/req/res/fn) - tokenizers split them same as the full word: zero tokens saved, reader still decodes. No causal arrows (->) in prose. Technical terms exact. Code blocks unchanged. Errors quoted exact.
49063
-
49064
- Preserve the user's dominant language: compress the style, not the language. Always keep technical terms, code, API names, CLI commands, commit-type keywords (feat/fix/...), and exact error strings verbatim unless the user explicitly asks for translation.
49065
-
49066
- No self-reference. Never name or announce the style. No compressed answer plus normal recap - compressed output only. Exception: user explicitly asks what the mode is.
49067
-
49068
- ## Auto-Clarity
49069
-
49070
- Drop compression and write normally when:
49071
- - Security warnings
49072
- - Irreversible action confirmations
49073
- - Multi-step sequences where fragment order or omitted conjunctions risk misread
49074
- - Compression itself creates technical ambiguity
49075
- - User asks to clarify or repeats a question
49076
-
49077
- Resume compression after the clear part is done.
49078
-
49079
- ## Boundaries
49080
-
49081
- Code, commit messages, and PR descriptions: write normal, uncompressed.`;
49082
- const COMPRESS_RULESETS = {
49083
- lite: `## Response compression: lite
49084
-
49085
- ${COMPRESS_SHARED_RULES}
49086
-
49087
- ## Intensity: lite
49088
-
49089
- No filler or hedging. Keep articles and full sentences. Professional but tight.
49090
-
49091
- Example - "Why does my React component re-render?"
49092
- "Your component re-renders because you create a new object reference each render. Wrap it in \`useMemo\`."`,
49093
- full: `## Response compression: full
49094
-
49095
- ${COMPRESS_SHARED_RULES}
49096
-
49097
- ## Intensity: full
49098
-
49099
- Drop articles (a/an/the), fragments OK, short synonyms. Pattern: [thing] [action] [reason]. [next step].
49100
-
49101
- Not: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..."
49102
- Yes: "Bug in auth middleware. Token expiry check use \`<\` not \`<=\`. Fix:"
49103
-
49104
- Example - "Why does my React component re-render?"
49105
- "New object ref each render. Inline object prop = new ref = re-render. Wrap in \`useMemo\`."`,
49106
- ultra: `## Response compression: ultra
49107
-
49108
- ${COMPRESS_SHARED_RULES}
49109
-
49110
- ## Intensity: ultra
49111
-
49112
- Drop articles, fragments OK, short synonyms. Also strip conjunctions when cause-then-effect stays unambiguous. One word when one word is enough. State each fact once. Code symbols, function names, API names, error strings: never touch.
49113
-
49114
- Example - "Why does my React component re-render?"
49115
- "Inline object prop, new ref, re-render. \`useMemo\`."`
49116
- };
49117
- const COMPRESS_TURN_REMINDERS = {
49118
- lite: "[response style: compressed lite - no filler/hedging, full sentences; code/commits/security text normal]",
49119
- full: "[response style: compressed full - terse fragments, drop articles/filler; code/commits/security text normal]",
49120
- ultra: "[response style: compressed ultra - maximum terseness, one word when enough; code/commits/security text normal]"
49121
- };
49122
- function compressRulesetFor(mode) {
49123
- return mode === "off" ? void 0 : COMPRESS_RULESETS[mode];
49124
- }
49125
- function compressTurnReminderFor(mode) {
49126
- return mode === "off" ? void 0 : COMPRESS_TURN_REMINDERS[mode];
49127
- }
49128
- //#endregion
49129
50067
  //#region src/provider/Layers/ClaudeAdapter.ts
49130
50068
  /**
49131
50069
  * ClaudeAdapterLive - Scoped live implementation for the Claude Agent provider adapter.
@@ -49687,6 +50625,20 @@ const SUPPORTED_CLAUDE_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
49687
50625
  /** Emitted by the CLI but absent from the SDK's `SDKMessage` union. */
49688
50626
  const UNTYPED_COMMAND_LIFECYCLE_MESSAGE = "command_lifecycle";
49689
50627
  const NARRATE_BEFORE_TOOLS_PROMPT = "Before your first tool call in a turn, write one short sentence saying what you are about to do. Keep it to a single line and skip it when you are answering without tools.";
50628
+ /**
50629
+ * Said whichever way the setting is set, and said as an override.
50630
+ *
50631
+ * The preset system prompt this adapter requests already carries an
50632
+ * instruction not to spawn subagents unless the user asked — and a preset is
50633
+ * taken wholesale, so there is no version of it with that line removed. An
50634
+ * append that merely stayed silent when subagents are allowed would therefore
50635
+ * leave the restriction standing, which is the one outcome the setting exists
50636
+ * to change. Both texts name the earlier instruction they replace, because a
50637
+ * later instruction that does not acknowledge the conflict reads as an
50638
+ * accident rather than a decision.
50639
+ */
50640
+ const SUBAGENTS_ALLOWED_PROMPT = "You may spawn subagents with the Task tool whenever it helps, without asking the user first. This overrides any earlier instruction to use subagents only when they are requested. Prefer one for bounded search or review work whose result is much smaller than the reading it takes to produce; do the work inline when the answer is a single file or a single edit.";
50641
+ const SUBAGENTS_ON_REQUEST_PROMPT = "Do not spawn subagents with the Task tool unless the user asks for one. This overrides any earlier instruction permitting them on your own initiative. Do the work inline instead, and say so if it would have been better delegated.";
49690
50642
  const CLAUDE_SETTING_SOURCES = [
49691
50643
  "user",
49692
50644
  "project",
@@ -49915,6 +50867,25 @@ function sdkNativeMethod(message) {
49915
50867
  * would only duplicate a refresh that has already happened.
49916
50868
  */
49917
50869
  const CONSUMED_UNDECLARED_SYSTEM_SUBTYPES = /* @__PURE__ */ new Set(["background_tasks_changed", "vcs_state_changed"]);
50870
+ /**
50871
+ * Another undeclared-but-real subtype, emitted once the CLI has pushed the
50872
+ * turn's changes to a hosting provider:
50873
+ * `{provider: "github", url: "https://github.com/org/repo/pull/96",
50874
+ * repo: "org/repo", identifier: "96"}`. Mapped to `code.published` so the
50875
+ * work log links the pull request instead of logging an unknown-subtype
50876
+ * warning.
50877
+ */
50878
+ const CODE_CHANGE_PUBLISHED_SUBTYPE = "code_change_published";
50879
+ function codePublishedPayloadFromSdkMessage(message) {
50880
+ const record = message;
50881
+ if (typeof record.url !== "string" || record.url.length === 0) return;
50882
+ return {
50883
+ hostingProvider: typeof record.provider === "string" && record.provider.length > 0 ? record.provider : "unknown",
50884
+ url: record.url,
50885
+ ...typeof record.repo === "string" && record.repo.length > 0 ? { repo: record.repo } : {},
50886
+ ...typeof record.identifier === "string" && record.identifier.length > 0 ? { identifier: record.identifier } : {}
50887
+ };
50888
+ }
49918
50889
  const SDK_MESSAGE_NOISE_KEYS = /* @__PURE__ */ new Set([
49919
50890
  "type",
49920
50891
  "subtype",
@@ -50886,6 +51857,17 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
50886
51857
  }
50887
51858
  };
50888
51859
  if (CONSUMED_UNDECLARED_SYSTEM_SUBTYPES.has(message.subtype)) return;
51860
+ if (message.subtype === CODE_CHANGE_PUBLISHED_SUBTYPE) {
51861
+ const published = codePublishedPayloadFromSdkMessage(message);
51862
+ if (published) {
51863
+ yield* offerRuntimeEvent({
51864
+ ...base,
51865
+ type: "code.published",
51866
+ payload: published
51867
+ });
51868
+ return;
51869
+ }
51870
+ }
50889
51871
  switch (message.subtype) {
50890
51872
  case "init":
50891
51873
  yield* offerRuntimeEvent({
@@ -51520,8 +52502,13 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
51520
52502
  const mcpSession = readMcpProviderSession(input.threadId);
51521
52503
  const externalMcpServers = options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers;
51522
52504
  const narrateBeforeTools = options?.resolveToolCallNarration === void 0 ? DEFAULT_SERVER_SETTINGS.enableToolCallNarration : yield* options.resolveToolCallNarration;
52505
+ const unpromptedSubagents = options?.resolveUnpromptedSubagents === void 0 ? DEFAULT_SERVER_SETTINGS.enableUnpromptedSubagents : yield* options.resolveUnpromptedSubagents;
51523
52506
  const compressRuleset = compressRulesetFor(input.compressMode ?? "off");
51524
- const systemPromptAppend = [...narrateBeforeTools ? [NARRATE_BEFORE_TOOLS_PROMPT] : [], ...compressRuleset !== void 0 ? [compressRuleset] : []].join("\n\n");
52507
+ const systemPromptAppend = [
52508
+ ...narrateBeforeTools ? [NARRATE_BEFORE_TOOLS_PROMPT] : [],
52509
+ unpromptedSubagents ? SUBAGENTS_ALLOWED_PROMPT : SUBAGENTS_ON_REQUEST_PROMPT,
52510
+ ...compressRuleset !== void 0 ? [compressRuleset] : []
52511
+ ].join("\n\n");
51525
52512
  const queryOptions = {
51526
52513
  ...input.cwd ? { cwd: input.cwd } : {},
51527
52514
  ...apiModelId ? { model: apiModelId } : {},
@@ -51983,12 +52970,14 @@ const ClaudeDriver = {
51983
52970
  const mcpRegistry = yield* McpRegistry;
51984
52971
  const resolveDisabledSkills = serverSettings.getSettings.pipe(Effect.map((settings) => settings.disabledSkills), Effect.orElseSucceed(() => []));
51985
52972
  const resolveToolCallNarration = serverSettings.getSettings.pipe(Effect.map((settings) => settings.enableToolCallNarration), Effect.orElseSucceed(() => DEFAULT_SERVER_SETTINGS.enableToolCallNarration));
52973
+ const resolveUnpromptedSubagents = serverSettings.getSettings.pipe(Effect.map((settings) => settings.enableUnpromptedSubagents), Effect.orElseSucceed(() => DEFAULT_SERVER_SETTINGS.enableUnpromptedSubagents));
51986
52974
  const adapter = yield* makeClaudeAdapter(effectiveConfig, {
51987
52975
  instanceId,
51988
52976
  environment: processEnv,
51989
52977
  resolveMcpServers: mcpRegistry.resolveForSession,
51990
52978
  resolveDisabledSkills,
51991
52979
  resolveToolCallNarration,
52980
+ resolveUnpromptedSubagents,
51992
52981
  ...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}
51993
52982
  });
51994
52983
  const textGeneration = yield* makeClaudeTextGeneration(effectiveConfig, processEnv);
@@ -85633,6 +86622,24 @@ function runtimeEventToActivities(event, taskTitle) {
85633
86622
  turnId: toTurnId$1(event.turnId) ?? null,
85634
86623
  ...maybeSequence
85635
86624
  }];
86625
+ case "code.published": {
86626
+ const label = event.payload.repo ? event.payload.identifier ? `${event.payload.repo}#${event.payload.identifier}` : event.payload.repo : event.payload.url;
86627
+ return [{
86628
+ id: event.eventId,
86629
+ createdAt: event.createdAt,
86630
+ tone: "info",
86631
+ kind: "code.published",
86632
+ summary: `Published ${label}`,
86633
+ payload: {
86634
+ hostingProvider: event.payload.hostingProvider,
86635
+ url: event.payload.url,
86636
+ ...event.payload.repo ? { repo: event.payload.repo } : {},
86637
+ ...event.payload.identifier ? { identifier: event.payload.identifier } : {}
86638
+ },
86639
+ turnId: toTurnId$1(event.turnId) ?? null,
86640
+ ...maybeSequence
86641
+ }];
86642
+ }
85636
86643
  case "turn.plan.updated": return [{
85637
86644
  id: event.eventId,
85638
86645
  createdAt: event.createdAt,
@@ -88017,7 +89024,7 @@ const ObservabilityLive = Layer.unwrap(Effect.gen(function* () {
88017
89024
  sink,
88018
89025
  ...delegate ? { delegate } : {}
88019
89026
  });
88020
- return Layer.mergeAll(Layer.succeed(Tracer.Tracer, tracer), layer$40(sink));
89027
+ return Layer.mergeAll(Layer.succeed(Tracer.Tracer, tracer), layer$41(sink));
88021
89028
  })).pipe(Layer.provide(OtlpExporter.layerFlusher), Layer.provideMerge(otlpSerializationLayer));
88022
89029
  const metricsLayer = config.otlpMetricsUrl === void 0 ? Layer.empty : OtlpMetrics.layer({
88023
89030
  url: config.otlpMetricsUrl,
@@ -88296,25 +89303,25 @@ const ReactorLayerLive = Layer.empty.pipe(Layer.provideMerge(OrchestrationReacto
88296
89303
  const ProviderSessionDirectoryLayerLive = ProviderSessionDirectoryLive.pipe(Layer.provide(layer$3));
88297
89304
  const ProviderLayerLive = ProviderServiceLive.pipe(Layer.provide(ProviderAdapterRegistryLive), Layer.provideMerge(ProviderSessionDirectoryLayerLive));
88298
89305
  const PersistenceLayerLive = Layer.empty.pipe(Layer.provideMerge(layerConfig));
88299
- const VcsDriverRegistryLayerLive = layer$36.pipe(Layer.provide(layer$37));
88300
- const SourceControlProviderRegistryLayerLive = layer$14.pipe(Layer.provide(Layer.mergeAll(layer$22, layer$20, layer$18, layer$16)), Layer.provideMerge(layer$38), Layer.provideMerge(VcsDriverRegistryLayerLive));
88301
- const GitManagerLayerLive = layer$13.pipe(Layer.provideMerge(layer$23), Layer.provideMerge(layer$38), Layer.provideMerge(SourceControlProviderRegistryLayerLive), Layer.provideMerge(layer$32));
88302
- const GitLayerLive = Layer.empty.pipe(Layer.provideMerge(GitManagerLayerLive), Layer.provideMerge(layer$38));
89306
+ const VcsDriverRegistryLayerLive = layer$37.pipe(Layer.provide(layer$38));
89307
+ const SourceControlProviderRegistryLayerLive = layer$14.pipe(Layer.provide(Layer.mergeAll(layer$22, layer$20, layer$18, layer$16)), Layer.provideMerge(layer$39), Layer.provideMerge(VcsDriverRegistryLayerLive));
89308
+ const GitManagerLayerLive = layer$13.pipe(Layer.provideMerge(layer$23), Layer.provideMerge(layer$39), Layer.provideMerge(SourceControlProviderRegistryLayerLive), Layer.provideMerge(layer$33));
89309
+ const GitLayerLive = Layer.empty.pipe(Layer.provideMerge(GitManagerLayerLive), Layer.provideMerge(layer$39));
88303
89310
  const GitWorkflowLayerLive = layer$12.pipe(Layer.provideMerge(VcsDriverRegistryLayerLive), Layer.provideMerge(GitLayerLive));
88304
- const SourceControlRepositoryServiceLayerLive = layer$4.pipe(Layer.provideMerge(layer$38), Layer.provideMerge(SourceControlProviderRegistryLayerLive));
88305
- const ReviewLayerLive = layer$9.pipe(Layer.provideMerge(layer$38), Layer.provideMerge(VcsDriverRegistryLayerLive));
88306
- const VcsLayerLive = Layer.empty.pipe(Layer.provideMerge(layer$37), Layer.provideMerge(VcsDriverRegistryLayerLive), Layer.provideMerge(layer$10.pipe(Layer.provide(VcsDriverRegistryLayerLive))), Layer.provideMerge(GitWorkflowLayerLive), Layer.provideMerge(ReviewLayerLive), Layer.provideMerge(SourceControlRepositoryServiceLayerLive), Layer.provideMerge(layer$11.pipe(Layer.provide(GitWorkflowLayerLive))));
88307
- const CheckpointingLayerLive = Layer.empty.pipe(Layer.provideMerge(layer$34), Layer.provideMerge(layer$35.pipe(Layer.provide(VcsDriverRegistryLayerLive))));
88308
- const PortScannerLayerLive = layer$30.pipe(Layer.provide(layer$53));
89311
+ const SourceControlRepositoryServiceLayerLive = layer$4.pipe(Layer.provideMerge(layer$39), Layer.provideMerge(SourceControlProviderRegistryLayerLive));
89312
+ const ReviewLayerLive = layer$9.pipe(Layer.provideMerge(layer$39), Layer.provideMerge(VcsDriverRegistryLayerLive));
89313
+ const VcsLayerLive = Layer.empty.pipe(Layer.provideMerge(layer$38), Layer.provideMerge(VcsDriverRegistryLayerLive), Layer.provideMerge(layer$10.pipe(Layer.provide(VcsDriverRegistryLayerLive))), Layer.provideMerge(GitWorkflowLayerLive), Layer.provideMerge(ReviewLayerLive), Layer.provideMerge(SourceControlRepositoryServiceLayerLive), Layer.provideMerge(layer$11.pipe(Layer.provide(GitWorkflowLayerLive))));
89314
+ const CheckpointingLayerLive = Layer.empty.pipe(Layer.provideMerge(layer$35), Layer.provideMerge(layer$36.pipe(Layer.provide(VcsDriverRegistryLayerLive))));
89315
+ const PortScannerLayerLive = layer$30.pipe(Layer.provide(layer$54));
88309
89316
  const TerminalLayerLive = layer$29.pipe(Layer.provide(PtyAdapterLive), Layer.provide(PortScannerLayerLive));
88310
89317
  const PreviewLayerLive = Layer.empty.pipe(Layer.provideMerge(layer$27), Layer.provideMerge(PortScannerLayerLive));
88311
- const WorkspaceEntriesLayerLive = layer$25.pipe(Layer.provide(layer$43));
88312
- const WorkspaceFileSystemLayerLive = layer$24.pipe(Layer.provide(layer$43), Layer.provide(WorkspaceEntriesLayerLive));
88313
- const WorkspaceLayerLive = Layer.mergeAll(layer$43, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
88314
- const ProjectFaviconResolverLayerLive = layer$41.pipe(Layer.provide(layer$43), Layer.provide(layer$42));
88315
- const AuthLayerLive = layer$63.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$67));
89318
+ const WorkspaceEntriesLayerLive = layer$25.pipe(Layer.provide(layer$44));
89319
+ const WorkspaceFileSystemLayerLive = layer$24.pipe(Layer.provide(layer$44), Layer.provide(WorkspaceEntriesLayerLive));
89320
+ const WorkspaceLayerLive = Layer.mergeAll(layer$44, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
89321
+ const ProjectFaviconResolverLayerLive = layer$42.pipe(Layer.provide(layer$44), Layer.provide(layer$43));
89322
+ const AuthLayerLive = layer$64.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$68));
88316
89323
  const ProviderRuntimeLayerLive = ProviderSessionReaperLive.pipe(Layer.provideMerge(ProviderLayerLive), Layer.provideMerge(OrchestrationLayerLive));
88317
- 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$60.pipe(Layer.provide(layer$67))), 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$59))), Layer.provideMerge(layer$67)).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$71));
89324
+ 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$52), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$61.pipe(Layer.provide(layer$68))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$53), Layer.provideMerge(layer$46), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(layer$31.pipe(Layer.provideMerge(Layer.mergeAll(layer$58, layer$32).pipe(Layer.provideMerge(layer$59), Layer.provideMerge(layer$60))))), Layer.provideMerge(layer$68)).pipe(Layer.provideMerge(layer$8), Layer.provideMerge(layer$7), Layer.provideMerge(layer$6), Layer.provideMerge(layer$49), Layer.provideMerge(layer$51), Layer.provideMerge(layer$50), Layer.provide(layer$72));
88318
89325
  /**
88319
89326
  * Hub asset sync.
88320
89327
  *
@@ -88322,9 +89329,9 @@ const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(Checkpo
88322
89329
  * not-configured shape, and every call reports that rather than failing — so
88323
89330
  * this costs a server with no hub nothing but a layer.
88324
89331
  */
88325
- const AssetSyncLive = layer$54.pipe(Layer.provideMerge(layer$55), Layer.provideMerge(layer$56));
88326
- const RuntimeServicesLive = layer$44.pipe(Layer.provideMerge(AssetSyncLive), Layer.provideMerge(RuntimeDependenciesLive));
88327
- 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));
89332
+ const AssetSyncLive = layer$55.pipe(Layer.provideMerge(layer$56), Layer.provideMerge(layer$57));
89333
+ const RuntimeServicesLive = layer$45.pipe(Layer.provideMerge(AssetSyncLive), Layer.provideMerge(RuntimeDependenciesLive));
89334
+ 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$43), Layer.provide(layer$28), Layer.provide(layer$47), Layer.provide(browserApiCorsLayer), Layer.provide(httpCompressionLayer));
88328
89335
  const makeServerLayer = Layer.unwrap(Effect.gen(function* () {
88329
89336
  const config = yield* ServerConfig$1;
88330
89337
  yield* fixPath();
@@ -88367,7 +89374,7 @@ const makeServerLayer = Layer.unwrap(Effect.gen(function* () {
88367
89374
  cause,
88368
89375
  servePort: configured.servePort
88369
89376
  }))) : Effect.void)) : Layer.empty;
88370
- return Layer.mergeAll(HttpRouter.serve(makeRoutesLayer, { disableLogger: !config.logWebSocketEvents }), httpListeningLayer, runtimeStateLayer, tailscaleServeLayer).pipe(Layer.provideMerge(RuntimeServicesLive), Layer.provideMerge(HttpResponseCompressionLive), Layer.provideMerge(HttpServerLive), Layer.provide(ObservabilityLive), Layer.provideMerge(FetchHttpClient.layer), Layer.provideMerge(layer$39), Layer.provideMerge(PlatformServicesLive));
89377
+ return Layer.mergeAll(HttpRouter.serve(makeRoutesLayer, { disableLogger: !config.logWebSocketEvents }), httpListeningLayer, runtimeStateLayer, tailscaleServeLayer).pipe(Layer.provideMerge(RuntimeServicesLive), Layer.provideMerge(HttpResponseCompressionLive), Layer.provideMerge(HttpServerLive), Layer.provide(ObservabilityLive), Layer.provideMerge(FetchHttpClient.layer), Layer.provideMerge(layer$40), Layer.provideMerge(PlatformServicesLive));
88371
89378
  }));
88372
89379
  const runServer = Layer.launch(makeServerLayer);
88373
89380
  //#endregion
@@ -88529,11 +89536,11 @@ const reconcileWithNewBinary = Effect.fn("cli.selfUpdate.reconcileWithNewBinary"
88529
89536
  //#endregion
88530
89537
  //#region src/cli/service.ts
88531
89538
  const isBootServiceCommandError = Schema$1.is(BootServiceCommandError);
88532
- const bootServiceLayer = (config) => layer$47({
89539
+ const bootServiceLayer = (config) => layer$48({
88533
89540
  baseDir: config.baseDir,
88534
89541
  logsDir: config.logsDir,
88535
89542
  cliVersion: version
88536
- }).pipe(Layer.provide(layer$53));
89543
+ }).pipe(Layer.provide(layer$54));
88537
89544
  /** Install, update, or repair the service using the CLI version running this command. */
88538
89545
  const reconcileService = Effect.fn("cli.service.reconcile")(function* () {
88539
89546
  const service = yield* BootService;
@@ -88580,7 +89587,7 @@ const runServiceCommand = Effect.fn("cli.service.run")(function* (flags, run, pr
88580
89587
  const baseDirNotice = yield* describeNonDefaultBaseDir(config.baseDir);
88581
89588
  if (baseDirNotice !== null) yield* Console.error(baseDirNotice);
88582
89589
  if (prepare !== void 0) yield* prepare(config);
88583
- return yield* run.pipe(Effect.tapError((error) => isBootServiceCommandError(error) ? BootService.pipe(Effect.flatMap((service) => Console.error(`Details: ${service.logPath}`))) : Effect.void), Effect.provide(Layer.mergeAll(bootServiceLayer(config), layer$53)));
89590
+ return yield* run.pipe(Effect.tapError((error) => isBootServiceCommandError(error) ? BootService.pipe(Effect.flatMap((service) => Console.error(`Details: ${service.logPath}`))) : Effect.void), Effect.provide(Layer.mergeAll(bootServiceLayer(config), layer$54)));
88584
89591
  });
88585
89592
  /**
88586
89593
  * Options the installed unit carries, rather than ones this command consumes.
@@ -88734,7 +89741,7 @@ const serviceCommand = Command.make("service").pipe(Command.withDescription("Man
88734
89741
  ]));
88735
89742
  //#endregion
88736
89743
  //#region src/bin.ts
88737
- const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$71, FetchHttpClient.layer);
89744
+ const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$72, FetchHttpClient.layer);
88738
89745
  /**
88739
89746
  * Prints an update reminder before dispatching, at most once a day.
88740
89747
  *
@@ -88764,6 +89771,7 @@ const reportUpdateAvailable = Effect.gen(function* () {
88764
89771
  const makeCli = () => Command.make("p4c", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the P4Code server."), Command.withHandler((flags) => runServerCommand(flags)), Command.withSubcommands([
88765
89772
  startCommand,
88766
89773
  serveCommand,
89774
+ agentsCommand,
88767
89775
  authCommand,
88768
89776
  hubCommand,
88769
89777
  mcpCommand,