@p4code/cli 0.1.17 → 0.1.18

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 +781 -260
  2. package/dist/client/assets/{DiffPanel-BrM3QqVj.js → DiffPanel-CP_cM9m0.js} +2 -2
  3. package/dist/client/assets/{FilePreviewPanel-DXk_4dj4.js → FilePreviewPanel-CLho0PPu.js} +2 -2
  4. package/dist/client/assets/{PreviewPanel-DfngnmUt.js → PreviewPanel-DvnUpMQv.js} +2 -2
  5. package/dist/client/assets/{angular-html-veA1EpVJ.js → angular-html-CEMakyzJ.js} +2 -2
  6. package/dist/client/assets/{angular-ts-CjhObYqk.js → angular-ts-YXZHq2uz.js} +2 -2
  7. package/dist/client/assets/{apl-BaBqmioI.js → apl-BTa85Bsd.js} +2 -2
  8. package/dist/client/assets/arrow-right-Bi8kADrV.js +2 -0
  9. package/dist/client/assets/{astro-aZep3bUH.js → astro-DIbti84c.js} +2 -2
  10. package/dist/client/assets/{blade-B-PS40R8.js → blade-CKAdvdwG.js} +2 -2
  11. package/dist/client/assets/{c-Da6G9SJL.js → c-DW17xzCK.js} +2 -2
  12. package/dist/client/assets/{cobol-MFD_Q4Xa.js → cobol-DXiicIcZ.js} +2 -2
  13. package/dist/client/assets/{coffee-DOKaGG2H.js → coffee-9Pdmmdn2.js} +2 -2
  14. package/dist/client/assets/{cpp-iD8OoA0X.js → cpp-bm3vFVmW.js} +2 -2
  15. package/dist/client/assets/{crystal-BgCh46iL.js → crystal-DbxZDZf9.js} +2 -2
  16. package/dist/client/assets/{css-C0AvK1Fp.js → css-BEBnbxDe.js} +2 -2
  17. package/dist/client/assets/{edge-DWC_9CO2.js → edge-K97wVEy_.js} +2 -2
  18. package/dist/client/assets/{elixir-5ahlHJpU.js → elixir-Rc2JDLLD.js} +2 -2
  19. package/dist/client/assets/{elm-B4b4KSk0.js → elm-QsA8aREL.js} +2 -2
  20. package/dist/client/assets/{erb-CNRZFKcV.js → erb-CliRJz4v.js} +2 -2
  21. package/dist/client/assets/{fileCommentAnnotations-V3SOLaB4.js → fileCommentAnnotations-DtIPA0Fm.js} +2 -2
  22. package/dist/client/assets/{git-rebase-AbfQBerd.js → git-rebase-Da_Wuhd_.js} +2 -2
  23. package/dist/client/assets/{glimmer-js-_dx97vsR.js → glimmer-js-XPbTw3tC.js} +2 -2
  24. package/dist/client/assets/{glimmer-ts-BFmDDwi5.js → glimmer-ts-BtK3NOkH.js} +2 -2
  25. package/dist/client/assets/{glsl-CXroyNbj.js → glsl-DqEtJptk.js} +2 -2
  26. package/dist/client/assets/{graphql-RU5TF49j.js → graphql-BSRTeXs0.js} +2 -2
  27. package/dist/client/assets/{hack-S6OyQkI3.js → hack-Dw9HaNHZ.js} +2 -2
  28. package/dist/client/assets/{haml-CclbQxsV.js → haml-BBNabTlb.js} +2 -2
  29. package/dist/client/assets/{handlebars-DFD506ku.js → handlebars-BgXzhWSj.js} +2 -2
  30. package/dist/client/assets/{html-derivative-oHJ5ggVN.js → html-derivative-CANLUZ-z.js} +2 -2
  31. package/dist/client/assets/{html-BtV0fnqE.js → html-wBdSHoow.js} +2 -2
  32. package/dist/client/assets/{http-C8JeRqCn.js → http-BY44QKMy.js} +2 -2
  33. package/dist/client/assets/{hurl-C1cew6hc.js → hurl-EMZQkkjM.js} +2 -2
  34. package/dist/client/assets/{index-5pO5drbg.js → index-kwzTYbVb.js} +191 -184
  35. package/dist/client/assets/{java-CrUEPeR7.js → java-ByjPbT42.js} +2 -2
  36. package/dist/client/assets/{javascript-DJT7psEs.js → javascript-C9vbUEcm.js} +2 -2
  37. package/dist/client/assets/{jinja-CXxzh_AI.js → jinja-DH-AbsBN.js} +2 -2
  38. package/dist/client/assets/{jison-BEgWlYuP.js → jison-Bef1Ntr_.js} +2 -2
  39. package/dist/client/assets/{json-DYMLm7oI.js → json-Gk_oM0bP.js} +2 -2
  40. package/dist/client/assets/{jsx-C2i-6RBR.js → jsx-Ds7YHLou.js} +2 -2
  41. package/dist/client/assets/{julia-268B5DSC.js → julia-Dm-6xpIK.js} +2 -2
  42. package/dist/client/assets/{just-DHlfSR_g.js → just-BXY93QN4.js} +2 -2
  43. package/dist/client/assets/{latex-hZJ97DlU.js → latex-B3-eYkFA.js} +2 -2
  44. package/dist/client/assets/{liquid-BXWKgP9a.js → liquid-C10OBLnm.js} +2 -2
  45. package/dist/client/assets/{lua-DZpMPVk1.js → lua-BUR8bsxC.js} +2 -2
  46. package/dist/client/assets/{marko-C4krJn5M.js → marko-COPLM_fz.js} +2 -2
  47. package/dist/client/assets/{mdc-DPMybKM9.js → mdc-CTAUTBsz.js} +2 -2
  48. package/dist/client/assets/{nginx-DS7EZxTP.js → nginx-D1BLqLRM.js} +2 -2
  49. package/dist/client/assets/{nim-DQ5EIgDG.js → nim-Xs8VbhdY.js} +2 -2
  50. package/dist/client/assets/{perl-BYRfkLcm.js → perl-D7-zmFa1.js} +2 -2
  51. package/dist/client/assets/{php-CRmJir-2.js → php-BiTVgoaL.js} +2 -2
  52. package/dist/client/assets/{pug-ChlH277L.js → pug-DphfHoFa.js} +2 -2
  53. package/dist/client/assets/{qml-D03lc83e.js → qml-CYcNnFOd.js} +2 -2
  54. package/dist/client/assets/{r-B0iWOCwM.js → r-BrJ-p0TS.js} +2 -2
  55. package/dist/client/assets/{razor-jqTSj5Nf.js → razor-1gio6m_g.js} +2 -2
  56. package/dist/client/assets/{regexp-BtdW0BtX.js → regexp-CNiuKup7.js} +2 -2
  57. package/dist/client/assets/{rst-ODlKAfCn.js → rst-BPOsO8_U.js} +2 -2
  58. package/dist/client/assets/{ruby-D3-MIdGC.js → ruby-CpfKHKDc.js} +2 -2
  59. package/dist/client/assets/{sas-CHM8NNaC.js → sas-CngX1we2.js} +2 -2
  60. package/dist/client/assets/{scss-C5u0ylVF.js → scss-B2remLQX.js} +2 -2
  61. package/dist/client/assets/{shellscript-e38vxsyD.js → shellscript-CQmxALRY.js} +2 -2
  62. package/dist/client/assets/{shellsession-C2xN505I.js → shellsession-BB-5CB_r.js} +2 -2
  63. package/dist/client/assets/{soy-Cm15Zvq3.js → soy-kYUnX51I.js} +2 -2
  64. package/dist/client/assets/{sql-BpFtjTfW.js → sql-Cq9fRkwr.js} +2 -2
  65. package/dist/client/assets/{stata-BlPPmFYy.js → stata-17nboilR.js} +2 -2
  66. package/dist/client/assets/{surrealql-BCULE51k.js → surrealql-D1hrcsSn.js} +2 -2
  67. package/dist/client/assets/{svelte-BNRSv2pn.js → svelte-GmGi3eK8.js} +2 -2
  68. package/dist/client/assets/{templ-DoGq1v8F.js → templ-DySdTLEs.js} +2 -2
  69. package/dist/client/assets/{tex--cpmM4NW.js → tex-CblcFqPi.js} +2 -2
  70. package/dist/client/assets/textarea-Cf56oU_6.js +46 -0
  71. package/dist/client/assets/{ts-tags-D5eCylEK.js → ts-tags-BQC5pzJI.js} +2 -2
  72. package/dist/client/assets/{tsx-Bu8K9CNP.js → tsx-1bNjXgiV.js} +2 -2
  73. package/dist/client/assets/{twig-BZHf1w2v.js → twig-CNwthYXl.js} +2 -2
  74. package/dist/client/assets/{typescript-CdzN72UO.js → typescript-BTKoaKmx.js} +2 -2
  75. package/dist/client/assets/{vue-BZJs2XXT.js → vue-B4XpWxge.js} +2 -2
  76. package/dist/client/assets/{vue-html-zt0y5fO9.js → vue-html-CcUSqexV.js} +2 -2
  77. package/dist/client/assets/{vue-vine-SsQX7Kll.js → vue-vine-JDMd26pY.js} +2 -2
  78. package/dist/client/assets/{xml-BKlTVdhA.js → xml-DRuNSJP8.js} +2 -2
  79. package/dist/client/assets/{xsl-B00O3UQ-.js → xsl-BIJJg1B0.js} +2 -2
  80. package/dist/client/assets/{yaml-Dr4fa503.js → yaml-BNEnOsJI.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
@@ -233,7 +233,7 @@ const make$75 = () => {
233
233
  const layer$71 = Layer.sync(NetService, make$75);
234
234
  //#endregion
235
235
  //#region package.json
236
- var version = "0.1.17";
236
+ var version = "0.1.18";
237
237
  //#endregion
238
238
  //#region src/config.ts
239
239
  /**
@@ -4773,6 +4773,7 @@ Schema$1.Literals([
4773
4773
  "config.warning",
4774
4774
  "deprecation.notice",
4775
4775
  "files.persisted",
4776
+ "code.published",
4776
4777
  "runtime.warning",
4777
4778
  "runtime.error"
4778
4779
  ]);
@@ -4821,6 +4822,7 @@ const ModelReroutedType = Schema$1.Literal("model.rerouted");
4821
4822
  const ConfigWarningType = Schema$1.Literal("config.warning");
4822
4823
  const DeprecationNoticeType = Schema$1.Literal("deprecation.notice");
4823
4824
  const FilesPersistedType = Schema$1.Literal("files.persisted");
4825
+ const CodePublishedType = Schema$1.Literal("code.published");
4824
4826
  const ToolDeniedType = Schema$1.Literal("tool.denied");
4825
4827
  const RuntimeWarningType = Schema$1.Literal("runtime.warning");
4826
4828
  const RuntimeErrorType = Schema$1.Literal("runtime.error");
@@ -5123,6 +5125,12 @@ const FilesPersistedPayload = Schema$1.Struct({
5123
5125
  error: TrimmedNonEmptyStringSchema$1
5124
5126
  })))
5125
5127
  });
5128
+ const CodePublishedPayload = Schema$1.Struct({
5129
+ hostingProvider: TrimmedNonEmptyStringSchema$1,
5130
+ url: TrimmedNonEmptyStringSchema$1,
5131
+ repo: Schema$1.optional(TrimmedNonEmptyStringSchema$1),
5132
+ identifier: Schema$1.optional(TrimmedNonEmptyStringSchema$1)
5133
+ });
5126
5134
  const ToolDeniedPayload = Schema$1.Struct({
5127
5135
  toolName: TrimmedNonEmptyStringSchema$1,
5128
5136
  toolUseId: Schema$1.optional(TrimmedNonEmptyStringSchema$1),
@@ -5363,6 +5371,11 @@ const ProviderRuntimeFilesPersistedEvent = Schema$1.Struct({
5363
5371
  type: FilesPersistedType,
5364
5372
  payload: FilesPersistedPayload
5365
5373
  });
5374
+ const ProviderRuntimeCodePublishedEvent = Schema$1.Struct({
5375
+ ...ProviderRuntimeEventBase.fields,
5376
+ type: CodePublishedType,
5377
+ payload: CodePublishedPayload
5378
+ });
5366
5379
  const ProviderRuntimeToolDeniedEvent = Schema$1.Struct({
5367
5380
  ...ProviderRuntimeEventBase.fields,
5368
5381
  type: ToolDeniedType,
@@ -5424,6 +5437,7 @@ Schema$1.Union([
5424
5437
  ProviderRuntimeConfigWarningEvent,
5425
5438
  ProviderRuntimeDeprecationNoticeEvent,
5426
5439
  ProviderRuntimeFilesPersistedEvent,
5440
+ ProviderRuntimeCodePublishedEvent,
5427
5441
  ProviderRuntimeToolDeniedEvent,
5428
5442
  ProviderRuntimeWarningEvent,
5429
5443
  ProviderRuntimeErrorEvent
@@ -5795,6 +5809,20 @@ const ServerSettings = Schema$1.Struct({
5795
5809
  * back for sessions started by the Claude provider.
5796
5810
  */
5797
5811
  enableToolCallNarration: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
5812
+ /**
5813
+ * Whether the model may spawn subagents without being asked to.
5814
+ *
5815
+ * On by default: a bounded search or review whose result is far smaller than
5816
+ * the reading it takes to produce is exactly the work a subagent is for, and
5817
+ * an agent that has to ask first will simply do it inline instead.
5818
+ *
5819
+ * Stated in the appended prompt whichever way it is set, rather than only
5820
+ * when off. The provider's own preset prompt carries an instruction not to
5821
+ * spawn subagents unless asked, and p4code requests that preset wholesale —
5822
+ * so there is nothing to withhold, and only a later, more specific
5823
+ * instruction can override an earlier general one.
5824
+ */
5825
+ enableUnpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
5798
5826
  automaticGitFetchInterval: Schema$1.DurationFromMillis.pipe(Schema$1.withDecodingDefault(Effect.succeed(Duration.toMillis(DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL)))),
5799
5827
  defaultThreadEnvMode: ThreadEnvMode.pipe(Schema$1.withDecodingDefault(Effect.succeed("local"))),
5800
5828
  newWorktreesStartFromOrigin: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
@@ -5929,6 +5957,7 @@ const ServerSettingsPatch = Schema$1.Struct({
5929
5957
  enableAssistantStreaming: Schema$1.optionalKey(Schema$1.Boolean),
5930
5958
  enableProviderUpdateChecks: Schema$1.optionalKey(Schema$1.Boolean),
5931
5959
  enableToolCallNarration: Schema$1.optionalKey(Schema$1.Boolean),
5960
+ enableUnpromptedSubagents: Schema$1.optionalKey(Schema$1.Boolean),
5932
5961
  automaticGitFetchInterval: Schema$1.optionalKey(Schema$1.DurationFromMillis),
5933
5962
  defaultThreadEnvMode: Schema$1.optionalKey(ThreadEnvMode),
5934
5963
  newWorktreesStartFromOrigin: Schema$1.optionalKey(Schema$1.Boolean),
@@ -7820,11 +7849,17 @@ const AGENT_ASSET_TOTAL_MAX_BYTES = 4 * 1024 * 1024;
7820
7849
  * directory rather than the provider's. The definition travels; the bearer
7821
7850
  * tokens and API keys it references do not, and stay in each machine's own
7822
7851
  * secret store.
7852
+ * - `agent` — a subagent definition, a single markdown file in
7853
+ * `<configDir>/agents`. The name is the filename, as for `memory`. This is the
7854
+ * only artifact that bounds what a subagent is handed and what it hands back,
7855
+ * because p4code observes subagent runs rather than constructing their
7856
+ * prompts — so the I/O contract can only ship as a file the provider reads.
7823
7857
  */
7824
7858
  const AgentAssetKind = Schema$1.Literals([
7825
7859
  "skill",
7826
7860
  "memory",
7827
- "mcp"
7861
+ "mcp",
7862
+ "agent"
7828
7863
  ]);
7829
7864
  /**
7830
7865
  * An asset's name, which for a skill is also its directory name.
@@ -15532,7 +15567,15 @@ function buildSkillOverrides(disabled, discovered) {
15532
15567
  * @module provider/Drivers/ClaudeSkills
15533
15568
  */
15534
15569
  const FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
15535
- function parseSkillFrontmatter(contents) {
15570
+ /**
15571
+ * The `name`/`description` header a skill and a subagent definition both carry.
15572
+ *
15573
+ * Exported because agent definitions are the same header in a different file,
15574
+ * and the sync validates them before publishing one. A second copy of this
15575
+ * would be a second answer to "does this load in the provider", which is the
15576
+ * one question both callers are asking.
15577
+ */
15578
+ function parseMarkdownFrontmatter(contents) {
15536
15579
  const match = FRONTMATTER_PATTERN.exec(contents);
15537
15580
  if (!match) return { kind: "missing" };
15538
15581
  let parsed;
@@ -15583,6 +15626,19 @@ const resolveClaudeUserSkillsDir = Effect.fn("resolveClaudeUserSkillsDir")(funct
15583
15626
  return path.join(configDirPath, "skills");
15584
15627
  });
15585
15628
  /**
15629
+ * The user-scope subagent directory the CLI would read.
15630
+ *
15631
+ * Same resolution as the skills directory beside it, for the same reason: the
15632
+ * sync materializes into exactly the directory the provider loads from, and
15633
+ * deriving it a second time is how the two come to disagree about where an
15634
+ * agent definition lives.
15635
+ */
15636
+ const resolveClaudeUserAgentsDir = Effect.fn("resolveClaudeUserAgentsDir")(function* (config, environment, cwd) {
15637
+ const path = yield* Path.Path;
15638
+ const configDirPath = yield* resolveClaudeConfigDirPath(config, environment ?? process.env, cwd);
15639
+ return path.join(configDirPath, "agents");
15640
+ });
15641
+ /**
15586
15642
  * Enumerate Claude Code skills from the user config dir and the workspace.
15587
15643
  * Discovery is best-effort: unreadable roots and malformed skill entries are
15588
15644
  * skipped so a broken skill never degrades the provider snapshot. On name
@@ -15607,7 +15663,7 @@ const discoverClaudeSkills = Effect.fn("discoverClaudeSkills")(function* (config
15607
15663
  const skillPath = path.join(root.directory, entry, "SKILL.md");
15608
15664
  const contents = yield* fileSystem.readFileString(skillPath).pipe(Effect.orElseSucceed(() => void 0));
15609
15665
  if (contents === void 0) continue;
15610
- const frontmatter = parseSkillFrontmatter(contents);
15666
+ const frontmatter = parseMarkdownFrontmatter(contents);
15611
15667
  if (frontmatter.kind === "malformed") continue;
15612
15668
  const name = (frontmatter.kind === "parsed" ? frontmatter.name : void 0) ?? entry.trim();
15613
15669
  if (!name) continue;
@@ -16429,6 +16485,288 @@ const make$62 = Effect.gen(function* () {
16429
16485
  });
16430
16486
  const layer$57 = Layer.effect(McpRegistry, make$62);
16431
16487
  //#endregion
16488
+ //#region src/sync/skillDirectory.ts
16489
+ /**
16490
+ * Reading and writing skill directories on disk.
16491
+ *
16492
+ * Kept apart from the sync itself so the filesystem shape has its own tests,
16493
+ * and so the rules about what is refusable — binaries, oversized files, deep
16494
+ * trees — live in one place rather than being rediscovered at each call site.
16495
+ *
16496
+ * @module sync/skillDirectory
16497
+ */
16498
+ /** Bounded so a stray symlink or a vendored `node_modules` cannot walk forever. */
16499
+ const MAX_SKILL_DEPTH = 4;
16500
+ /**
16501
+ * Entries that are never part of a skill, skipped rather than refused.
16502
+ *
16503
+ * The distinction matters. Refusing an asset because it cannot be represented
16504
+ * faithfully is right — a half-materialized skill loads and misbehaves. But a
16505
+ * `.DS_Store` is not part of the skill at all, and macOS drops one into any
16506
+ * directory that has been opened in Finder, so refusing on its account rejects
16507
+ * a perfectly good skill for a reason its author cannot see.
16508
+ *
16509
+ * These could not sync in any case: `AgentAssetFilePath` rejects a leading dot,
16510
+ * so a dotfile has no representable path even if it were text.
16511
+ */
16512
+ const IGNORED_ENTRIES = /* @__PURE__ */ new Set([
16513
+ ".DS_Store",
16514
+ "Thumbs.db",
16515
+ ".git",
16516
+ ".svn",
16517
+ ".hg",
16518
+ "node_modules",
16519
+ "__pycache__",
16520
+ ".venv",
16521
+ ".pytest_cache",
16522
+ ".ruff_cache",
16523
+ ".mypy_cache"
16524
+ ]);
16525
+ /**
16526
+ * Anything else with a leading dot is skipped too — an editor's `.idea`, a
16527
+ * `.gitignore` that belongs to the checkout rather than the skill. The rule is
16528
+ * the same one the path schema already enforces, applied earlier so it reads as
16529
+ * "not part of the skill" instead of "this skill is broken".
16530
+ */
16531
+ const isIgnoredEntry = (entry) => IGNORED_ENTRIES.has(entry) || entry.startsWith(".");
16532
+ /**
16533
+ * A file is binary if decoding it yields a NUL.
16534
+ *
16535
+ * Cheap, and right for what is being asked: the question is not "is this valid
16536
+ * UTF-8" but "can this round-trip through a JSON text column", and a NUL is
16537
+ * what actually breaks that on the way back out of SQLite.
16538
+ *
16539
+ * Exported so the single-file kinds ask it the same way this one does, rather
16540
+ * than each keeping a copy that could drift into a different answer.
16541
+ */
16542
+ const looksBinary$1 = (content) => content.includes("\0");
16543
+ /**
16544
+ * Read one directory as an asset's files, or say why it cannot be one.
16545
+ *
16546
+ * Exported because memory's `global-memory/` is the same shape by a different
16547
+ * name: a directory whose whole contents are the asset. Sharing this keeps the
16548
+ * refusal rules — depth, file count, binaries, size — identical for both,
16549
+ * rather than letting a second copy drift into accepting what the first
16550
+ * rejects.
16551
+ */
16552
+ const readSkillFiles = Effect.fn("skillDirectory.readSkillFiles")(function* (root) {
16553
+ const fileSystem = yield* FileSystem.FileSystem;
16554
+ const path = yield* Path.Path;
16555
+ const files = [];
16556
+ const walk = Effect.fn("skillDirectory.walk")(function* (directory, prefix, depth) {
16557
+ if (depth > MAX_SKILL_DEPTH) return "too-deep";
16558
+ const entries = yield* fileSystem.readDirectory(directory).pipe(Effect.orElseSucceed(() => []));
16559
+ for (const entry of [...entries].sort()) {
16560
+ if (isIgnoredEntry(entry)) continue;
16561
+ const absolute = path.join(directory, entry);
16562
+ const relative = prefix === "" ? entry : `${prefix}/${entry}`;
16563
+ const info = yield* fileSystem.stat(absolute).pipe(Effect.orElseSucceed(() => void 0));
16564
+ if (info === void 0) return "unreadable";
16565
+ if (info.type === "Directory") {
16566
+ const nested = yield* walk(absolute, relative, depth + 1);
16567
+ if (nested !== null) return nested;
16568
+ continue;
16569
+ }
16570
+ if (info.type !== "File") continue;
16571
+ if (files.length >= 512) return "too-many-files";
16572
+ const content = yield* fileSystem.readFileString(absolute).pipe(Effect.orElseSucceed(() => void 0));
16573
+ if (content === void 0) return "unreadable";
16574
+ if (looksBinary$1(content)) return "binary-file";
16575
+ if (content.length > 524288) return "file-too-large";
16576
+ files.push({
16577
+ path: relative,
16578
+ content
16579
+ });
16580
+ }
16581
+ return null;
16582
+ });
16583
+ const refusal = yield* walk(root, "", 1);
16584
+ if (refusal !== null) return { refusal };
16585
+ if (agentAssetTotalBytes(files) > 4194304) return { refusal: "asset-too-large" };
16586
+ return { files };
16587
+ });
16588
+ /**
16589
+ * Read every skill directory under a user-scope `skills/` root.
16590
+ *
16591
+ * A directory with no `SKILL.md` is not a skill and is skipped silently —
16592
+ * Claude Code would ignore it too, so reporting it would be noise about
16593
+ * something that was never a skill in the first place.
16594
+ */
16595
+ const readSkillDirectory = Effect.fn("skillDirectory.readSkillDirectory")(function* (skillsRoot) {
16596
+ const fileSystem = yield* FileSystem.FileSystem;
16597
+ const path = yield* Path.Path;
16598
+ const entries = yield* fileSystem.readDirectory(skillsRoot).pipe(Effect.orElseSucceed(() => []));
16599
+ const skills = [];
16600
+ const refused = [];
16601
+ for (const entry of [...entries].sort()) {
16602
+ if (isIgnoredEntry(entry)) continue;
16603
+ const directory = path.join(skillsRoot, entry);
16604
+ if ((yield* fileSystem.stat(directory).pipe(Effect.orElseSucceed(() => void 0)))?.type !== "Directory") continue;
16605
+ if (!(yield* fileSystem.exists(path.join(directory, "SKILL.md")).pipe(Effect.orElseSucceed(() => false)))) continue;
16606
+ const read = yield* readSkillFiles(directory);
16607
+ if ("refusal" in read) {
16608
+ refused.push({
16609
+ name: entry,
16610
+ reason: read.refusal
16611
+ });
16612
+ continue;
16613
+ }
16614
+ skills.push({
16615
+ name: entry,
16616
+ files: read.files
16617
+ });
16618
+ }
16619
+ return {
16620
+ skills,
16621
+ refused
16622
+ };
16623
+ });
16624
+ /**
16625
+ * Replace a skill directory with exactly the asset's files.
16626
+ *
16627
+ * Written to a sibling and then swapped, so a crash mid-write leaves the old
16628
+ * skill intact rather than half of the new one. A skill is loaded as a unit and
16629
+ * a partially written one is instructions with the middle missing, which an
16630
+ * agent will happily follow.
16631
+ */
16632
+ const writeSkillDirectory = Effect.fn("skillDirectory.writeSkillDirectory")(function* (input) {
16633
+ const fileSystem = yield* FileSystem.FileSystem;
16634
+ const path = yield* Path.Path;
16635
+ const target = path.join(input.skillsRoot, input.name);
16636
+ const staging = path.join(input.skillsRoot, `.${input.name}.p4code-staging`);
16637
+ yield* fileSystem.makeDirectory(input.skillsRoot, { recursive: true });
16638
+ yield* fileSystem.remove(staging, {
16639
+ recursive: true,
16640
+ force: true
16641
+ });
16642
+ yield* fileSystem.makeDirectory(staging, { recursive: true });
16643
+ for (const file of input.files) {
16644
+ const destination = path.join(staging, file.path);
16645
+ yield* fileSystem.makeDirectory(path.dirname(destination), { recursive: true });
16646
+ yield* fileSystem.writeFileString(destination, file.content);
16647
+ }
16648
+ yield* fileSystem.remove(target, {
16649
+ recursive: true,
16650
+ force: true
16651
+ });
16652
+ yield* fileSystem.rename(staging, target);
16653
+ });
16654
+ const removeSkillDirectory = Effect.fn("skillDirectory.removeSkillDirectory")(function* (input) {
16655
+ const fileSystem = yield* FileSystem.FileSystem;
16656
+ const path = yield* Path.Path;
16657
+ yield* fileSystem.remove(path.join(input.skillsRoot, input.name), {
16658
+ recursive: true,
16659
+ force: true
16660
+ });
16661
+ });
16662
+ /**
16663
+ * Whether this text is a definition the provider would actually load.
16664
+ *
16665
+ * Both fields, not just the header. `name` is how a subagent is addressed and
16666
+ * `description` is how the orchestrator decides to address it, so a definition
16667
+ * missing either is present on disk and unreachable in practice.
16668
+ */
16669
+ const isLoadableAgentDefinition = (content) => {
16670
+ const frontmatter = parseMarkdownFrontmatter(content);
16671
+ return frontmatter.kind === "parsed" && frontmatter.name !== void 0 && frontmatter.description !== void 0;
16672
+ };
16673
+ /**
16674
+ * Every definition in the directory, as syncable assets.
16675
+ *
16676
+ * A file that will not load is refused rather than skipped, unlike a stray
16677
+ * `.DS_Store`: a `.md` in this directory is meant to be an agent, so a broken
16678
+ * header is a fault worth reporting instead of noise to ignore.
16679
+ */
16680
+ const readAgentDefinitions = Effect.fn("agentDefinitionFiles.read")(function* (agentsRoot) {
16681
+ const fileSystem = yield* FileSystem.FileSystem;
16682
+ const path = yield* Path.Path;
16683
+ const entries = yield* fileSystem.readDirectory(agentsRoot).pipe(Effect.orElseSucceed(() => []));
16684
+ const skills = [];
16685
+ const refused = [];
16686
+ for (const entry of [...entries].sort()) {
16687
+ if (!entry.endsWith(".md") || entry.startsWith(".")) continue;
16688
+ const absolute = path.join(agentsRoot, entry);
16689
+ if ((yield* fileSystem.stat(absolute).pipe(Effect.orElseSucceed(() => void 0)))?.type !== "File") continue;
16690
+ const content = yield* fileSystem.readFileString(absolute).pipe(Effect.orElseSucceed(() => void 0));
16691
+ if (content === void 0) {
16692
+ refused.push({
16693
+ name: entry,
16694
+ reason: "unreadable"
16695
+ });
16696
+ continue;
16697
+ }
16698
+ if (looksBinary$1(content)) {
16699
+ refused.push({
16700
+ name: entry,
16701
+ reason: "binary-file"
16702
+ });
16703
+ continue;
16704
+ }
16705
+ if (content.length > 524288) {
16706
+ refused.push({
16707
+ name: entry,
16708
+ reason: "file-too-large"
16709
+ });
16710
+ continue;
16711
+ }
16712
+ if (!isLoadableAgentDefinition(content)) {
16713
+ refused.push({
16714
+ name: entry,
16715
+ reason: "unreadable"
16716
+ });
16717
+ continue;
16718
+ }
16719
+ const files = [{
16720
+ path: entry,
16721
+ content
16722
+ }];
16723
+ if (agentAssetTotalBytes(files) > 4194304) {
16724
+ refused.push({
16725
+ name: entry,
16726
+ reason: "asset-too-large"
16727
+ });
16728
+ continue;
16729
+ }
16730
+ skills.push({
16731
+ name: entry,
16732
+ files
16733
+ });
16734
+ }
16735
+ return {
16736
+ skills,
16737
+ refused
16738
+ };
16739
+ });
16740
+ /**
16741
+ * Write a definition that arrived from the hub.
16742
+ *
16743
+ * Refused unless it is exactly one markdown file whose path is the asset's own
16744
+ * name and whose frontmatter would load. The last check is the one that matters
16745
+ * across machines: it is what stops a hub row edited by hand from installing a
16746
+ * file the provider quietly ignores on every machine that syncs it.
16747
+ *
16748
+ * Written atomically because a definition is read whole when a subagent starts,
16749
+ * and half of one is instructions with the contract missing.
16750
+ */
16751
+ const writeAgentDefinition = Effect.fn("agentDefinitionFiles.write")(function* (input) {
16752
+ const fileSystem = yield* FileSystem.FileSystem;
16753
+ const path = yield* Path.Path;
16754
+ const file = input.files[0];
16755
+ if (!input.name.endsWith(".md") || input.files.length !== 1 || file === void 0 || file.path !== input.name) return "refused";
16756
+ if (!isLoadableAgentDefinition(file.content)) return "refused";
16757
+ yield* fileSystem.makeDirectory(input.agentsRoot, { recursive: true }).pipe(Effect.ignore);
16758
+ return yield* writeFileStringAtomically({
16759
+ filePath: path.join(input.agentsRoot, input.name),
16760
+ contents: file.content
16761
+ }).pipe(Effect.as("written"), Effect.orElseSucceed(() => "failed"));
16762
+ });
16763
+ const removeAgentDefinition = Effect.fn("agentDefinitionFiles.remove")(function* (input) {
16764
+ if (!input.name.endsWith(".md")) return;
16765
+ const fileSystem = yield* FileSystem.FileSystem;
16766
+ const path = yield* Path.Path;
16767
+ yield* fileSystem.remove(path.join(input.agentsRoot, input.name), { force: true });
16768
+ });
16769
+ //#endregion
16432
16770
  //#region src/sync/HubLink.ts
16433
16771
  /**
16434
16772
  * Where this server's hub is, what it authenticates with, and whether it syncs
@@ -16802,178 +17140,6 @@ const removeMcpRegistration = Effect.fn("mcpRegistrationFiles.remove")(function*
16802
17140
  yield* fileSystem.remove(path.join(input.registrationsRoot, input.name), { force: true });
16803
17141
  });
16804
17142
  //#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
17143
  //#region src/sync/memoryFile.ts
16978
17144
  /**
16979
17145
  * Reading and writing user-scope memory files.
@@ -17254,12 +17420,20 @@ const PROCEED = { _tag: "proceed" };
17254
17420
  * - A skill without `SKILL.md` would be invisible to discovery: the picker
17255
17421
  * skips directories without one, so the create would look like it silently
17256
17422
  * failed. Refused with the reason instead.
17423
+ * - An agent definition is one markdown file whose name is the asset's own, and
17424
+ * anything else is invisible in the same way — the provider reads `.md` in
17425
+ * that directory and nothing else. The frontmatter check is the writer's,
17426
+ * because it needs a YAML parse this module deliberately does without.
17257
17427
  * - An existing local name is never overwritten. Create means create; editing
17258
17428
  * is a different operation with a different surface.
17259
17429
  */
17260
17430
  function decideCreateLocal(input) {
17261
17431
  if (input.asset.kind === "mcp") return refuse("refused", "MCP registrations are created through mcp.save, not as files.");
17262
17432
  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.");
17433
+ if (input.asset.kind === "agent") {
17434
+ const [file, ...rest] = input.asset.files;
17435
+ 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'.");
17436
+ }
17263
17437
  if (input.localNames.has(input.asset.name)) return refuse("exists", `An asset named '${input.asset.name}' already exists locally.`);
17264
17438
  return PROCEED;
17265
17439
  }
@@ -17502,6 +17676,22 @@ const make$59 = Effect.gen(function* () {
17502
17676
  name
17503
17677
  }).pipe(Effect.ignore)
17504
17678
  },
17679
+ {
17680
+ kind: "agent",
17681
+ resolveRoot: Effect.gen(function* () {
17682
+ return yield* resolveClaudeUserAgentsDir(yield* claudeHome());
17683
+ }),
17684
+ read: (root) => readAgentDefinitions(root),
17685
+ write: ({ root, name, files }) => writeAgentDefinition({
17686
+ agentsRoot: root,
17687
+ name,
17688
+ files
17689
+ }),
17690
+ remove: ({ root, name }) => removeAgentDefinition({
17691
+ agentsRoot: root,
17692
+ name
17693
+ }).pipe(Effect.ignore)
17694
+ },
17505
17695
  {
17506
17696
  kind: "mcp",
17507
17697
  resolveRoot: Effect.succeed(path.join(config.stateDir, "mcp")),
@@ -18143,6 +18333,262 @@ const make$59 = Effect.gen(function* () {
18143
18333
  });
18144
18334
  const layer$54 = Layer.effect(AssetSync, make$59);
18145
18335
  //#endregion
18336
+ //#region src/provider/CompressPrompts.ts
18337
+ /**
18338
+ * Caveman-style response compression rulesets, adapted from the caveman
18339
+ * plugin's SKILL.md. Pre-baked per level so injection is a lookup, not a
18340
+ * runtime template. Applies to assistant prose only: code blocks, commits,
18341
+ * PRs, error strings, and safety-critical text stay uncompressed.
18342
+ */
18343
+ const COMPRESS_SHARED_RULES = `Respond terse. All technical substance stays. Only fluff dies.
18344
+
18345
+ ## Persistence
18346
+
18347
+ Active every response. No revert after many turns. No filler drift. Still active if unsure.
18348
+
18349
+ ## Rules
18350
+
18351
+ 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.
18352
+
18353
+ Never drop not/never/no/only/except: flipping the meaning costs more than any token saved. Numbers and units exact.
18354
+
18355
+ 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.
18356
+
18357
+ 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.
18358
+
18359
+ 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.
18360
+
18361
+ 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.
18362
+
18363
+ ## Auto-Clarity
18364
+
18365
+ Drop compression and write normally when:
18366
+ - Security warnings
18367
+ - Irreversible action confirmations
18368
+ - Multi-step sequences where fragment order or omitted conjunctions risk misread
18369
+ - Compression itself creates technical ambiguity (e.g. "migrate table drop column backup first" - order unclear without articles and conjunctions)
18370
+ - User asks to clarify or repeats a question
18371
+
18372
+ 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.
18373
+
18374
+ Example - destructive operation:
18375
+ > **Warning:** This will permanently delete all rows in the \`users\` table and cannot be undone.
18376
+ > \`\`\`sql
18377
+ > DROP TABLE users;
18378
+ > \`\`\`
18379
+ > Compression resumes. Verify a backup exists first.
18380
+
18381
+ ## Boundaries
18382
+
18383
+ 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.`;
18384
+ const COMPRESS_RULESETS = {
18385
+ lite: `## Response compression: lite
18386
+
18387
+ ${COMPRESS_SHARED_RULES}
18388
+
18389
+ ## Intensity: lite
18390
+
18391
+ No filler or hedging. Keep articles and full sentences. Professional but tight.
18392
+
18393
+ Example - "Why does my React component re-render?"
18394
+ "Your component re-renders because you create a new object reference each render. Wrap it in \`useMemo\`."
18395
+
18396
+ Example - "Explain database connection pooling."
18397
+ "Connection pooling reuses open connections instead of creating a new one per request. Avoids repeated handshake overhead."`,
18398
+ full: `## Response compression: full
18399
+
18400
+ ${COMPRESS_SHARED_RULES}
18401
+
18402
+ ## Intensity: full
18403
+
18404
+ Drop articles (a/an/the), fragments OK, short synonyms. Pattern: [thing] [action] [reason]. [next step].
18405
+
18406
+ Not: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..."
18407
+ Yes: "Bug in auth middleware. Token expiry check use \`<\` not \`<=\`. Fix:"
18408
+
18409
+ Example - "Why does my React component re-render?"
18410
+ "New object ref each render. Inline object prop = new ref = re-render. Wrap in \`useMemo\`."
18411
+
18412
+ Example - "Explain database connection pooling."
18413
+ "Pool reuses open DB connections. No new connection per request. Skips handshake overhead."`,
18414
+ ultra: `## Response compression: ultra
18415
+
18416
+ ${COMPRESS_SHARED_RULES}
18417
+
18418
+ ## Intensity: ultra
18419
+
18420
+ 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.
18421
+
18422
+ Example - "Why does my React component re-render?"
18423
+ "Inline object prop, new ref, re-render. \`useMemo\`."
18424
+
18425
+ Example - "Explain database connection pooling."
18426
+ "Pool reuses open DB connections. No per-request handshake."`
18427
+ };
18428
+ const COMPRESS_TURN_REMINDERS = {
18429
+ lite: "[response style: compressed lite - no filler/hedging, full sentences; keep negations and numbers exact; code/commits/security text normal]",
18430
+ full: "[response style: compressed full - terse fragments, drop articles/filler; keep negations and numbers exact; code/commits/security text normal]",
18431
+ ultra: "[response style: compressed ultra - maximum terseness, one word when enough; keep negations and numbers exact; code/commits/security text normal]"
18432
+ };
18433
+ function compressRulesetFor(mode) {
18434
+ return mode === "off" ? void 0 : COMPRESS_RULESETS[mode];
18435
+ }
18436
+ function compressTurnReminderFor(mode) {
18437
+ return mode === "off" ? void 0 : COMPRESS_TURN_REMINDERS[mode];
18438
+ }
18439
+ //#endregion
18440
+ //#region src/sync/agentPresets.ts
18441
+ /**
18442
+ * The row ceiling both presets impose.
18443
+ *
18444
+ * A cap rather than "be brief": an uncapped list is how a subagent turns a
18445
+ * bounded answer back into a page, and a number is the only form of that
18446
+ * instruction a model cannot negotiate with.
18447
+ */
18448
+ const PRESET_MAX_RESULT_ROWS = 20;
18449
+ /**
18450
+ * Named `p4-*` on purpose.
18451
+ *
18452
+ * The directory is shared with whatever the user has written by hand, and
18453
+ * installing a preset must never quietly replace their own `reviewer`. The
18454
+ * prefix also makes provenance obvious in the settings list, where every kind
18455
+ * of agent definition appears in one column.
18456
+ */
18457
+ const PRESET_NAME_PREFIX = "p4-";
18458
+ /**
18459
+ * What every preset says about what it is handed.
18460
+ *
18461
+ * Shared because it is the same contract whatever the subagent does: no
18462
+ * inherited history, exact paths, and a refusal instead of a guess. The last
18463
+ * clause is the one that earns its place — a subagent that invents the missing
18464
+ * half of an under-specified prompt returns something confident and wrong,
18465
+ * which costs more than the turn that asked for it.
18466
+ */
18467
+ const INPUT_CONTRACT = `## What you are given
18468
+
18469
+ You inherit no session history. The prompt is the whole of what you know: one
18470
+ request, plus whatever exact paths the orchestrator has already established.
18471
+ Treat those paths as authoritative and start from them rather than searching for
18472
+ them again.
18473
+
18474
+ What the prompt does not say, you do not assume. If it names no request, or
18475
+ names a path that is not there, answer with the BLOCKED line below. Guessing at
18476
+ the missing half of a request produces an answer that reads as confident and is
18477
+ not checkable, which is worse than no answer.`;
18478
+ /**
18479
+ * What every preset says about the answer.
18480
+ *
18481
+ * The two sentinels matter as much as the table. Without `NONE` a clean run
18482
+ * comes back as a paragraph explaining that everything looked fine; without
18483
+ * `BLOCKED` an under-specified request comes back as an apology. Both are the
18484
+ * narrative this contract exists to remove, and both are cheap to name.
18485
+ */
18486
+ const outputContract = (row, extra) => [
18487
+ "## What you return",
18488
+ "",
18489
+ "Your final message is the result. It is the table and nothing else: no",
18490
+ "preamble, no restatement of the request, no summary underneath it.",
18491
+ "",
18492
+ `One row per finding, at most ${PRESET_MAX_RESULT_ROWS}, most important first:`,
18493
+ "",
18494
+ ` ${row}`,
18495
+ "",
18496
+ "Nothing to report: the single word NONE.",
18497
+ "Request insufficient: BLOCKED - <what is missing>, on one line.",
18498
+ "",
18499
+ "Paths are repository-relative and carry a line number. A row without one is",
18500
+ "a row you have not verified: open the file and get it.",
18501
+ ...extra
18502
+ ].join("\n");
18503
+ /**
18504
+ * Assemble one definition.
18505
+ *
18506
+ * The compression ruleset goes last so the contract above it is read first.
18507
+ * Both are instructions to the same model and neither overrides the other:
18508
+ * the contract fixes the shape, the ruleset fixes the prose inside it.
18509
+ */
18510
+ const buildPreset = (input) => {
18511
+ const name = `${PRESET_NAME_PREFIX}${input.name}`;
18512
+ const content = [
18513
+ "---",
18514
+ `name: ${name}`,
18515
+ `description: ${JSON.stringify(input.description)}`,
18516
+ `tools: ${input.tools}`,
18517
+ "---",
18518
+ "",
18519
+ input.body.trim(),
18520
+ "",
18521
+ compressRulesetFor("full") ?? "",
18522
+ ""
18523
+ ].join("\n");
18524
+ const fileName = `${name}.md`;
18525
+ return {
18526
+ name: fileName,
18527
+ summary: input.summary,
18528
+ files: [{
18529
+ path: fileName,
18530
+ content
18531
+ }]
18532
+ };
18533
+ };
18534
+ /** Every preset, in the order `p4c agents presets` lists them. */
18535
+ const AGENT_PRESETS = [buildPreset({
18536
+ name: "investigator",
18537
+ 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.",
18538
+ tools: "Read, Grep, Glob, Bash",
18539
+ summary: "Locates code and answers with a path:line table.",
18540
+ body: `You locate code. You do not change it, and you say no more about any location
18541
+ than the one clause that explains why it is in the list.
18542
+
18543
+ ${INPUT_CONTRACT}
18544
+
18545
+ ${outputContract("path:line - symbol - one clause on why it matters", [
18546
+ "",
18547
+ "Symbol is the function, type or constant the line belongs to, not a paraphrase",
18548
+ "of the line. Where there is genuinely no symbol, name the block: a route table,",
18549
+ "a config literal."
18550
+ ])}`
18551
+ }), buildPreset({
18552
+ name: "reviewer",
18553
+ 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.",
18554
+ tools: "Read, Grep, Glob, Bash",
18555
+ summary: "Reviews named files and answers with a severity-tagged finding table.",
18556
+ body: `You review code you did not write. You do not change it, and you do not report
18557
+ what it does well - the orchestrator asked what is wrong, and a list padded with
18558
+ approval is a list that has to be re-read to find the three rows that matter.
18559
+
18560
+ ${INPUT_CONTRACT}
18561
+
18562
+ ${outputContract("path:line - severity - what is wrong, and what it breaks", [
18563
+ "",
18564
+ "Severity is one of three words:",
18565
+ "",
18566
+ " bug - wrong behaviour reachable in ordinary use. Name the input that reaches it.",
18567
+ " risk - correct today, and depends on something the code does not state.",
18568
+ " nit - style or clarity. Include only when the file is otherwise clean.",
18569
+ "",
18570
+ "A finding you cannot reach with a concrete input is a risk, not a bug. Saying",
18571
+ "so is what keeps the bug rows worth reading."
18572
+ ])}`
18573
+ })];
18574
+ /**
18575
+ * Look one up by the name someone typed.
18576
+ *
18577
+ * Both spellings are accepted - `investigator` and `p4-investigator.md` - because
18578
+ * the listing shows the filename and the sentence people write is the bare word,
18579
+ * and refusing one of the two would be refusing whichever the person happened to
18580
+ * read most recently.
18581
+ */
18582
+ const findAgentPreset = (requested) => {
18583
+ const wanted = requested.trim().toLowerCase();
18584
+ if (wanted.length === 0) return void 0;
18585
+ return AGENT_PRESETS.find((preset) => {
18586
+ const fileName = preset.name.toLowerCase();
18587
+ const bare = fileName.slice(3, -3);
18588
+ return wanted === fileName || wanted === bare || wanted === `${PRESET_NAME_PREFIX}${bare}`;
18589
+ });
18590
+ };
18591
+ //#endregion
18146
18592
  //#region src/cli/assets.ts
18147
18593
  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));
18148
18594
  const runWithSync = (flags, run) => Effect.gen(function* () {
@@ -18186,6 +18632,14 @@ const MCP_WORDS = {
18186
18632
  rootLabel: "MCP",
18187
18633
  nameDescription: "Registration filename, for example linear.json."
18188
18634
  };
18635
+ const AGENT_WORDS = {
18636
+ kind: "agent",
18637
+ command: "agents",
18638
+ singular: "agent definition",
18639
+ plural: "agent definitions",
18640
+ rootLabel: "Agents",
18641
+ nameDescription: "Definition filename, for example p4-reviewer.md."
18642
+ };
18189
18643
  const rootFor = (status, kind) => status.roots.find((root) => root.kind === kind)?.path ?? null;
18190
18644
  /**
18191
18645
  * Exported for tests. These two are the only place a wrong report becomes a
@@ -18248,7 +18702,8 @@ const formatReport = (report) => {
18248
18702
  const ALL_WORDS = [
18249
18703
  SKILL_WORDS,
18250
18704
  MEMORY_WORDS,
18251
- MCP_WORDS
18705
+ MCP_WORDS,
18706
+ AGENT_WORDS
18252
18707
  ];
18253
18708
  /**
18254
18709
  * The whole picture, in one page.
@@ -18380,6 +18835,67 @@ const makeAssetCommands = (words) => {
18380
18835
  const skillCommands = makeAssetCommands(SKILL_WORDS);
18381
18836
  const memoryCommands = makeAssetCommands(MEMORY_WORDS);
18382
18837
  const mcpCommands = makeAssetCommands(MCP_WORDS);
18838
+ const agentCommands = makeAssetCommands(AGENT_WORDS);
18839
+ /**
18840
+ * What one install attempt did, in a sentence.
18841
+ *
18842
+ * Exported for tests: `exists` in particular must never read as success. The
18843
+ * whole point of refusing to overwrite is that someone who has edited a preset
18844
+ * keeps their edits, and a line saying "installed" would tell them the opposite.
18845
+ */
18846
+ const formatPresetInstall = (input) => {
18847
+ if (input.outcome === "created") return `Installed ${input.name}${input.path === null ? "" : ` at ${input.path}`}`;
18848
+ if (input.outcome === "exists") return `Skipped ${input.name}: it already exists here, and installing would overwrite your copy.`;
18849
+ return `Could not install ${input.name}: ${input.detail ?? input.outcome}`;
18850
+ };
18851
+ /**
18852
+ * `p4c agents presets` — what is on offer, before anything is written.
18853
+ *
18854
+ * Its own command rather than a flag on `install`, because reading the list is
18855
+ * what people do first and a listing that requires naming an install target is
18856
+ * not a listing.
18857
+ */
18858
+ const presetsCommand = Command.make("presets", { ...authLocationFlags }).pipe(Command.withDescription("List the agent definitions p4code ships, without installing any."), Command.withHandler(() => Console.log([
18859
+ ...AGENT_PRESETS.map((preset) => ` ${preset.name.padEnd(28)} ${preset.summary}`),
18860
+ "",
18861
+ "Install one with: p4c agents install <name>"
18862
+ ].join("\n"))));
18863
+ /**
18864
+ * Install a shipped definition onto this machine, and only this machine.
18865
+ *
18866
+ * `createLocal` rather than a hub write, so an installed preset starts out
18867
+ * exactly like one written by hand: local, unmanaged, and shared only when
18868
+ * something explicitly publishes it. It also means this works with no hub
18869
+ * configured, which is when a fresh machine most wants it.
18870
+ */
18871
+ const presetInstallCommand = Command.make("install", {
18872
+ ...authLocationFlags,
18873
+ name: Argument.string("name").pipe(Argument.withDescription("Preset to install, for example investigator."), Argument.optional),
18874
+ all: Flag.boolean("all").pipe(Flag.withDescription("Install every preset p4code ships."))
18875
+ }).pipe(Command.withDescription("Install a p4code agent definition into this machine's agents directory."), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => Effect.gen(function* () {
18876
+ const requested = Option.getOrUndefined(flags.name);
18877
+ 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");
18878
+ const chosen = flags.all ? AGENT_PRESETS : (() => {
18879
+ const found = findAgentPreset(requested ?? "");
18880
+ return found === void 0 ? [] : [found];
18881
+ })();
18882
+ if (chosen.length === 0) return yield* Console.error(`No preset named ${requested ?? ""}. See them with: p4c agents presets`);
18883
+ const lines = [];
18884
+ for (const preset of chosen) {
18885
+ const result = yield* sync.createLocal({
18886
+ kind: AGENT_WORDS.kind,
18887
+ name: preset.name,
18888
+ files: preset.files
18889
+ });
18890
+ lines.push(formatPresetInstall({
18891
+ name: preset.name,
18892
+ outcome: result.outcome,
18893
+ path: result.path,
18894
+ detail: result.detail
18895
+ }));
18896
+ }
18897
+ return yield* Console.log(lines.join("\n"));
18898
+ }))));
18383
18899
  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
18900
  yield* link.setShareMode("all");
18385
18901
  yield* Console.log(formatStatus(yield* sync.status));
@@ -18432,6 +18948,23 @@ const mcpCommand = Command.make("mcp", {}).pipe(Command.withDescription("Share M
18432
18948
  mcpCommands.showCommand,
18433
18949
  mcpCommands.removeCommand
18434
18950
  ]));
18951
+ /**
18952
+ * `p4c agents` — the subagent definitions this machine runs.
18953
+ *
18954
+ * The same six subcommands every kind gets, plus the two that are particular to
18955
+ * this one: p4code ships definitions, and a definition is the only place a
18956
+ * subagent's input and output contract can be stated.
18957
+ */
18958
+ const agentsCommand = Command.make("agents", {}).pipe(Command.withDescription("Share subagent definitions across your machines, and install p4code's."), Command.withSubcommands([
18959
+ agentCommands.statusCommand,
18960
+ agentCommands.syncCommand,
18961
+ agentCommands.publishCommand,
18962
+ agentCommands.unpublishCommand,
18963
+ agentCommands.showCommand,
18964
+ agentCommands.removeCommand,
18965
+ presetsCommand,
18966
+ presetInstallCommand
18967
+ ]));
18435
18968
  /** Exported for the hub command, which owns `connect`/`disconnect`. */
18436
18969
  const hubLinkCommands = [hubConnectCommand, hubDisconnectCommand];
18437
18970
  //#endregion
@@ -49044,88 +49577,6 @@ function formatAskUserQuestionAnswers(answers) {
49044
49577
  return formatted;
49045
49578
  }
49046
49579
  //#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
49580
  //#region src/provider/Layers/ClaudeAdapter.ts
49130
49581
  /**
49131
49582
  * ClaudeAdapterLive - Scoped live implementation for the Claude Agent provider adapter.
@@ -49687,6 +50138,20 @@ const SUPPORTED_CLAUDE_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
49687
50138
  /** Emitted by the CLI but absent from the SDK's `SDKMessage` union. */
49688
50139
  const UNTYPED_COMMAND_LIFECYCLE_MESSAGE = "command_lifecycle";
49689
50140
  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.";
50141
+ /**
50142
+ * Said whichever way the setting is set, and said as an override.
50143
+ *
50144
+ * The preset system prompt this adapter requests already carries an
50145
+ * instruction not to spawn subagents unless the user asked — and a preset is
50146
+ * taken wholesale, so there is no version of it with that line removed. An
50147
+ * append that merely stayed silent when subagents are allowed would therefore
50148
+ * leave the restriction standing, which is the one outcome the setting exists
50149
+ * to change. Both texts name the earlier instruction they replace, because a
50150
+ * later instruction that does not acknowledge the conflict reads as an
50151
+ * accident rather than a decision.
50152
+ */
50153
+ 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.";
50154
+ 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
50155
  const CLAUDE_SETTING_SOURCES = [
49691
50156
  "user",
49692
50157
  "project",
@@ -49915,6 +50380,25 @@ function sdkNativeMethod(message) {
49915
50380
  * would only duplicate a refresh that has already happened.
49916
50381
  */
49917
50382
  const CONSUMED_UNDECLARED_SYSTEM_SUBTYPES = /* @__PURE__ */ new Set(["background_tasks_changed", "vcs_state_changed"]);
50383
+ /**
50384
+ * Another undeclared-but-real subtype, emitted once the CLI has pushed the
50385
+ * turn's changes to a hosting provider:
50386
+ * `{provider: "github", url: "https://github.com/org/repo/pull/96",
50387
+ * repo: "org/repo", identifier: "96"}`. Mapped to `code.published` so the
50388
+ * work log links the pull request instead of logging an unknown-subtype
50389
+ * warning.
50390
+ */
50391
+ const CODE_CHANGE_PUBLISHED_SUBTYPE = "code_change_published";
50392
+ function codePublishedPayloadFromSdkMessage(message) {
50393
+ const record = message;
50394
+ if (typeof record.url !== "string" || record.url.length === 0) return;
50395
+ return {
50396
+ hostingProvider: typeof record.provider === "string" && record.provider.length > 0 ? record.provider : "unknown",
50397
+ url: record.url,
50398
+ ...typeof record.repo === "string" && record.repo.length > 0 ? { repo: record.repo } : {},
50399
+ ...typeof record.identifier === "string" && record.identifier.length > 0 ? { identifier: record.identifier } : {}
50400
+ };
50401
+ }
49918
50402
  const SDK_MESSAGE_NOISE_KEYS = /* @__PURE__ */ new Set([
49919
50403
  "type",
49920
50404
  "subtype",
@@ -50886,6 +51370,17 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
50886
51370
  }
50887
51371
  };
50888
51372
  if (CONSUMED_UNDECLARED_SYSTEM_SUBTYPES.has(message.subtype)) return;
51373
+ if (message.subtype === CODE_CHANGE_PUBLISHED_SUBTYPE) {
51374
+ const published = codePublishedPayloadFromSdkMessage(message);
51375
+ if (published) {
51376
+ yield* offerRuntimeEvent({
51377
+ ...base,
51378
+ type: "code.published",
51379
+ payload: published
51380
+ });
51381
+ return;
51382
+ }
51383
+ }
50889
51384
  switch (message.subtype) {
50890
51385
  case "init":
50891
51386
  yield* offerRuntimeEvent({
@@ -51520,8 +52015,13 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
51520
52015
  const mcpSession = readMcpProviderSession(input.threadId);
51521
52016
  const externalMcpServers = options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers;
51522
52017
  const narrateBeforeTools = options?.resolveToolCallNarration === void 0 ? DEFAULT_SERVER_SETTINGS.enableToolCallNarration : yield* options.resolveToolCallNarration;
52018
+ const unpromptedSubagents = options?.resolveUnpromptedSubagents === void 0 ? DEFAULT_SERVER_SETTINGS.enableUnpromptedSubagents : yield* options.resolveUnpromptedSubagents;
51523
52019
  const compressRuleset = compressRulesetFor(input.compressMode ?? "off");
51524
- const systemPromptAppend = [...narrateBeforeTools ? [NARRATE_BEFORE_TOOLS_PROMPT] : [], ...compressRuleset !== void 0 ? [compressRuleset] : []].join("\n\n");
52020
+ const systemPromptAppend = [
52021
+ ...narrateBeforeTools ? [NARRATE_BEFORE_TOOLS_PROMPT] : [],
52022
+ unpromptedSubagents ? SUBAGENTS_ALLOWED_PROMPT : SUBAGENTS_ON_REQUEST_PROMPT,
52023
+ ...compressRuleset !== void 0 ? [compressRuleset] : []
52024
+ ].join("\n\n");
51525
52025
  const queryOptions = {
51526
52026
  ...input.cwd ? { cwd: input.cwd } : {},
51527
52027
  ...apiModelId ? { model: apiModelId } : {},
@@ -51983,12 +52483,14 @@ const ClaudeDriver = {
51983
52483
  const mcpRegistry = yield* McpRegistry;
51984
52484
  const resolveDisabledSkills = serverSettings.getSettings.pipe(Effect.map((settings) => settings.disabledSkills), Effect.orElseSucceed(() => []));
51985
52485
  const resolveToolCallNarration = serverSettings.getSettings.pipe(Effect.map((settings) => settings.enableToolCallNarration), Effect.orElseSucceed(() => DEFAULT_SERVER_SETTINGS.enableToolCallNarration));
52486
+ const resolveUnpromptedSubagents = serverSettings.getSettings.pipe(Effect.map((settings) => settings.enableUnpromptedSubagents), Effect.orElseSucceed(() => DEFAULT_SERVER_SETTINGS.enableUnpromptedSubagents));
51986
52487
  const adapter = yield* makeClaudeAdapter(effectiveConfig, {
51987
52488
  instanceId,
51988
52489
  environment: processEnv,
51989
52490
  resolveMcpServers: mcpRegistry.resolveForSession,
51990
52491
  resolveDisabledSkills,
51991
52492
  resolveToolCallNarration,
52493
+ resolveUnpromptedSubagents,
51992
52494
  ...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}
51993
52495
  });
51994
52496
  const textGeneration = yield* makeClaudeTextGeneration(effectiveConfig, processEnv);
@@ -85633,6 +86135,24 @@ function runtimeEventToActivities(event, taskTitle) {
85633
86135
  turnId: toTurnId$1(event.turnId) ?? null,
85634
86136
  ...maybeSequence
85635
86137
  }];
86138
+ case "code.published": {
86139
+ const label = event.payload.repo ? event.payload.identifier ? `${event.payload.repo}#${event.payload.identifier}` : event.payload.repo : event.payload.url;
86140
+ return [{
86141
+ id: event.eventId,
86142
+ createdAt: event.createdAt,
86143
+ tone: "info",
86144
+ kind: "code.published",
86145
+ summary: `Published ${label}`,
86146
+ payload: {
86147
+ hostingProvider: event.payload.hostingProvider,
86148
+ url: event.payload.url,
86149
+ ...event.payload.repo ? { repo: event.payload.repo } : {},
86150
+ ...event.payload.identifier ? { identifier: event.payload.identifier } : {}
86151
+ },
86152
+ turnId: toTurnId$1(event.turnId) ?? null,
86153
+ ...maybeSequence
86154
+ }];
86155
+ }
85636
86156
  case "turn.plan.updated": return [{
85637
86157
  id: event.eventId,
85638
86158
  createdAt: event.createdAt,
@@ -88764,6 +89284,7 @@ const reportUpdateAvailable = Effect.gen(function* () {
88764
89284
  const makeCli = () => Command.make("p4c", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the P4Code server."), Command.withHandler((flags) => runServerCommand(flags)), Command.withSubcommands([
88765
89285
  startCommand,
88766
89286
  serveCommand,
89287
+ agentsCommand,
88767
89288
  authCommand,
88768
89289
  hubCommand,
88769
89290
  mcpCommand,