@p4code/cli 0.4.12 → 0.4.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.mjs CHANGED
@@ -240,7 +240,7 @@ const make$93 = () => {
240
240
  const layer$82 = Layer.sync(NetService, make$93);
241
241
  //#endregion
242
242
  //#region package.json
243
- var version = "0.4.12";
243
+ var version = "0.4.14";
244
244
  //#endregion
245
245
  //#region src/config.ts
246
246
  /**
@@ -21599,6 +21599,90 @@ function expandHomePath$3(value) {
21599
21599
  if (value.startsWith("~/") || value.startsWith("~\\")) return NodePath.join(NodeOS.homedir(), value.slice(2));
21600
21600
  return value;
21601
21601
  }
21602
+ /**
21603
+ * Reasoning levels `muse exec --reasoning-effort` accepts, in the CLI's own
21604
+ * order (`muse exec --help`, Muse Code 1.1.1). `high` is the CLI default, so
21605
+ * it is the default here too.
21606
+ */
21607
+ const MUSE_REASONING_EFFORTS = [
21608
+ "none",
21609
+ "minimal",
21610
+ "low",
21611
+ "medium",
21612
+ "high",
21613
+ "xhigh",
21614
+ "max",
21615
+ "ultra"
21616
+ ];
21617
+ const MUSE_DEFAULT_REASONING_EFFORT = "high";
21618
+ /**
21619
+ * Keep a selection Muse cannot parse out of the argv. `reasoningEffort` is a
21620
+ * shared option id — Codex publishes one too — so a selection left over from
21621
+ * another provider can reach this driver, and an unknown level makes
21622
+ * `muse exec` reject the whole invocation.
21623
+ */
21624
+ function museReasoningEffort(value) {
21625
+ return value !== void 0 && MUSE_REASONING_EFFORTS.includes(value) ? value : void 0;
21626
+ }
21627
+ /**
21628
+ * Build the argument vector for one turn.
21629
+ *
21630
+ * Approval policy is the load-bearing decision here. Muse Code 1.1.1 does
21631
+ * express approval as a policy — `--approval-mode untrusted|on-request|never`,
21632
+ * plus named `--permission-profile` documents — but a headless `exec` run
21633
+ * still has no channel to answer a prompt: approvals are served over MSP
21634
+ * (`muse serve`), not on the `exec` stream. So the only two postures `exec`
21635
+ * can honour end to end are `--disable-approval` (keep the OS sandbox) and
21636
+ * `--yolo` (drop approval and the sandbox, and trust the workspace). P4Code
21637
+ * owns the user-facing runtime mode, so it maps onto those two:
21638
+ *
21639
+ * - `full-access` → `--yolo`
21640
+ * - everything else → `--disable-approval --trust-workspace`, leaving the
21641
+ * sandbox to contain what runs.
21642
+ *
21643
+ * `approval-required` cannot be honoured over `exec`; the adapter emits a
21644
+ * `config.warning` for it rather than silently pretending otherwise. Serving
21645
+ * a real approval prompt means moving the driver onto MSP.
21646
+ *
21647
+ * Flags here were verified against Muse Code 1.1.1-R2514.1 (`muse exec
21648
+ * --help`).
21649
+ */
21650
+ function buildMuseExecArgs(input) {
21651
+ const args = [
21652
+ "exec",
21653
+ "--json",
21654
+ "--session-id",
21655
+ input.sessionId
21656
+ ];
21657
+ if (input.model) args.push("--model", input.model);
21658
+ if (input.reasoningEffort) args.push("--reasoning-effort", input.reasoningEffort);
21659
+ if (input.baseUrl) args.push("--base-url", input.baseUrl);
21660
+ if (input.runtimeMode === "full-access") args.push("--yolo");
21661
+ else {
21662
+ args.push("--disable-approval", "--trust-workspace");
21663
+ switch (input.sandboxMode) {
21664
+ case "danger-full-access":
21665
+ args.push("--disable-sandbox");
21666
+ break;
21667
+ case "read-only":
21668
+ args.push("--disable-write", "--disable-shell");
21669
+ break;
21670
+ default: break;
21671
+ }
21672
+ }
21673
+ for (const imagePath of input.imagePaths ?? []) args.push("--image", imagePath);
21674
+ args.push(input.prompt);
21675
+ return args;
21676
+ }
21677
+ /** `muse --version` argv, used by the provider status probe. */
21678
+ const MUSE_VERSION_ARGS = ["--version"];
21679
+ /**
21680
+ * Whether a runtime mode asks for interactive approvals that Muse Code
21681
+ * cannot deliver headlessly.
21682
+ */
21683
+ function museApprovalsUnsupported(runtimeMode) {
21684
+ return runtimeMode === "approval-required";
21685
+ }
21602
21686
  //#endregion
21603
21687
  //#region src/provider/skillOverrides.ts
21604
21688
  /**
@@ -21680,19 +21764,7 @@ function buildSkillOverrides(disabled, discovered) {
21680
21764
  return Object.keys(overrides).length === 0 ? void 0 : overrides;
21681
21765
  }
21682
21766
  //#endregion
21683
- //#region src/provider/Drivers/ClaudeSkills.ts
21684
- /**
21685
- * ClaudeSkills — filesystem discovery of Claude Code skills for the `$` picker.
21686
- *
21687
- * Claude Code loads skills from `<config dir>/skills` (user scope) and
21688
- * `<cwd>/.claude/skills` (project scope), one directory per skill with a
21689
- * `SKILL.md` carrying YAML frontmatter. The Agent SDK init handshake surfaces
21690
- * skills only as slash commands without their filesystem paths, so the
21691
- * provider snapshot scans the same locations directly, mirroring how the
21692
- * Codex app-server reports its skills.
21693
- *
21694
- * @module provider/Drivers/ClaudeSkills
21695
- */
21767
+ //#region src/provider/Drivers/skillFrontmatter.ts
21696
21768
  const FRONTMATTER_PATTERN$1 = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
21697
21769
  /** The `true` spellings the CLI accepts for a boolean frontmatter field. */
21698
21770
  const TRUTHY_FRONTMATTER_VALUES = /* @__PURE__ */ new Set([
@@ -21727,10 +21799,10 @@ function stripMarkdownFrontmatter(contents) {
21727
21799
  /**
21728
21800
  * The `name`/`description` header a skill and a subagent definition both carry.
21729
21801
  *
21730
- * Exported because agent definitions are the same header in a different file,
21802
+ * Shared because agent definitions are the same header in a different file,
21731
21803
  * and the sync validates them before publishing one. A second copy of this
21732
21804
  * would be a second answer to "does this load in the provider", which is the
21733
- * one question both callers are asking.
21805
+ * one question every caller is asking.
21734
21806
  */
21735
21807
  function parseMarkdownFrontmatter(contents) {
21736
21808
  const match = FRONTMATTER_PATTERN$1.exec(contents);
@@ -21755,6 +21827,245 @@ function parseMarkdownFrontmatter(contents) {
21755
21827
  ...requiredInteractionMode === void 0 ? {} : { requiredInteractionMode }
21756
21828
  };
21757
21829
  }
21830
+ //#endregion
21831
+ //#region src/provider/Drivers/SharedSkills.ts
21832
+ /**
21833
+ * SharedSkills — the skills a provider can reach, whichever provider it is.
21834
+ *
21835
+ * A skill is a directory holding `SKILL.md`, and by now every CLI P4Code drives
21836
+ * loads them from the *same* provider-neutral user root — `~/.agents/skills` —
21837
+ * on top of its own. That is why the sync materializes there
21838
+ * (`sync/sharedSkillRoot.ts`): one copy of a skill, visible to all of them.
21839
+ *
21840
+ * What was still Claude- and Codex-only is the *reporting*: the composer's `$`
21841
+ * picker and the transcript's skill chips read `ServerProvider.skills`, and a
21842
+ * provider that never filled that array looked like it had no skills at all
21843
+ * while its CLI was happily loading them. So discovery is done here once,
21844
+ * against the roots each CLI documents, and every driver fills the same field.
21845
+ *
21846
+ * **Reported, not enforced.** `enabled` mirrors P4Code's own disabled-skill
21847
+ * setting so a skill switched off disappears from the picker everywhere. Only
21848
+ * Claude can additionally be told to withhold it from the model
21849
+ * (`skillOverrides`); the other CLIs own their listing, so the toggle is a
21850
+ * P4Code-surface toggle there, exactly as the flag's name in discovery says.
21851
+ *
21852
+ * Roots per CLI, from each vendor's own documentation:
21853
+ * - Cursor: `~/.agents/skills`, `~/.cursor/skills`, `.agents/skills`,
21854
+ * `.cursor/skills` — https://cursor.com/docs/skills
21855
+ * - Grok: `~/.agents/skills`, `~/.grok/skills`, `.agents/skills`,
21856
+ * `.grok/skills` — https://docs.x.ai/build/features/skills-plugins-marketplaces
21857
+ * - OpenCode: `~/.agents/skills`, `~/.claude/skills`,
21858
+ * `<XDG config>/opencode/skills`, and the project mirrors of all three —
21859
+ * https://opencode.ai/docs/skills/
21860
+ * - Muse: `~/.agents/skills`, `<XDG config>/muse/skills`, `.agents/skills`,
21861
+ * plus the foreign personal roots it reads unless `--no-foreign-personal-context`
21862
+ * is passed (`~/.claude/skills`, `~/.codex/skills`), which `muse skills list`
21863
+ * reports as user-scope entries. Those two are gated on
21864
+ * `MUSE_EXEC_LOADS_FOREIGN_PERSONAL_SKILLS`, which the argv builder owns,
21865
+ * so the claim tracks the flag the adapter actually spawns with.
21866
+ *
21867
+ * Claude and Codex are absent on purpose: each already reports its own skills
21868
+ * from a source that knows more than the filesystem does — Claude's config
21869
+ * directory may be redirected per instance, and Codex answers a `skills/list`
21870
+ * RPC that includes skills no directory scan would find.
21871
+ *
21872
+ * @module provider/Drivers/SharedSkills
21873
+ */
21874
+ /** `SKILL.md` is the only file a skill is required to have. */
21875
+ const SKILL_FILENAME$1 = "SKILL.md";
21876
+ /** The provider-neutral user skill root the sync materializes into. */
21877
+ const SHARED_USER_SKILL_DIRNAME = ".agents";
21878
+ /**
21879
+ * Enumerate skills across roots, least specific first.
21880
+ *
21881
+ * Discovery is best-effort: unreadable roots and malformed skill entries are
21882
+ * skipped so a broken skill never degrades the provider snapshot. On name
21883
+ * collisions the later root wins, matching the most-specific-wins resolution
21884
+ * every one of these CLIs uses — which is also why the shared root is listed
21885
+ * before the provider's own.
21886
+ */
21887
+ const discoverSkillsInRoots = Effect.fn("discoverSkillsInRoots")(function* (roots, disabledSkills) {
21888
+ const fileSystem = yield* FileSystem.FileSystem;
21889
+ const path = yield* Path.Path;
21890
+ const skillsByName = /* @__PURE__ */ new Map();
21891
+ for (const root of roots) {
21892
+ const entries = yield* fileSystem.readDirectory(root.directory).pipe(Effect.orElseSucceed(() => []));
21893
+ for (const entry of [...entries].sort()) {
21894
+ const skillPath = path.join(root.directory, entry, SKILL_FILENAME$1);
21895
+ const contents = yield* fileSystem.readFileString(skillPath).pipe(Effect.orElseSucceed(() => void 0));
21896
+ if (contents === void 0) continue;
21897
+ const frontmatter = parseMarkdownFrontmatter(contents);
21898
+ if (frontmatter.kind === "malformed") continue;
21899
+ const name = (frontmatter.kind === "parsed" ? frontmatter.name : void 0) ?? entry.trim();
21900
+ if (!name) continue;
21901
+ skillsByName.set(name, {
21902
+ name,
21903
+ path: skillPath,
21904
+ enabled: !isSkillDisabled({
21905
+ name,
21906
+ path: skillPath
21907
+ }, disabledSkills ?? []),
21908
+ scope: root.scope,
21909
+ ...frontmatter.kind === "parsed" && frontmatter.description ? { description: frontmatter.description } : {},
21910
+ ...frontmatter.kind === "parsed" && frontmatter.requiredInteractionMode ? { requiredInteractionMode: frontmatter.requiredInteractionMode } : {}
21911
+ });
21912
+ }
21913
+ }
21914
+ return [...skillsByName.values()].sort((left, right) => left.name.localeCompare(right.name));
21915
+ });
21916
+ /**
21917
+ * The config home a CLI following the XDG convention reads, matching what the
21918
+ * spawned process would see: its own `XDG_CONFIG_HOME` when set, else
21919
+ * `~/.config`. Read from the provider environment rather than `process.env`
21920
+ * because an instance may be configured with a different one.
21921
+ */
21922
+ function xdgConfigHome$1(environment) {
21923
+ const configured = environment.XDG_CONFIG_HOME?.trim() ?? "";
21924
+ return configured.length > 0 ? configured : `${homeDirectory(environment)}/.config`;
21925
+ }
21926
+ /**
21927
+ * The home the spawned CLI would resolve: the provider instance's own `HOME`
21928
+ * when it sets one, else this process's. A provider configured with a separate
21929
+ * home keeps its own skills, which is the point of configuring one.
21930
+ */
21931
+ function homeDirectory(environment) {
21932
+ const configured = environment.HOME?.trim() ?? "";
21933
+ return configured.length > 0 ? configured : NodeOS.homedir();
21934
+ }
21935
+ /**
21936
+ * The provider-neutral roots every CLI reads, least specific first.
21937
+ *
21938
+ * For callers that act on a skill without knowing which provider will run it —
21939
+ * the server-side expansion of a user-invoked skill, which happens before the
21940
+ * turn reaches an adapter.
21941
+ */
21942
+ const sharedSkillDirectories = Effect.fn("sharedSkillDirectories")(function* (cwd, environment) {
21943
+ const path = yield* Path.Path;
21944
+ const user = path.join(homeDirectory(environment ?? process.env), SHARED_USER_SKILL_DIRNAME, "skills");
21945
+ return cwd === void 0 ? [user] : [user, path.join(cwd, SHARED_USER_SKILL_DIRNAME, "skills")];
21946
+ });
21947
+ /** User-scope roots per driver, shared root first. */
21948
+ function userSkillDirectories(driver, environment) {
21949
+ const home = homeDirectory(environment);
21950
+ const shared = [
21951
+ home,
21952
+ SHARED_USER_SKILL_DIRNAME,
21953
+ "skills"
21954
+ ];
21955
+ switch (driver) {
21956
+ case "cursor": return [shared, [
21957
+ home,
21958
+ ".cursor",
21959
+ "skills"
21960
+ ]];
21961
+ case "grok": return [shared, [
21962
+ home,
21963
+ ".grok",
21964
+ "skills"
21965
+ ]];
21966
+ case "opencode": return [
21967
+ shared,
21968
+ [
21969
+ home,
21970
+ ".claude",
21971
+ "skills"
21972
+ ],
21973
+ [
21974
+ xdgConfigHome$1(environment),
21975
+ "opencode",
21976
+ "skills"
21977
+ ]
21978
+ ];
21979
+ case "muse": return [
21980
+ shared,
21981
+ ...[[
21982
+ home,
21983
+ ".claude",
21984
+ "skills"
21985
+ ], [
21986
+ home,
21987
+ ".codex",
21988
+ "skills"
21989
+ ]],
21990
+ [
21991
+ xdgConfigHome$1(environment),
21992
+ "muse",
21993
+ "skills"
21994
+ ]
21995
+ ];
21996
+ default: return [shared];
21997
+ }
21998
+ }
21999
+ /** Project-scope roots per driver, relative to the workspace. */
22000
+ function projectSkillDirectories(driver) {
22001
+ const shared = [SHARED_USER_SKILL_DIRNAME, "skills"];
22002
+ switch (driver) {
22003
+ case "cursor": return [shared, [".cursor", "skills"]];
22004
+ case "grok": return [shared, [".grok", "skills"]];
22005
+ case "opencode": return [
22006
+ shared,
22007
+ [".claude", "skills"],
22008
+ [".opencode", "skills"]
22009
+ ];
22010
+ default: return [shared];
22011
+ }
22012
+ }
22013
+ /** Every root a driver's CLI reads, least specific first. */
22014
+ const providerSkillRoots = Effect.fn("providerSkillRoots")(function* (input) {
22015
+ const path = yield* Path.Path;
22016
+ const environment = input.environment ?? process.env;
22017
+ const user = userSkillDirectories(input.driver, environment).map((segments) => ({
22018
+ directory: path.join(...segments),
22019
+ scope: "user"
22020
+ }));
22021
+ const cwd = input.cwd;
22022
+ if (cwd === void 0) return user;
22023
+ return [...user, ...projectSkillDirectories(input.driver).map((segments) => ({
22024
+ directory: path.join(cwd, ...segments),
22025
+ scope: "project"
22026
+ }))];
22027
+ });
22028
+ /**
22029
+ * A snapshot draft carrying the skills its CLI can reach.
22030
+ *
22031
+ * Applied after the probe rather than inside it: what a CLI reports about
22032
+ * itself — version, auth, models — and what it loads off disk are independent,
22033
+ * and a provider that is failing its probe still has the skills sitting there
22034
+ * for the picker.
22035
+ */
22036
+ const withProviderSkills = Effect.fn("withProviderSkills")(function* (draft, input) {
22037
+ const skills = yield* discoverProviderSkills(input);
22038
+ return {
22039
+ ...draft,
22040
+ skills
22041
+ };
22042
+ });
22043
+ /**
22044
+ * The skills a provider snapshot should report for a driver that loads them
22045
+ * from disk. Drivers call this instead of leaving `skills` empty.
22046
+ */
22047
+ const discoverProviderSkills = Effect.fn("discoverProviderSkills")(function* (input) {
22048
+ const roots = yield* providerSkillRoots({
22049
+ driver: input.driver,
22050
+ cwd: input.cwd,
22051
+ environment: input.environment
22052
+ });
22053
+ return yield* discoverSkillsInRoots(roots, input.disabledSkills);
22054
+ });
22055
+ //#endregion
22056
+ //#region src/provider/Drivers/ClaudeSkills.ts
22057
+ /**
22058
+ * ClaudeSkills — filesystem discovery of Claude Code skills for the `$` picker.
22059
+ *
22060
+ * Claude Code loads skills from `<config dir>/skills` (user scope) and
22061
+ * `<cwd>/.claude/skills` (project scope), one directory per skill with a
22062
+ * `SKILL.md` carrying YAML frontmatter. The Agent SDK init handshake surfaces
22063
+ * skills only as slash commands without their filesystem paths, so the
22064
+ * provider snapshot scans the same locations directly, mirroring how the
22065
+ * Codex app-server reports its skills.
22066
+ *
22067
+ * @module provider/Drivers/ClaudeSkills
22068
+ */
21758
22069
  /**
21759
22070
  * Resolve the Claude config directory the CLI would use, matching the
21760
22071
  * precedence the spawned CLI sees: the instance's `homePath` (exported as
@@ -21807,41 +22118,15 @@ const resolveClaudeUserAgentsDir = Effect.fn("resolveClaudeUserAgentsDir")(funct
21807
22118
  * most-specific-wins resolution.
21808
22119
  */
21809
22120
  const discoverClaudeSkills = Effect.fn("discoverClaudeSkills")(function* (config, cwd, environment, disabledSkills) {
21810
- const fileSystem = yield* FileSystem.FileSystem;
21811
22121
  const path = yield* Path.Path;
21812
22122
  const configDirPath = yield* resolveClaudeConfigDirPath(config, environment ?? process.env, cwd);
21813
- const roots = [{
22123
+ return yield* discoverSkillsInRoots([{
21814
22124
  directory: path.join(configDirPath, "skills"),
21815
22125
  scope: "user"
21816
22126
  }, ...cwd ? [{
21817
22127
  directory: path.join(cwd, ".claude", "skills"),
21818
22128
  scope: "project"
21819
- }] : []];
21820
- const skillsByName = /* @__PURE__ */ new Map();
21821
- for (const root of roots) {
21822
- const entries = yield* fileSystem.readDirectory(root.directory).pipe(Effect.orElseSucceed(() => []));
21823
- for (const entry of [...entries].sort()) {
21824
- const skillPath = path.join(root.directory, entry, "SKILL.md");
21825
- const contents = yield* fileSystem.readFileString(skillPath).pipe(Effect.orElseSucceed(() => void 0));
21826
- if (contents === void 0) continue;
21827
- const frontmatter = parseMarkdownFrontmatter(contents);
21828
- if (frontmatter.kind === "malformed") continue;
21829
- const name = (frontmatter.kind === "parsed" ? frontmatter.name : void 0) ?? entry.trim();
21830
- if (!name) continue;
21831
- skillsByName.set(name, {
21832
- name,
21833
- path: skillPath,
21834
- enabled: !isSkillDisabled({
21835
- name,
21836
- path: skillPath
21837
- }, disabledSkills ?? []),
21838
- scope: root.scope,
21839
- ...frontmatter.kind === "parsed" && frontmatter.description ? { description: frontmatter.description } : {},
21840
- ...frontmatter.kind === "parsed" && frontmatter.requiredInteractionMode ? { requiredInteractionMode: frontmatter.requiredInteractionMode } : {}
21841
- });
21842
- }
21843
- }
21844
- return [...skillsByName.values()].sort((left, right) => left.name.localeCompare(right.name));
22129
+ }] : []], disabledSkills);
21845
22130
  });
21846
22131
  //#endregion
21847
22132
  //#region src/mcp/ClaudeMcpFiles.ts
@@ -23818,6 +24103,138 @@ function planSkillSync(input) {
23818
24103
  };
23819
24104
  }
23820
24105
  //#endregion
24106
+ //#region src/sync/assetMirrorOps.ts
24107
+ /**
24108
+ * Whether this machine may write the asset into a mirror root.
24109
+ *
24110
+ * `foreign` covers the case that matters: a file someone else wrote, or a
24111
+ * symlink a user set up between two providers, whose content matches neither
24112
+ * what is being written nor what P4Code last wrote from here. Overwriting it
24113
+ * would silently replace a configuration the user owns.
24114
+ */
24115
+ function decideMirrorWrite(input) {
24116
+ if (input.existing === void 0) return "write";
24117
+ if (input.existing === input.next) return "up-to-date";
24118
+ if (input.previous !== void 0 && input.existing === input.previous) return "write";
24119
+ return "foreign";
24120
+ }
24121
+ /**
24122
+ * Whether a mirrored copy may be removed.
24123
+ *
24124
+ * Only a copy that still matches what P4Code wrote. An edited mirror is the
24125
+ * user's, and a deletion upstream is not a reason to throw their edit away.
24126
+ */
24127
+ function shouldRemoveMirror(input) {
24128
+ return input.existing !== void 0 && input.managed !== void 0 && input.existing === input.managed;
24129
+ }
24130
+ //#endregion
24131
+ //#region src/sync/providerAssetRoots.ts
24132
+ /**
24133
+ * providerAssetRoots — where a memory file or a subagent definition has to
24134
+ * land for a provider other than Claude to read it.
24135
+ *
24136
+ * Skills needed none of this: every CLI P4Code drives loads
24137
+ * `~/.agents/skills`, so one directory serves them all. Memory and subagent
24138
+ * definitions have no such shared root — there is a proposal to standardize a
24139
+ * user-level `AGENTS.md` location and nothing shipped — so each provider reads
24140
+ * its own path, and an asset that exists only in Claude's config directory is
24141
+ * an asset the other providers never see.
24142
+ *
24143
+ * Verified on this machine rather than taken from documentation:
24144
+ * - Codex keeps user instructions at `$CODEX_HOME/AGENTS.md` (default
24145
+ * `~/.codex`) and its subagent definitions at `$CODEX_HOME/agents/*.toml`.
24146
+ * - OpenCode keeps user instructions at `<XDG config>/opencode/AGENTS.md`
24147
+ * and its subagent definitions at `<XDG config>/opencode/agents/*.md`,
24148
+ * markdown with the same `name`/`description` frontmatter Claude uses.
24149
+ * - Cursor reads subagent definitions from `~/.cursor/agents`, and its own
24150
+ * documentation says it also reads `~/.claude/agents` and `~/.codex/agents`
24151
+ * for cross-tool compatibility.
24152
+ * - Muse reads the other CLIs' personal rules — `~/.claude/CLAUDE.md` and
24153
+ * `~/.codex/AGENTS.md` — unless a run passes
24154
+ * `--no-foreign-personal-context`, which P4Code never does
24155
+ * (`MUSE_EXEC_LOADS_FOREIGN_PERSONAL_SKILLS`). It therefore needs no
24156
+ * mirror of its own.
24157
+ *
24158
+ * Deliberately absent:
24159
+ * - **Codex subagents.** They are TOML with `developer_instructions`, not
24160
+ * markdown with frontmatter. Mirroring one would mean converting a
24161
+ * definition rather than copying it, and a lossy conversion of the file
24162
+ * that decides what a subagent is handed is worse than not having it.
24163
+ * - **Cursor user rules.** Cursor keeps cross-project prose in its own
24164
+ * settings store, not in a file on disk, so there is nothing to write.
24165
+ * - **Grok.** Its skills root is confirmed (`~/.agents/skills`); a
24166
+ * user-level instructions file is not, and x.ai's own documentation does
24167
+ * not name one. Writing a guessed path is worse than writing nothing.
24168
+ *
24169
+ * @module sync/providerAssetRoots
24170
+ */
24171
+ /**
24172
+ * The filename every non-Claude provider here reads its user instructions from.
24173
+ *
24174
+ * Codex and OpenCode read `AGENTS.md` and nothing else, so a `CLAUDE.md` asset
24175
+ * — the default, and what most machines actually hold — reaches them only
24176
+ * under this name. That rename is the one place in the sync where the asset's
24177
+ * name and the file on disk differ, and it is narrow on purpose: it happens for
24178
+ * this one pair of names, into these roots, and only when there is no
24179
+ * `AGENTS.md` asset of its own to mirror instead.
24180
+ */
24181
+ const SHARED_MEMORY_FILE_NAME = "AGENTS.md";
24182
+ /**
24183
+ * The config home of a CLI following the XDG convention, as the CLI itself
24184
+ * would resolve it.
24185
+ */
24186
+ function xdgConfigHome(environment) {
24187
+ const configured = environment.XDG_CONFIG_HOME?.trim() ?? "";
24188
+ if (configured.length > 0) return configured;
24189
+ const home = environment.HOME?.trim() ?? "";
24190
+ return `${home.length > 0 ? home : NodeOS.homedir()}/.config`;
24191
+ }
24192
+ /** Codex's home, matching the CLI's own `CODEX_HOME` precedence. */
24193
+ function codexHome(environment) {
24194
+ const configured = environment.CODEX_HOME?.trim() ?? "";
24195
+ return configured.length > 0 ? configured : `${NodeOS.homedir()}/.codex`;
24196
+ }
24197
+ /**
24198
+ * Directories that should hold a copy of a shared memory file.
24199
+ *
24200
+ * Directories rather than file paths because the sync writes an asset by name
24201
+ * into a root, and the name *is* the filename.
24202
+ */
24203
+ const memoryMirrorRoots = Effect.fn("memoryMirrorRoots")(function* (environment) {
24204
+ const path = yield* Path.Path;
24205
+ const resolved = environment ?? process.env;
24206
+ return [path.resolve(codexHome(resolved)), path.join(path.resolve(xdgConfigHome(resolved)), "opencode")];
24207
+ });
24208
+ /**
24209
+ * Where a memory asset should be copied, and under which name.
24210
+ *
24211
+ * `AGENTS.md` maps to itself. `CLAUDE.md` maps to `AGENTS.md`, but only when no
24212
+ * `AGENTS.md` asset exists: with both in play the destination would otherwise
24213
+ * flip between two sources depending on which one synced last, and a file whose
24214
+ * content depends on ordering is worse than one provider missing a mirror.
24215
+ */
24216
+ const memoryMirrorTargets = Effect.fn("memoryMirrorTargets")(function* (input) {
24217
+ if (!(input.name === "AGENTS.md" || input.name === "CLAUDE.md" && !input.siblingNames.has("AGENTS.md"))) return [];
24218
+ return (yield* memoryMirrorRoots(input.environment)).map((root) => ({
24219
+ root,
24220
+ name: SHARED_MEMORY_FILE_NAME
24221
+ }));
24222
+ });
24223
+ /** Directories that should hold a copy of a markdown subagent definition. */
24224
+ const agentDefinitionMirrorRoots = Effect.fn("agentDefinitionMirrorRoots")(function* (environment) {
24225
+ const path = yield* Path.Path;
24226
+ const resolved = environment ?? process.env;
24227
+ const home = resolved.HOME?.trim() ?? "";
24228
+ return [path.join(home.length > 0 ? home : NodeOS.homedir(), ".cursor", "agents"), path.join(path.resolve(xdgConfigHome(resolved)), "opencode", "agents")];
24229
+ });
24230
+ /** Where a markdown subagent definition should be copied, under its own name. */
24231
+ const agentDefinitionMirrorTargets = Effect.fn("agentDefinitionMirrorTargets")(function* (input) {
24232
+ return (yield* agentDefinitionMirrorRoots(input.environment)).map((root) => ({
24233
+ root,
24234
+ name: input.name
24235
+ }));
24236
+ });
24237
+ //#endregion
23821
24238
  //#region src/sync/sharedSkillRoot.ts
23822
24239
  const SHARED_SKILL_ROOT_REFUSAL = "shared-root-migration-blocked";
23823
24240
  const inspectLayout = Effect.fn("sharedSkillRoot.inspectLayout")(function* (input) {
@@ -24055,15 +24472,70 @@ const make$77 = Effect.gen(function* () {
24055
24472
  }]
24056
24473
  }))));
24057
24474
  });
24475
+ const prepareSkillRoot = Effect.gen(function* () {
24476
+ yield* prepareSharedSkillRoot({
24477
+ claudeSkillsRoot: yield* claudeHome().pipe(Effect.flatMap(resolveClaudeUserSkillsDir)),
24478
+ manifestPath
24479
+ });
24480
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning("shared skill root migration failed", { cause })));
24481
+ /** What a mirror root currently holds under that name, if anything. */
24482
+ const mirrorDigest = Effect.fn("AssetSync.mirrorDigest")(function* (driver, root, name) {
24483
+ const existing = (yield* driver.read(root)).skills.find((entry) => entry.name === name);
24484
+ return existing ? digestSkillFiles(existing.files) : void 0;
24485
+ });
24486
+ /**
24487
+ * Copy a managed asset into the roots other providers read.
24488
+ *
24489
+ * Nothing P4Code did not put there is ever overwritten: a mirror holding
24490
+ * content that matches neither what is being written nor what was last
24491
+ * written from here belongs to someone else, and it is left alone. That is
24492
+ * the whole guard — a file the user wrote by hand, or a symlink they set up
24493
+ * between two providers, survives a sync untouched.
24494
+ */
24495
+ const mirrorWrite = Effect.fn("AssetSync.mirrorWrite")(function* (input) {
24496
+ const mirrors = input.driver.mirrors;
24497
+ if (mirrors === void 0) return;
24498
+ const nextDigest = digestSkillFiles(input.files);
24499
+ const targets = yield* mirrors({
24500
+ name: input.name,
24501
+ siblingNames: input.siblingNames
24502
+ });
24503
+ for (const target of targets) {
24504
+ if (decideMirrorWrite({
24505
+ existing: yield* mirrorDigest(input.driver, target.root, target.name),
24506
+ next: nextDigest,
24507
+ previous: input.previousDigest
24508
+ }) !== "write") continue;
24509
+ yield* input.driver.write({
24510
+ root: target.root,
24511
+ name: target.name,
24512
+ files: input.files
24513
+ });
24514
+ }
24515
+ });
24516
+ /** Drop a mirrored copy, and only a copy that still matches what we wrote. */
24517
+ const mirrorRemove = Effect.fn("AssetSync.mirrorRemove")(function* (input) {
24518
+ const mirrors = input.driver.mirrors;
24519
+ if (mirrors === void 0) return;
24520
+ const targets = yield* mirrors({
24521
+ name: input.name,
24522
+ siblingNames: input.siblingNames
24523
+ });
24524
+ for (const target of targets) {
24525
+ if (!shouldRemoveMirror({
24526
+ existing: yield* mirrorDigest(input.driver, target.root, target.name),
24527
+ managed: input.digest
24528
+ })) continue;
24529
+ yield* input.driver.remove({
24530
+ root: target.root,
24531
+ name: target.name
24532
+ });
24533
+ }
24534
+ });
24058
24535
  const drivers = [
24059
24536
  {
24060
24537
  kind: "skill",
24061
- prepareRoot: Effect.gen(function* () {
24062
- yield* prepareSharedSkillRoot({
24063
- claudeSkillsRoot: yield* claudeHome().pipe(Effect.flatMap(resolveClaudeUserSkillsDir)),
24064
- manifestPath
24065
- });
24066
- }).pipe(Effect.catchCause((cause) => Effect.logWarning("shared skill root migration failed", { cause }))),
24538
+ prepareRoot: prepareSkillRoot,
24067
24539
  resolveState: inspectSkillRoot(),
24068
24540
  resolveRoot: inspectSkillRoot().pipe(Effect.map((result) => result.root)),
24069
24541
  read: (root) => readSkillDirectory(root),
@@ -24091,7 +24563,11 @@ const make$77 = Effect.gen(function* () {
24091
24563
  memoryRoot: root,
24092
24564
  sharedMemoryRoot,
24093
24565
  name
24094
- }).pipe(Effect.ignore)
24566
+ }).pipe(Effect.ignore),
24567
+ mirrors: ({ name, siblingNames }) => memoryMirrorTargets({
24568
+ name,
24569
+ siblingNames
24570
+ })
24095
24571
  },
24096
24572
  {
24097
24573
  kind: "agent",
@@ -24105,7 +24581,8 @@ const make$77 = Effect.gen(function* () {
24105
24581
  remove: ({ root, name }) => removeAgentDefinition({
24106
24582
  agentsRoot: root,
24107
24583
  name
24108
- }).pipe(Effect.ignore)
24584
+ }).pipe(Effect.ignore),
24585
+ mirrors: ({ name }) => agentDefinitionMirrorTargets({ name })
24109
24586
  },
24110
24587
  {
24111
24588
  kind: "mcp",
@@ -24212,6 +24689,11 @@ const make$77 = Effect.gen(function* () {
24212
24689
  manifest: views.managed
24213
24690
  });
24214
24691
  const entries = new Map(views.managed.map((entry) => [entry.name, entry]));
24692
+ /**
24693
+ * Every asset of this kind in play, which the memory driver reads to
24694
+ * decide whether a `CLAUDE.md` may stand in for a missing `AGENTS.md`.
24695
+ */
24696
+ const assetNames = /* @__PURE__ */ new Set([...views.hub.map((asset) => asset.name), ...views.local.map((asset) => asset.name)]);
24215
24697
  /** One manifest for the whole run, re-read per kind so roots do not clobber. */
24216
24698
  const commit = Effect.fn("AssetSync.commit")(function* () {
24217
24699
  const manifest = yield* readSkillManifest(manifestPath);
@@ -24232,6 +24714,13 @@ const make$77 = Effect.gen(function* () {
24232
24714
  });
24233
24715
  break;
24234
24716
  }
24717
+ yield* mirrorWrite({
24718
+ driver,
24719
+ name: action.asset.name,
24720
+ siblingNames: assetNames,
24721
+ files: action.asset.files,
24722
+ previousDigest: entries.get(action.asset.name)?.digest
24723
+ });
24235
24724
  entries.set(action.asset.name, {
24236
24725
  name: action.asset.name,
24237
24726
  version: action.asset.version,
@@ -24274,6 +24763,12 @@ const make$77 = Effect.gen(function* () {
24274
24763
  root,
24275
24764
  name: action.name
24276
24765
  });
24766
+ yield* mirrorRemove({
24767
+ driver,
24768
+ name: action.name,
24769
+ siblingNames: assetNames,
24770
+ digest: entries.get(action.name)?.digest
24771
+ });
24277
24772
  entries.delete(action.name);
24278
24773
  removed.push({
24279
24774
  kind,
@@ -24711,6 +25206,13 @@ const make$77 = Effect.gen(function* () {
24711
25206
  name: input.name,
24712
25207
  files: input.files
24713
25208
  });
25209
+ if (written === "written") yield* mirrorWrite({
25210
+ driver,
25211
+ name: input.name,
25212
+ siblingNames: new Set(local.skills.map((asset) => asset.name)),
25213
+ files: input.files,
25214
+ previousDigest: void 0
25215
+ });
24714
25216
  if (written !== "written") return {
24715
25217
  outcome: written,
24716
25218
  path: null,
@@ -24747,10 +25249,17 @@ const make$77 = Effect.gen(function* () {
24747
25249
  path: null,
24748
25250
  detail: decision.detail
24749
25251
  };
25252
+ const removedDigest = local.skills.find((entry) => entry.name === ref.name);
24750
25253
  yield* driver.remove({
24751
25254
  root,
24752
25255
  name: ref.name
24753
25256
  });
25257
+ yield* mirrorRemove({
25258
+ driver,
25259
+ name: ref.name,
25260
+ siblingNames: new Set(local.skills.map((asset) => asset.name)),
25261
+ digest: removedDigest ? digestSkillFiles(removedDigest.files) : void 0
25262
+ });
24754
25263
  return {
24755
25264
  outcome: "removed",
24756
25265
  path: null,
@@ -100200,7 +100709,7 @@ const CURSOR_PRESENTATION = {
100200
100709
  badgeLabel: "Early Access",
100201
100710
  showInteractionModeToggle: true
100202
100711
  };
100203
- const EMPTY_CAPABILITIES$2 = createModelCapabilities({ optionDescriptors: [] });
100712
+ const EMPTY_CAPABILITIES$1 = createModelCapabilities({ optionDescriptors: [] });
100204
100713
  const CURSOR_ACP_MODEL_DISCOVERY_TIMEOUT_MS = 15e3;
100205
100714
  const CURSOR_PARAMETERIZED_MODEL_PICKER_MIN_VERSION_DATE = 20260408;
100206
100715
  const CURSOR_CLI_INSTALLATION_DOCS_URL = "https://cursor.com/docs/cli/installation";
@@ -100308,7 +100817,7 @@ function getBooleanCurrentValue(option) {
100308
100817
  if (normalized === "false") return false;
100309
100818
  }
100310
100819
  function buildCursorCapabilitiesFromConfigOptions(configOptions) {
100311
- if (!configOptions || configOptions.length === 0) return EMPTY_CAPABILITIES$2;
100820
+ if (!configOptions || configOptions.length === 0) return EMPTY_CAPABILITIES$1;
100312
100821
  const reasoningConfig = findCursorEffortConfigOption(configOptions);
100313
100822
  const reasoningEffortLevels = reasoningConfig?.type === "select" ? flattenSessionConfigSelectOptions(reasoningConfig).flatMap((entry) => {
100314
100823
  const normalizedValue = normalizeCursorReasoningValue(entry.value);
@@ -100477,7 +100986,7 @@ const discoverCursorModelsViaListAvailableModels = (cursorSettings, environment)
100477
100986
  }), environment);
100478
100987
  const discoverCursorModelsViaAcp = (cursorSettings, environment) => discoverCursorModelsViaListAvailableModels(cursorSettings, environment);
100479
100988
  function getCursorFallbackModels(cursorSettings) {
100480
- return providerModelsFromSettings([], cursorSettings.customModels, EMPTY_CAPABILITIES$2);
100989
+ return providerModelsFromSettings([], cursorSettings.customModels, EMPTY_CAPABILITIES$1);
100481
100990
  }
100482
100991
  /** Timeout for `agent about` — it's slower than a simple `--version` probe. */
100483
100992
  const ABOUT_TIMEOUT_MS = 8e3;
@@ -100513,7 +101022,7 @@ function buildCursorProviderSnapshot(input) {
100513
101022
  presentation: CURSOR_PRESENTATION,
100514
101023
  enabled: input.cursorSettings.enabled,
100515
101024
  checkedAt: input.checkedAt,
100516
- models: providerModelsFromSettings(input.discoveredModels ?? [], input.cursorSettings.customModels, EMPTY_CAPABILITIES$2),
101025
+ models: providerModelsFromSettings(input.discoveredModels ?? [], input.cursorSettings.customModels, EMPTY_CAPABILITIES$1),
100517
101026
  probe: {
100518
101027
  installed: true,
100519
101028
  version: input.parsed.version,
@@ -102007,6 +102516,7 @@ const CursorDriver = {
102007
102516
  const fileSystem = yield* FileSystem.FileSystem;
102008
102517
  const path = yield* Path.Path;
102009
102518
  const httpClient = yield* HttpClient.HttpClient;
102519
+ const serverConfig = yield* ServerConfig$1;
102010
102520
  const serverSettings = yield* ServerSettingsService;
102011
102521
  const eventLoggers = yield* ProviderEventLoggers;
102012
102522
  const processEnv = mergeProviderInstanceEnvironment(environment);
@@ -102035,7 +102545,12 @@ const CursorDriver = {
102035
102545
  instanceId
102036
102546
  });
102037
102547
  const textGeneration = yield* makeCursorTextGeneration(effectiveConfig, processEnv);
102038
- const checkProvider = checkCursorProviderStatus(effectiveConfig, processEnv).pipe(Effect.map(stampIdentity), Effect.provideService(Crypto.Crypto, crypto), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, path));
102548
+ const checkProvider = serverSettings.getSettings.pipe(Effect.map((settings) => settings.disabledSkills), Effect.orElseSucceed(() => [])).pipe(Effect.flatMap((disabledSkills) => checkCursorProviderStatus(effectiveConfig, processEnv).pipe(Effect.flatMap((draft) => withProviderSkills(draft, {
102549
+ driver: DRIVER_KIND$3,
102550
+ cwd: serverConfig.cwd,
102551
+ environment: processEnv,
102552
+ disabledSkills
102553
+ })))), Effect.map(stampIdentity), Effect.provideService(Crypto.Crypto, crypto), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, path));
102039
102554
  const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings);
102040
102555
  const snapshot = yield* makeManagedServerProvider({
102041
102556
  maintenanceCapabilities,
@@ -103346,14 +103861,14 @@ const GROK_PRESENTATION = {
103346
103861
  showInteractionModeToggle: false,
103347
103862
  requiresNewThreadForModelChange: true
103348
103863
  };
103349
- const EMPTY_CAPABILITIES$1 = createModelCapabilities({ optionDescriptors: [] });
103864
+ const EMPTY_CAPABILITIES = createModelCapabilities({ optionDescriptors: [] });
103350
103865
  const VERSION_PROBE_TIMEOUT_MS$1 = 4e3;
103351
103866
  const GROK_ACP_MODEL_DISCOVERY_TIMEOUT_MS = 15e3;
103352
103867
  const GROK_BUILT_IN_MODELS = [{
103353
103868
  slug: "grok-build",
103354
103869
  name: "Grok Build",
103355
103870
  isCustom: false,
103356
- capabilities: EMPTY_CAPABILITIES$1
103871
+ capabilities: EMPTY_CAPABILITIES
103357
103872
  }];
103358
103873
  function buildInitialGrokProviderSnapshot(grokSettings) {
103359
103874
  return Effect.gen(function* () {
@@ -103388,7 +103903,7 @@ function buildInitialGrokProviderSnapshot(grokSettings) {
103388
103903
  });
103389
103904
  }
103390
103905
  function grokModelsFromSettings(customModels, builtInModels = GROK_BUILT_IN_MODELS) {
103391
- return providerModelsFromSettings(builtInModels, customModels ?? [], EMPTY_CAPABILITIES$1);
103906
+ return providerModelsFromSettings(builtInModels, customModels ?? [], EMPTY_CAPABILITIES);
103392
103907
  }
103393
103908
  function buildGrokDiscoveredModelsFromSessionModelState(modelState) {
103394
103909
  if (!modelState || modelState.availableModels.length === 0) return [];
@@ -103401,7 +103916,7 @@ function buildGrokDiscoveredModelsFromSessionModelState(modelState) {
103401
103916
  slug,
103402
103917
  name: model.name.trim() || slug,
103403
103918
  isCustom: false,
103404
- capabilities: EMPTY_CAPABILITIES$1
103919
+ capabilities: EMPTY_CAPABILITIES
103405
103920
  };
103406
103921
  }).filter((model) => model !== void 0);
103407
103922
  }
@@ -103576,6 +104091,9 @@ const GrokDriver = {
103576
104091
  const crypto = yield* Crypto.Crypto;
103577
104092
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
103578
104093
  const httpClient = yield* HttpClient.HttpClient;
104094
+ const fileSystem = yield* FileSystem.FileSystem;
104095
+ const path = yield* Path.Path;
104096
+ const serverConfig = yield* ServerConfig$1;
103579
104097
  const serverSettings = yield* ServerSettingsService;
103580
104098
  const eventLoggers = yield* ProviderEventLoggers;
103581
104099
  const processEnv = mergeProviderInstanceEnvironment(environment);
@@ -103604,7 +104122,12 @@ const GrokDriver = {
103604
104122
  instanceId
103605
104123
  });
103606
104124
  const textGeneration = yield* makeGrokTextGeneration(effectiveConfig, processEnv);
103607
- const checkProvider = checkGrokProviderStatus(effectiveConfig, processEnv).pipe(Effect.map(stampIdentity), Effect.provideService(Crypto.Crypto, crypto), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner));
104125
+ const checkProvider = serverSettings.getSettings.pipe(Effect.map((settings) => settings.disabledSkills), Effect.orElseSucceed(() => [])).pipe(Effect.flatMap((disabledSkills) => checkGrokProviderStatus(effectiveConfig, processEnv).pipe(Effect.flatMap((draft) => withProviderSkills(draft, {
104126
+ driver: DRIVER_KIND$2,
104127
+ cwd: serverConfig.cwd,
104128
+ environment: processEnv,
104129
+ disabledSkills
104130
+ })))), Effect.map(stampIdentity), Effect.provideService(Crypto.Crypto, crypto), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, path));
103608
104131
  const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings);
103609
104132
  const snapshot = yield* makeManagedServerProvider({
103610
104133
  maintenanceCapabilities,
@@ -103640,68 +104163,21 @@ const GrokDriver = {
103640
104163
  };
103641
104164
  })
103642
104165
  };
103643
- /**
103644
- * Build the argument vector for one turn.
103645
- *
103646
- * Approval policy is the load-bearing decision here. A headless Muse run has
103647
- * no channel to answer an approval prompt — the CLI only offers
103648
- * `--disable-approval` (keep the OS sandbox) and `--yolo` (drop approval and
103649
- * the sandbox, and trust the workspace). P4Code owns the user-facing runtime
103650
- * mode, so it maps onto those two postures:
103651
- *
103652
- * - `full-access` → `--yolo`
103653
- * - everything else → `--disable-approval --trust-workspace`, leaving the
103654
- * sandbox to contain what runs.
103655
- *
103656
- * `approval-required` cannot be honoured; the adapter emits a
103657
- * `config.warning` for it rather than silently pretending otherwise.
103658
- */
103659
- function buildMuseExecArgs(input) {
103660
- const args = [
103661
- "exec",
103662
- "--json",
103663
- "--session-id",
103664
- input.sessionId
103665
- ];
103666
- if (input.model) args.push("--model", input.model);
103667
- if (input.reasoningEffort) args.push("--reasoning-effort", input.reasoningEffort);
103668
- if (input.baseUrl) args.push("--base-url", input.baseUrl);
103669
- if (input.runtimeMode === "full-access") args.push("--yolo");
103670
- else {
103671
- args.push("--disable-approval", "--trust-workspace");
103672
- switch (input.sandboxMode) {
103673
- case "danger-full-access":
103674
- args.push("--disable-sandbox");
103675
- break;
103676
- case "read-only":
103677
- args.push("--disable-write", "--disable-shell");
103678
- break;
103679
- default: break;
103680
- }
103681
- }
103682
- for (const imagePath of input.imagePaths ?? []) args.push("--image", imagePath);
103683
- args.push(input.prompt);
103684
- return args;
103685
- }
103686
- /** `muse --version` argv, used by the provider status probe. */
103687
- const MUSE_VERSION_ARGS = ["--version"];
103688
- /**
103689
- * Whether a runtime mode asks for interactive approvals that Muse Code
103690
- * cannot deliver headlessly.
103691
- */
103692
- function museApprovalsUnsupported(runtimeMode) {
103693
- return runtimeMode === "approval-required";
103694
- }
103695
104166
  //#endregion
103696
104167
  //#region src/provider/muse/MuseExecEvents.ts
103697
104168
  /**
103698
104169
  * MuseExecEvents — pure translation of `muse exec --json` JSONL records into
103699
104170
  * canonical `ProviderRuntimeEvent`s.
103700
104171
  *
103701
- * Muse Code has no ACP or app-server surface (verified against CLI
103702
- * 0.1.0-R708.1): the only scriptable path is `muse exec --json`, which runs
103703
- * one prompt to completion and prints one JSON object per line. Multi-turn
103704
- * threads are rebuilt by re-invoking with `--session-id`.
104172
+ * P4Code drives Muse through `muse exec --json`, which runs one prompt to
104173
+ * completion and prints one JSON object per line; multi-turn threads are
104174
+ * rebuilt by re-invoking with `--session-id`. Muse Code 1.1.1 also serves a
104175
+ * session host over stdio (`muse serve`, the MSP wire protocol, exported by
104176
+ * `muse schema`) with approval, user-input and steering methods that `exec`
104177
+ * has no channel for. Moving onto MSP is the way to lift those limits; the
104178
+ * mapping below stays on the `exec` stream until then.
104179
+ *
104180
+ * Record shapes here were re-verified against CLI 1.1.1-R2514.1.
103705
104181
  *
103706
104182
  * Everything here is a pure function over a caller-owned state record so the
103707
104183
  * mapping can be tested against captured CLI output without spawning a
@@ -103995,21 +104471,19 @@ function toolCallIdFromIdempotencyKey(key) {
103995
104471
  const callId = key.slice(5).trim();
103996
104472
  return callId.length > 0 ? callId : void 0;
103997
104473
  }
104474
+ /**
104475
+ * Muse's tool names, as the CLI itself reports them (Muse Code 1.1.1). Names
104476
+ * it does not have are not listed: an unknown tool still maps to
104477
+ * `dynamic_tool_call`, which is the right answer for the memory, goal, cron,
104478
+ * skill and todo tools that have no canonical item type here.
104479
+ */
103998
104480
  function itemTypeForToolName(toolName) {
103999
104481
  switch (toolName) {
104000
104482
  case "bash":
104001
- case "shell":
104002
- case "run_command":
104003
- case "unified_exec": return "command_execution";
104483
+ case "bash_input": return "command_execution";
104004
104484
  case "write_file":
104005
- case "edit_file":
104006
- case "multi_edit":
104007
- case "apply_patch":
104008
- case "delete_file":
104009
- case "move_file": return "file_change";
104010
- case "web_search":
104011
- case "web_fetch": return "web_search";
104012
- case "view_image": return "image_view";
104485
+ case "edit_file": return "file_change";
104486
+ case "web_search": return "web_search";
104013
104487
  default:
104014
104488
  if (toolName?.startsWith("subagent_")) return "collab_agent_tool_call";
104015
104489
  if (toolName?.startsWith("mcp__")) return "mcp_tool_call";
@@ -104220,16 +104694,174 @@ const makeMuseTextGeneration = (museSettings, environment = process.env) => Effe
104220
104694
  })
104221
104695
  };
104222
104696
  });
104697
+ /** The one file the overlay owns; everything else links back to the source. */
104698
+ const MUSE_SETTINGS_FILENAME = "settings.json";
104699
+ /** The credential the launcher and the binary both read through this variable. */
104700
+ const MUSE_AUTH_FILENAME = "auth.json";
104701
+ /** The config subdirectory Muse keeps under an XDG config home. */
104702
+ const MUSE_CONFIG_DIRNAME = "muse";
104703
+ /**
104704
+ * The generated settings file carries this session's bearer token, so the
104705
+ * overlay is private to the account running the server.
104706
+ */
104707
+ const OVERLAY_DIRECTORY_MODE = 448;
104708
+ const OVERLAY_SETTINGS_MODE = 384;
104709
+ /**
104710
+ * Registered servers as Muse settings entries.
104711
+ *
104712
+ * SSE has no spelling here — Muse reads an entry carrying `type: "sse"` as no
104713
+ * entry at all — so those are reported as skipped instead of written and
104714
+ * quietly ignored. The reserved names are dropped for the reason every other
104715
+ * adapter drops them: a registration called `p4-code` must not displace the
104716
+ * built-in one and take the session's own tools with it.
104717
+ */
104718
+ function toMuseMcpServers(resolved, reservedNames) {
104719
+ const entries = {};
104720
+ const skipped = [];
104721
+ for (const [name, server] of Object.entries(resolved)) {
104722
+ if (reservedNames.has(name)) continue;
104723
+ if (server.type === "stdio") {
104724
+ entries[name] = {
104725
+ type: "stdio",
104726
+ command: server.command,
104727
+ ...server.args.length > 0 ? { args: [...server.args] } : {},
104728
+ ...Object.keys(server.env).length > 0 ? { env: { ...server.env } } : {}
104729
+ };
104730
+ continue;
104731
+ }
104732
+ if (server.type === "sse") {
104733
+ skipped.push(name);
104734
+ continue;
104735
+ }
104736
+ entries[name] = {
104737
+ type: "http",
104738
+ url: server.url,
104739
+ ...Object.keys(server.headers).length > 0 ? { headers: { ...server.headers } } : {}
104740
+ };
104741
+ }
104742
+ return {
104743
+ entries,
104744
+ skipped
104745
+ };
104746
+ }
104747
+ /**
104748
+ * The config home a CLI following the XDG convention reads, matching what the
104749
+ * spawned process would see: its own `XDG_CONFIG_HOME` when set, else
104750
+ * `~/.config` under the environment's own `HOME`.
104751
+ */
104752
+ function resolveXdgConfigHome(environment) {
104753
+ const configured = environment.XDG_CONFIG_HOME?.trim() ?? "";
104754
+ if (configured.length > 0) return configured;
104755
+ const home = environment.HOME?.trim() ?? "";
104756
+ return `${home.length > 0 ? home : NodeOS.homedir()}/.config`;
104757
+ }
104758
+ /**
104759
+ * The settings document as this module needs to see it: the one key it owns,
104760
+ * with every other key carried through untouched, so an overlay never quietly
104761
+ * drops a model default or a TUI preference the user set.
104762
+ */
104763
+ const MuseSettingsFile = Schema$1.StructWithRest(Schema$1.Struct({
104764
+ schema_version: Schema$1.optional(Schema$1.Number),
104765
+ mcpServers: Schema$1.optional(Schema$1.Unknown)
104766
+ }), [Schema$1.Record(Schema$1.String, Schema$1.Unknown)]);
104767
+ const MuseSettingsFileFromJson = Schema$1.fromJsonString(MuseSettingsFile);
104768
+ const decodeSettingsFile = Schema$1.decodeUnknownExit(MuseSettingsFileFromJson);
104769
+ const encodeSettingsFile = Schema$1.encodeSync(MuseSettingsFileFromJson);
104770
+ /** The user's settings document, or an empty one when there is nothing usable to read. */
104771
+ const readSourceSettings = Effect.fnUntraced(function* (settingsPath) {
104772
+ const contents = yield* (yield* FileSystem.FileSystem).readFileString(settingsPath).pipe(Effect.orElseSucceed(() => void 0));
104773
+ if (contents === void 0) return {};
104774
+ const decoded = decodeSettingsFile(contents);
104775
+ return decoded._tag === "Success" ? decoded.value : {};
104776
+ });
104777
+ /**
104778
+ * Prepare the overlay and return the environment a turn must add to reach it.
104779
+ *
104780
+ * Rebuilt every turn: the endpoint and the bearer token are per session, and a
104781
+ * stale settings file would point the CLI at a credential that no longer
104782
+ * authorizes anything.
104783
+ */
104784
+ const prepareMuseConfigOverlay = Effect.fn("prepareMuseConfigOverlay")(function* (input) {
104785
+ const fileSystem = yield* FileSystem.FileSystem;
104786
+ const path = yield* Path.Path;
104787
+ const sourceMuseDir = path.join(input.sourceConfigHome, MUSE_CONFIG_DIRNAME);
104788
+ const overlayMuseDir = path.join(input.overlayRoot, MUSE_CONFIG_DIRNAME);
104789
+ yield* fileSystem.makeDirectory(overlayMuseDir, {
104790
+ recursive: true,
104791
+ mode: OVERLAY_DIRECTORY_MODE
104792
+ });
104793
+ yield* fileSystem.chmod(input.overlayRoot, OVERLAY_DIRECTORY_MODE).pipe(Effect.ignore);
104794
+ yield* fileSystem.chmod(overlayMuseDir, OVERLAY_DIRECTORY_MODE).pipe(Effect.ignore);
104795
+ const sourceEntries = yield* fileSystem.readDirectory(sourceMuseDir).pipe(Effect.orElseSucceed(() => []));
104796
+ for (const entry of sourceEntries) {
104797
+ if (entry === MUSE_SETTINGS_FILENAME) continue;
104798
+ const link = path.join(overlayMuseDir, entry);
104799
+ if (!(yield* fileSystem.readLink(link).pipe(Effect.as(true), Effect.orElseSucceed(() => false))) && (yield* fileSystem.exists(link))) continue;
104800
+ yield* fileSystem.remove(link, {
104801
+ recursive: true,
104802
+ force: true
104803
+ });
104804
+ yield* fileSystem.symlink(path.join(sourceMuseDir, entry), link).pipe(Effect.ignore);
104805
+ }
104806
+ const sourceSettings = yield* readSourceSettings(path.join(sourceMuseDir, MUSE_SETTINGS_FILENAME));
104807
+ const sourceServers = sourceSettings.mcpServers;
104808
+ const mergedServers = typeof sourceServers === "object" && sourceServers !== null && !Array.isArray(sourceServers) ? { ...sourceServers } : {};
104809
+ const external = toMuseMcpServers(input.externalServers ?? {}, /* @__PURE__ */ new Set([input.mcpServer.name]));
104810
+ Object.assign(mergedServers, external.entries);
104811
+ mergedServers[input.mcpServer.name] = {
104812
+ type: "http",
104813
+ url: input.mcpServer.url,
104814
+ headers: { Authorization: input.mcpServer.authorizationHeader }
104815
+ };
104816
+ const settings = {
104817
+ ...sourceSettings,
104818
+ schema_version: sourceSettings.schema_version ?? 1,
104819
+ mcpServers: mergedServers
104820
+ };
104821
+ const settingsPath = path.join(overlayMuseDir, MUSE_SETTINGS_FILENAME);
104822
+ yield* fileSystem.writeFileString(settingsPath, `${encodeSettingsFile(settings)}\n`, { mode: OVERLAY_SETTINGS_MODE });
104823
+ yield* fileSystem.chmod(settingsPath, OVERLAY_SETTINGS_MODE).pipe(Effect.ignore);
104824
+ return {
104825
+ skippedServers: external.skipped,
104826
+ environment: {
104827
+ XDG_CONFIG_HOME: input.overlayRoot,
104828
+ MUSE_AUTH_PATH: path.join(sourceMuseDir, MUSE_AUTH_FILENAME)
104829
+ }
104830
+ };
104831
+ });
104832
+ /**
104833
+ * Drop what the overlay generated, and nothing else.
104834
+ *
104835
+ * The links are P4Code's to remove; a real file under the overlay is something
104836
+ * Muse wrote during the session — a trust decision, an OAuth token — and it
104837
+ * stays, so the next session on this thread still has it. The directory goes
104838
+ * only when removing the generated file and the links leaves it empty.
104839
+ */
104840
+ const clearMuseConfigOverlay = Effect.fn("clearMuseConfigOverlay")(function* (overlayRoot) {
104841
+ const fileSystem = yield* FileSystem.FileSystem;
104842
+ const path = yield* Path.Path;
104843
+ const overlayMuseDir = path.join(overlayRoot, MUSE_CONFIG_DIRNAME);
104844
+ yield* fileSystem.remove(path.join(overlayMuseDir, MUSE_SETTINGS_FILENAME), { force: true }).pipe(Effect.ignore);
104845
+ const entries = yield* fileSystem.readDirectory(overlayMuseDir).pipe(Effect.orElseSucceed(() => []));
104846
+ for (const entry of entries) {
104847
+ const entryPath = path.join(overlayMuseDir, entry);
104848
+ if (!(yield* fileSystem.readLink(entryPath).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
104849
+ yield* fileSystem.remove(entryPath, { force: true }).pipe(Effect.ignore);
104850
+ }
104851
+ yield* fileSystem.remove(overlayMuseDir).pipe(Effect.ignore);
104852
+ yield* fileSystem.remove(overlayRoot).pipe(Effect.ignore);
104853
+ });
104223
104854
  //#endregion
104224
104855
  //#region src/provider/muse/MuseExecRuntime.ts
104225
104856
  /**
104226
104857
  * MuseExecRuntime — one `muse exec --json` child process per turn.
104227
104858
  *
104228
- * Muse Code has no long-lived protocol surface: a turn is a process, and the
104229
- * thread is rebuilt from the CLI's own event-sourced session log when the
104230
- * next turn passes `--session-id`. This module owns spawning, line framing,
104231
- * interruption, and exit classification; it knows nothing about canonical
104232
- * runtime events.
104859
+ * On the `exec` path a turn is a process, and the thread is rebuilt from the
104860
+ * CLI's own event-sourced session log when the next turn passes
104861
+ * `--session-id`. (Muse Code 1.1.1 does have a long-lived surface —
104862
+ * `muse serve`, the MSP session host which this driver does not use yet.)
104863
+ * This module owns spawning, line framing, interruption, and exit
104864
+ * classification; it knows nothing about canonical runtime events.
104233
104865
  *
104234
104866
  * @module provider/muse/MuseExecRuntime
104235
104867
  */
@@ -104473,11 +105105,12 @@ function nonNegativeInt(value) {
104473
105105
  *
104474
105106
  * Consequences that are visible to users, and deliberate:
104475
105107
  *
104476
- * - **No interactive approvals.** Headless Muse has no channel to answer an
104477
- * approval prompt, so the adapter runs with approval disabled and lets
104478
- * Muse's OS sandbox do the containing. `respondToRequest` fails rather
104479
- * than pretending to route a decision, and `approval-required` threads get
104480
- * a `config.warning`.
105108
+ * - **No interactive approvals.** `muse exec` has no channel to answer an
105109
+ * approval prompt Muse Code 1.1.1 serves approvals over MSP
105110
+ * (`muse serve`), which this adapter does not speak — so the adapter runs
105111
+ * with approval disabled and lets Muse's OS sandbox do the containing.
105112
+ * `respondToRequest` fails rather than pretending to route a decision, and
105113
+ * `approval-required` threads get a `config.warning`.
104481
105114
  * - **No provider-side rollback.** Muse can fork a session interactively but
104482
105115
  * exposes nothing headless.
104483
105116
  * - **Tool arguments are not streamed.** They exist only in the on-disk
@@ -104510,6 +105143,11 @@ function makeMuseAdapter(museSettings, options) {
104510
105143
  const fileSystem = yield* FileSystem.FileSystem;
104511
105144
  const path = yield* Path.Path;
104512
105145
  const serverConfig = yield* Effect.service(ServerConfig$1);
105146
+ /**
105147
+ * Where a thread's generated Muse configuration lives. One directory per
105148
+ * thread because the bearer token inside it is per session.
105149
+ */
105150
+ const overlayRootFor = (threadId) => path.join(serverConfig.stateDir, "muse", "config", threadId);
104513
105151
  const sessions = /* @__PURE__ */ new Map();
104514
105152
  const runtimeEventPubSub = yield* PubSub.unbounded();
104515
105153
  const nowIso = Effect.map(DateTime.now, DateTime.formatIso);
@@ -104617,14 +105255,36 @@ function makeMuseAdapter(museSettings, options) {
104617
105255
  }
104618
105256
  const turnId = TurnId.make(yield* randomUUIDv4);
104619
105257
  const model = input.modelSelection?.model ?? ctx.session.model;
105258
+ const reasoningEffort = input.modelSelection?.instanceId === boundInstanceId ? museReasoningEffort(getModelSelectionStringOptionValue(input.modelSelection, "reasoningEffort")) : void 0;
104620
105259
  const args = buildMuseExecArgs({
104621
105260
  sessionId: ctx.museSessionId,
104622
105261
  prompt,
104623
105262
  model,
105263
+ reasoningEffort,
104624
105264
  baseUrl: museSettings.baseUrl || void 0,
104625
105265
  runtimeMode: ctx.session.runtimeMode,
104626
105266
  imagePaths
104627
105267
  });
105268
+ const mcpSession = readMcpProviderSession(input.threadId);
105269
+ const overlay = mcpSession ? yield* prepareMuseConfigOverlay({
105270
+ overlayRoot: overlayRootFor(input.threadId),
105271
+ sourceConfigHome: resolveXdgConfigHome(environment),
105272
+ mcpServer: {
105273
+ name: P4CODE_MCP_SERVER_NAME,
105274
+ url: mcpSession.endpoint,
105275
+ authorizationHeader: mcpSession.authorizationHeader
105276
+ },
105277
+ externalServers: options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers
105278
+ }).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, path), Effect.orElseSucceed(() => void 0)) : void 0;
105279
+ if (overlay && overlay.skippedServers.length > 0) yield* Effect.logWarning("Muse cannot express these MCP servers", {
105280
+ threadId: input.threadId,
105281
+ servers: overlay.skippedServers,
105282
+ transport: "sse"
105283
+ });
105284
+ const turnEnvironment = {
105285
+ ...environment,
105286
+ ...overlay?.environment
105287
+ };
104628
105288
  const state = makeMuseExecTurnState();
104629
105289
  const mappingContext = {
104630
105290
  provider: PROVIDER$1,
@@ -104655,54 +105315,74 @@ function makeMuseAdapter(museSettings, options) {
104655
105315
  ...model ? { model } : {}
104656
105316
  };
104657
105317
  const turnScope = yield* Scope.make();
104658
- const run = yield* runMuseExec({
105318
+ let runStarted = false;
105319
+ let settled = false;
105320
+ let outcome;
105321
+ /**
105322
+ * Release everything the turn holds, exactly once, whatever ended it:
105323
+ * a clean exit, a typed failure, a defect, or an interrupt from a
105324
+ * stopped thread. Leaving `activeTurn` or a `running` session behind
105325
+ * would wedge the thread — every later `sendTurn` would refuse with
105326
+ * "a Muse turn is already running" until the server restarted — so
105327
+ * this runs from an `ensuring` finalizer rather than the happy path.
105328
+ */
105329
+ const finishTurn = Effect.suspend(() => {
105330
+ if (settled) return Effect.void;
105331
+ settled = true;
105332
+ return Effect.gen(function* () {
105333
+ ctx.activeTurn = void 0;
105334
+ yield* Scope.close(turnScope, Exit.void);
105335
+ if (runStarted && state.terminal === void 0) if (outcome === void 0 || outcome.kind === "interrupted") yield* publish({
105336
+ type: "turn.aborted",
105337
+ ...yield* makeEventStamp(),
105338
+ provider: PROVIDER$1,
105339
+ providerInstanceId: boundInstanceId,
105340
+ threadId: input.threadId,
105341
+ turnId,
105342
+ payload: { reason: "interrupted" }
105343
+ });
105344
+ else {
105345
+ const detail = outcome.kind === "failed" ? outcome.stderr || `\`muse exec\` exited with code ${outcome.exitCode}.` : "`muse exec` ended without a terminal event.";
105346
+ yield* publish({
105347
+ type: "turn.completed",
105348
+ ...yield* makeEventStamp(),
105349
+ provider: PROVIDER$1,
105350
+ providerInstanceId: boundInstanceId,
105351
+ threadId: input.threadId,
105352
+ turnId,
105353
+ payload: {
105354
+ state: "failed",
105355
+ errorMessage: detail
105356
+ }
105357
+ });
105358
+ }
105359
+ if (runStarted) yield* publishTokenUsage(ctx, turnId);
105360
+ const updatedAt = yield* nowIso;
105361
+ const { activeTurnId: _activeTurnId, ...readySession } = ctx.session;
105362
+ ctx.session = {
105363
+ ...readySession,
105364
+ status: "ready",
105365
+ updatedAt
105366
+ };
105367
+ });
105368
+ }).pipe(Effect.ignore);
105369
+ yield* Effect.acquireUseRelease(runMuseExec({
104659
105370
  threadId: input.threadId,
104660
105371
  binaryPath: museSettings.binaryPath || "muse",
104661
105372
  args,
104662
105373
  cwd: ctx.cwd,
104663
- environment,
105374
+ environment: turnEnvironment,
104664
105375
  onLine: handleLine
104665
- }).pipe(Effect.provideService(Scope.Scope, turnScope), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.tapError(() => Scope.close(turnScope, Exit.void)));
104666
- ctx.activeTurn = {
104667
- turnId,
104668
- run,
104669
- state
104670
- };
104671
- const outcome = yield* run.awaitOutcome;
104672
- yield* Scope.close(turnScope, Exit.void);
104673
- ctx.activeTurn = void 0;
104674
- if (state.terminal === void 0) if (outcome.kind === "interrupted") yield* publish({
104675
- type: "turn.aborted",
104676
- ...yield* makeEventStamp(),
104677
- provider: PROVIDER$1,
104678
- providerInstanceId: boundInstanceId,
104679
- threadId: input.threadId,
104680
- turnId,
104681
- payload: { reason: "interrupted" }
104682
- });
104683
- else {
104684
- const detail = outcome.kind === "failed" ? outcome.stderr || `\`muse exec\` exited with code ${outcome.exitCode}.` : "`muse exec` ended without a terminal event.";
104685
- yield* publish({
104686
- type: "turn.completed",
104687
- ...yield* makeEventStamp(),
104688
- provider: PROVIDER$1,
104689
- providerInstanceId: boundInstanceId,
104690
- threadId: input.threadId,
105376
+ }).pipe(Effect.provideService(Scope.Scope, turnScope), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.tapError(() => finishTurn), Effect.tap((run) => Effect.sync(() => {
105377
+ runStarted = true;
105378
+ ctx.activeTurn = {
104691
105379
  turnId,
104692
- payload: {
104693
- state: "failed",
104694
- errorMessage: detail
104695
- }
104696
- });
104697
- }
104698
- yield* publishTokenUsage(ctx, turnId);
104699
- const updatedAt = yield* nowIso;
104700
- const { activeTurnId: _activeTurnId, ...readySession } = ctx.session;
104701
- ctx.session = {
104702
- ...readySession,
104703
- status: "ready",
104704
- updatedAt
104705
- };
105380
+ run,
105381
+ state
105382
+ };
105383
+ }))), (run) => Effect.gen(function* () {
105384
+ outcome = yield* run.awaitOutcome;
105385
+ }), () => finishTurn);
104706
105386
  return {
104707
105387
  threadId: input.threadId,
104708
105388
  turnId,
@@ -104777,6 +105457,7 @@ function makeMuseAdapter(museSettings, options) {
104777
105457
  yield* ctx.activeTurn.run.interrupt.pipe(Effect.ignore);
104778
105458
  ctx.activeTurn = void 0;
104779
105459
  }
105460
+ yield* clearMuseConfigOverlay(overlayRootFor(ctx.threadId)).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, path), Effect.ignore);
104780
105461
  const updatedAt = yield* nowIso;
104781
105462
  ctx.session = {
104782
105463
  ...ctx.session,
@@ -104831,7 +105512,17 @@ const MUSE_PRESENTATION = {
104831
105512
  requiresNewThreadForModelChange: false,
104832
105513
  supportedRuntimeModes: ["auto", "full-access"]
104833
105514
  };
104834
- const EMPTY_CAPABILITIES = createModelCapabilities({ optionDescriptors: [] });
105515
+ const MUSE_CAPABILITIES = createModelCapabilities({ optionDescriptors: [{
105516
+ id: "reasoningEffort",
105517
+ label: "Reasoning",
105518
+ type: "select",
105519
+ options: MUSE_REASONING_EFFORTS.map((effort) => ({
105520
+ id: effort,
105521
+ label: effort.charAt(0).toUpperCase() + effort.slice(1),
105522
+ ...effort === "high" ? { isDefault: true } : {}
105523
+ })),
105524
+ currentValue: MUSE_DEFAULT_REASONING_EFFORT
105525
+ }] });
104835
105526
  const VERSION_PROBE_TIMEOUT_MS = 4e3;
104836
105527
  /** Models Meta documents for Muse Code; used until the CLI caches a catalog. */
104837
105528
  const MUSE_BUILT_IN_MODELS = [
@@ -104839,30 +105530,30 @@ const MUSE_BUILT_IN_MODELS = [
104839
105530
  slug: "muse-spark-1.3",
104840
105531
  name: "Muse Spark 1.3",
104841
105532
  isCustom: false,
104842
- capabilities: EMPTY_CAPABILITIES
105533
+ capabilities: MUSE_CAPABILITIES
104843
105534
  },
104844
105535
  {
104845
105536
  slug: "muse-spark-1.3-contributor",
104846
105537
  name: "Muse Spark 1.3 (contributor)",
104847
105538
  isCustom: false,
104848
105539
  isDefault: true,
104849
- capabilities: EMPTY_CAPABILITIES
105540
+ capabilities: MUSE_CAPABILITIES
104850
105541
  },
104851
105542
  {
104852
105543
  slug: "muse-spark-1.2",
104853
105544
  name: "Muse Spark 1.2",
104854
105545
  isCustom: false,
104855
- capabilities: EMPTY_CAPABILITIES
105546
+ capabilities: MUSE_CAPABILITIES
104856
105547
  },
104857
105548
  {
104858
105549
  slug: "muse-spark-1.2-contributor",
104859
105550
  name: "Muse Spark 1.2 (contributor)",
104860
105551
  isCustom: false,
104861
- capabilities: EMPTY_CAPABILITIES
105552
+ capabilities: MUSE_CAPABILITIES
104862
105553
  }
104863
105554
  ];
104864
105555
  function museModelsFromSettings(customModels, builtInModels = MUSE_BUILT_IN_MODELS) {
104865
- return providerModelsFromSettings(builtInModels, customModels ?? [], EMPTY_CAPABILITIES);
105556
+ return providerModelsFromSettings(builtInModels, customModels ?? [], MUSE_CAPABILITIES);
104866
105557
  }
104867
105558
  const discoverMuseModels = Effect.fn("discoverMuseModels")(function* (environment) {
104868
105559
  return (yield* readMuseModelCatalog(environment)).map((model) => ({
@@ -104870,7 +105561,7 @@ const discoverMuseModels = Effect.fn("discoverMuseModels")(function* (environmen
104870
105561
  name: model.name,
104871
105562
  isCustom: false,
104872
105563
  ...model.isDefault ? { isDefault: true } : {},
104873
- capabilities: EMPTY_CAPABILITIES
105564
+ capabilities: MUSE_CAPABILITIES
104874
105565
  }));
104875
105566
  });
104876
105567
  /**
@@ -105066,6 +105757,7 @@ const MuseDriver = {
105066
105757
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
105067
105758
  const fileSystem = yield* FileSystem.FileSystem;
105068
105759
  const path = yield* Path.Path;
105760
+ const serverConfig = yield* ServerConfig$1;
105069
105761
  const serverSettings = yield* ServerSettingsService;
105070
105762
  const processEnv = mergeProviderInstanceEnvironment(environment);
105071
105763
  const continuationIdentity = defaultProviderContinuationIdentity({
@@ -105088,9 +105780,16 @@ const MuseDriver = {
105088
105780
  });
105089
105781
  const adapter = yield* makeMuseAdapter(effectiveConfig, {
105090
105782
  environment: processEnv,
105091
- instanceId
105783
+ instanceId,
105784
+ resolveMcpServers: (yield* McpRegistry).resolveForSession
105092
105785
  });
105093
105786
  const textGeneration = yield* makeMuseTextGeneration(effectiveConfig, processEnv);
105787
+ const checkProvider = serverSettings.getSettings.pipe(Effect.map((settings) => settings.disabledSkills), Effect.orElseSucceed(() => [])).pipe(Effect.flatMap((disabledSkills) => checkMuseProviderStatus(effectiveConfig, processEnv).pipe(Effect.flatMap((draft) => withProviderSkills(draft, {
105788
+ driver: DRIVER_KIND$1,
105789
+ cwd: serverConfig.cwd,
105790
+ environment: processEnv,
105791
+ disabledSkills
105792
+ })))));
105094
105793
  const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings);
105095
105794
  const snapshot = yield* makeManagedServerProvider({
105096
105795
  maintenanceCapabilities,
@@ -105098,7 +105797,7 @@ const MuseDriver = {
105098
105797
  streamSettings: snapshotSettings.streamSettings,
105099
105798
  haveSettingsChanged: haveProviderSnapshotSettingsChanged,
105100
105799
  initialSnapshot: (settings) => buildInitialMuseProviderSnapshot(settings.provider).pipe(Effect.map(stampIdentity)),
105101
- checkProvider: checkMuseProviderStatus(effectiveConfig, processEnv).pipe(Effect.map(stampIdentity), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, path)),
105800
+ checkProvider: checkProvider.pipe(Effect.map(stampIdentity), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, path)),
105102
105801
  enrichSnapshot: ({ settings, snapshot: currentSnapshot, publishSnapshot }) => enrichMuseSnapshot({
105103
105802
  snapshot: currentSnapshot,
105104
105803
  maintenanceCapabilities,
@@ -106849,6 +107548,8 @@ const BUILT_IN_DRIVERS = [
106849
107548
  defaultConfig: () => decodeOpenCodeSettings({}),
106850
107549
  create: ({ instanceId, displayName, accentColor, environment, enabled, config }) => Effect.gen(function* () {
106851
107550
  const openCodeRuntime = yield* OpenCodeRuntime;
107551
+ const fileSystem = yield* FileSystem.FileSystem;
107552
+ const path = yield* Path.Path;
106852
107553
  const serverConfig = yield* ServerConfig$1;
106853
107554
  const httpClient = yield* HttpClient.HttpClient;
106854
107555
  const serverSettings = yield* ServerSettingsService;
@@ -106879,7 +107580,12 @@ const BUILT_IN_DRIVERS = [
106879
107580
  ...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}
106880
107581
  });
106881
107582
  const textGeneration = yield* makeOpenCodeTextGeneration(effectiveConfig, processEnv);
106882
- const checkProvider = checkOpenCodeProviderStatus(effectiveConfig, serverConfig.cwd, processEnv).pipe(Effect.map(stampIdentity), Effect.provideService(OpenCodeRuntime, openCodeRuntime));
107583
+ const checkProvider = serverSettings.getSettings.pipe(Effect.map((settings) => settings.disabledSkills), Effect.orElseSucceed(() => [])).pipe(Effect.flatMap((disabledSkills) => checkOpenCodeProviderStatus(effectiveConfig, serverConfig.cwd, processEnv).pipe(Effect.flatMap((draft) => withProviderSkills(draft, {
107584
+ driver: DRIVER_KIND,
107585
+ cwd: serverConfig.cwd,
107586
+ environment: processEnv,
107587
+ disabledSkills
107588
+ })))), Effect.map(stampIdentity), Effect.provideService(OpenCodeRuntime, openCodeRuntime), Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, path));
106883
107589
  const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings);
106884
107590
  const snapshot = yield* makeManagedServerProvider({
106885
107591
  maintenanceCapabilities,
@@ -111039,7 +111745,13 @@ const make$4 = Effect.gen(function* () {
111039
111745
  projects: project ? [project] : [],
111040
111746
  chatWorkspaceDir: serverConfig.chatWorkspaceDir
111041
111747
  });
111042
- return [userSkillsDir, ...cwd ? [path.join(cwd, ".claude", "skills")] : []];
111748
+ const [sharedUserDir, sharedProjectDir] = yield* sharedSkillDirectories(cwd ?? void 0);
111749
+ return [
111750
+ ...sharedUserDir !== void 0 && sharedUserDir !== userSkillsDir ? [sharedUserDir] : [],
111751
+ userSkillsDir,
111752
+ ...sharedProjectDir === void 0 ? [] : [sharedProjectDir],
111753
+ ...cwd ? [path.join(cwd, ".claude", "skills")] : []
111754
+ ];
111043
111755
  });
111044
111756
  const resolveThread = Effect.fnUntraced(function* (threadId) {
111045
111757
  return yield* projectionSnapshotQuery.getThreadDetailById(threadId).pipe(Effect.map(Option.getOrUndefined));