thoth-agents 0.11.0 → 0.13.0

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 (95) hide show
  1. package/README.md +16 -5
  2. package/dist/agents/agent-config.d.ts +13 -0
  3. package/dist/agents/index.d.ts +1 -1
  4. package/dist/agents/orchestrator.d.ts +1 -1
  5. package/dist/{chunk-I5TXGNSG.js → chunk-5MYXVRZZ.js} +634 -366
  6. package/dist/chunk-6O4GKDS7.js +207 -0
  7. package/dist/{chunk-RWQPMWEM.js → chunk-JLU3FSBT.js} +17 -2
  8. package/dist/chunk-MIDGZE34.js +107 -0
  9. package/dist/{chunk-HACUJFKI.js → chunk-NOLDFT7O.js} +4 -230
  10. package/dist/{chunk-5WPP6IY7.js → chunk-VVYAQTYE.js} +2 -2
  11. package/dist/{chunk-DNPKVFOB.js → chunk-YCWGTDFE.js} +604 -315
  12. package/dist/cli/config-io.d.ts +2 -2
  13. package/dist/cli/index.js +94 -41
  14. package/dist/cli/install.d.ts +1 -0
  15. package/dist/cli/opencode-profiles.d.ts +10 -0
  16. package/dist/cli/opencode-skill-consent.d.ts +5 -0
  17. package/dist/cli/operations/opencode.d.ts +2 -1
  18. package/dist/cli/operations/types.d.ts +1 -0
  19. package/dist/cli/owned-skills.d.ts +16 -0
  20. package/dist/cli/paths.d.ts +1 -1
  21. package/dist/cli/providers.d.ts +1 -1
  22. package/dist/cli/system.d.ts +3 -1
  23. package/dist/cli/thoth-mem-install.d.ts +10 -10
  24. package/dist/cli/tui/index.js +53 -5
  25. package/dist/cli/tui/operations.d.ts +2 -0
  26. package/dist/cli/types.d.ts +2 -4
  27. package/dist/config/loader.d.ts +2 -5
  28. package/dist/config/schema.d.ts +0 -53
  29. package/dist/harness/adapters/opencode.d.ts +1 -1
  30. package/dist/hooks/delegate-task-retry/hook.d.ts +8 -8
  31. package/dist/hooks/foreground-fallback/index.d.ts +14 -69
  32. package/dist/hooks/index.d.ts +0 -2
  33. package/dist/hooks/json-error-recovery/hook.d.ts +2 -15
  34. package/dist/hooks/oracle-gate-verdict/hook.d.ts +4 -11
  35. package/dist/index.d.ts +7 -4
  36. package/dist/index.js +1603 -1408
  37. package/dist/opencode/agents.d.ts +3 -0
  38. package/dist/opencode/command.d.ts +3 -0
  39. package/dist/opencode/context.d.ts +36 -0
  40. package/dist/opencode/delegation-hooks.d.ts +3 -0
  41. package/dist/opencode/mcp.d.ts +3 -0
  42. package/dist/opencode/permission-rules.d.ts +9 -0
  43. package/dist/opencode/permissions.d.ts +3 -0
  44. package/dist/opencode/profile-rpc-handlers.d.ts +3 -0
  45. package/dist/opencode/profile-rpc.d.ts +688 -0
  46. package/dist/opencode/profile-runtime.d.ts +3 -0
  47. package/dist/opencode/profile-sessions.d.ts +18 -0
  48. package/dist/opencode/profiles/editor.d.ts +44 -0
  49. package/dist/opencode/profiles/index.d.ts +18 -0
  50. package/dist/opencode/profiles/resolver.d.ts +12 -0
  51. package/dist/opencode/profiles/store.d.ts +63 -0
  52. package/dist/opencode/profiles/types.d.ts +26 -0
  53. package/dist/opencode/session-hooks.d.ts +3 -0
  54. package/dist/opencode/session-state.d.ts +39 -0
  55. package/dist/opencode/skills.d.ts +3 -0
  56. package/dist/opencode/tool-overrides/evaluator.d.ts +13 -0
  57. package/dist/opencode/tool-overrides/index.d.ts +5 -0
  58. package/dist/opencode/tool-overrides/store.d.ts +38 -0
  59. package/dist/opencode/tool-overrides/types.d.ts +32 -0
  60. package/dist/opencode/tool-overrides/validation.d.ts +5 -0
  61. package/dist/opencode/tool-overrides/wildcard.d.ts +3 -0
  62. package/dist/opencode/tool-rpc-handlers.d.ts +3 -0
  63. package/dist/opencode/tool-rpc.d.ts +428 -0
  64. package/dist/opencode/tools.d.ts +3 -0
  65. package/dist/pi.js +3 -3
  66. package/dist/tools/ast-grep/cli.d.ts +1 -0
  67. package/dist/tools/ast-grep/index.d.ts +108 -2
  68. package/dist/tools/ast-grep/tools.d.ts +107 -3
  69. package/dist/tools/define-tool.d.ts +23 -0
  70. package/dist/tools/lsp/tools.d.ts +97 -5
  71. package/dist/tui/fixtures.d.ts +2 -0
  72. package/dist/tui/flow.d.ts +16 -0
  73. package/dist/tui/index.d.ts +7 -0
  74. package/dist/tui/index.js +1384 -0
  75. package/dist/tui/model.d.ts +84 -0
  76. package/dist/tui/tools/fixtures.d.ts +3 -0
  77. package/dist/tui/tools/flow.d.ts +11 -0
  78. package/dist/tui/tools/model.d.ts +97 -0
  79. package/dist/utils/index.d.ts +0 -1
  80. package/package.json +18 -3
  81. package/plugin/.claude-plugin/plugin.json +1 -1
  82. package/plugin/.codex-plugin/plugin.json +1 -1
  83. package/plugin/skills/thoth-sdd/SKILL.md +8 -2
  84. package/plugin/skills/thoth-sdd/scripts/validate-result.mjs +28 -2
  85. package/skills/thoth-sdd/SKILL.md +8 -2
  86. package/skills/thoth-sdd/scripts/validate-result.mjs +28 -2
  87. package/thoth-agents.schema.json +0 -96
  88. package/dist/chunk-62L3ORXN.js +0 -30
  89. package/dist/delegation/project-id.d.ts +0 -10
  90. package/dist/hooks/auto-update-checker/checker.d.ts +0 -23
  91. package/dist/hooks/auto-update-checker/constants.d.ts +0 -11
  92. package/dist/hooks/auto-update-checker/index.d.ts +0 -17
  93. package/dist/hooks/auto-update-checker/types.d.ts +0 -22
  94. package/dist/utils/tmux-session-manager.d.ts +0 -63
  95. package/dist/utils/tmux.d.ts +0 -32
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  CONTEXT7_MCP_URL,
3
3
  GREP_APP_MCP_URL,
4
- exa
5
- } from "./chunk-62L3ORXN.js";
4
+ exa,
5
+ spawn
6
+ } from "./chunk-MIDGZE34.js";
6
7
  import {
7
8
  PI_EFFORT_VALUES,
8
9
  PI_MANAGED_OWNER,
@@ -16,7 +17,7 @@ import {
16
17
  savePiModelConfig,
17
18
  syncPiSpecialists,
18
19
  writePiManagedText
19
- } from "./chunk-5WPP6IY7.js";
20
+ } from "./chunk-VVYAQTYE.js";
20
21
  import {
21
22
  ALL_AGENT_NAMES,
22
23
  CLAUDE_CODE_PROMPT_DIALECT,
@@ -25,30 +26,38 @@ import {
25
26
  CONFIRMED_OPENAI_SUBAGENT_PRESET,
26
27
  DEFAULT_MODELS,
27
28
  LEGACY_OPENAI_PRESET,
29
+ MODEL_MAPPINGS,
28
30
  PI_SPECIALIST_ROLES,
29
31
  SUBAGENT_NAMES,
32
+ activeOpenCodeProfile,
30
33
  claudeCodeSubagentType,
34
+ effectiveOpenCodeProfile,
31
35
  ensureConfigDir,
32
36
  findPackageRoot,
33
37
  generateLiteConfig,
34
38
  getAgentOverride,
35
39
  getAgentPackContract,
40
+ getConfigDir,
36
41
  getDefaultOpenCodeModel,
37
42
  getDefaultOpenCodeVariant,
38
43
  getExistingConfigPath,
39
44
  getExistingLiteConfigPath,
40
45
  getOpenCodeManagedModelStatePath,
41
46
  getPrimaryModelId,
47
+ isManagedOpenCodePlugin,
42
48
  isPiSpecialistRole,
43
49
  isValidPackageVersion,
50
+ openCodeProfileStore,
44
51
  parseConfig,
45
52
  piSpecialistName,
53
+ pluginEntries,
46
54
  renderAgentRoutingDescription,
47
55
  resolveExecutingPackageVersion,
48
56
  updateOpenCodeMainConfig,
57
+ withoutLegacyPresets,
49
58
  writeConfig,
50
59
  writeLiteConfig
51
- } from "./chunk-DNPKVFOB.js";
60
+ } from "./chunk-YCWGTDFE.js";
52
61
 
53
62
  // src/cli/model-effort.ts
54
63
  function normalizeEffortSelection(value) {
@@ -4387,47 +4396,35 @@ function recordCompletedInstall(options) {
4387
4396
 
4388
4397
  // src/cli/thoth-mem-install.ts
4389
4398
  import { spawnSync as spawnSync3 } from "child_process";
4390
-
4391
- // src/cli/npx-command.ts
4392
- function getNpxCommand(args, options = {}) {
4393
- if ((options.platform ?? process.platform) !== "win32") {
4394
- return { command: "npx", args: [...args] };
4395
- }
4396
- return {
4397
- command: options.commandShell ?? process.env.ComSpec ?? "cmd.exe",
4398
- args: ["/d", "/s", "/c", ["npx", ...args].join(" ")]
4399
- };
4400
- }
4401
-
4402
- // src/cli/thoth-mem-install.ts
4399
+ var THOTH_MEM_PACKAGE_SPEC = "thoth-mem@>=0.7.0";
4403
4400
  var THOTH_MEM_SETUP_TIMEOUT_MS = 12e4;
4404
4401
  var STATUS_EXIT_CODE = {
4402
+ planned: 0,
4405
4403
  complete: 0,
4406
- failed: 1,
4407
- partial: 2,
4408
- requires_user_action: 3
4404
+ unsupported: 1,
4405
+ "requires-user-action": 1
4409
4406
  };
4410
- var SETUP_STATUSES = new Set(
4411
- Object.keys(STATUS_EXIT_CODE)
4412
- );
4413
- var STEP_OUTCOMES = /* @__PURE__ */ new Set([
4414
- "planned",
4415
- "skipped",
4416
- "confirmed",
4417
- "failed",
4418
- "unavailable"
4419
- ]);
4420
4407
  function isRecord6(value) {
4421
4408
  return typeof value === "object" && value !== null && !Array.isArray(value);
4422
4409
  }
4423
4410
  function isStringArray(value) {
4424
4411
  return Array.isArray(value) && value.every((item) => typeof item === "string");
4425
4412
  }
4426
- function isSetupStep(value) {
4427
- return isRecord6(value) && typeof value.name === "string" && value.name.length > 0 && typeof value.outcome === "string" && STEP_OUTCOMES.has(value.outcome);
4428
- }
4429
4413
  function isProviderSetupResult(value, expectedHarness) {
4430
- return isRecord6(value) && typeof value.status === "string" && SETUP_STATUSES.has(value.status) && typeof value.changed === "boolean" && value.harness === expectedHarness && value.scope === "global" && typeof value.target === "string" && value.target.length > 0 && Array.isArray(value.steps) && value.steps.every(isSetupStep) && isStringArray(value.diagnostics) && isStringArray(value.manual_actions) && (value.receipt === null || typeof value.receipt === "string");
4414
+ if (!isRecord6(value) || typeof value.status !== "string" || !Object.hasOwn(STATUS_EXIT_CODE, value.status) || typeof value.changed !== "boolean" || typeof value.recovered !== "boolean" || value.host !== void 0 && value.host !== expectedHarness || !isStringArray(value.actions) || !isStringArray(value.warnings) || !(value.receiptPath === null || typeof value.receiptPath === "string") || value.diagnostics !== void 0 && !isStringArray(value.diagnostics) || value.restartRequired !== void 0 && typeof value.restartRequired !== "boolean") {
4415
+ return false;
4416
+ }
4417
+ if (expectedHarness === "opencode") {
4418
+ return typeof value.plugin === "string" && typeof value.opencodeVersion === "string" && typeof value.configPath === "string" && typeof value.skillPath === "string" && typeof value.providerConfigPath === "string";
4419
+ }
4420
+ if (value.host !== expectedHarness || typeof value.source !== "string" || typeof value.version !== "string" || !isRecord6(value.verification)) {
4421
+ return false;
4422
+ }
4423
+ const verification = value.verification;
4424
+ if (expectedHarness === "pi") {
4425
+ return typeof value.piVersion === "string" && typeof verification.package === "boolean" && typeof verification.source === "boolean" && typeof verification.manifest === "boolean";
4426
+ }
4427
+ return (value.strategy === "plugin_manager" || value.strategy === "unsupported") && isStringArray(value.diagnostics) && typeof value.restartRequired === "boolean" && typeof verification.marketplace === "boolean" && typeof verification.plugin === "boolean" && typeof verification.enabled === "boolean" && (verification.modelUse === void 0 || verification.modelUse === false);
4431
4428
  }
4432
4429
  function parseProviderSetupResult(stdout, expectedHarness) {
4433
4430
  let value;
@@ -4438,9 +4435,10 @@ function parseProviderSetupResult(stdout, expectedHarness) {
4438
4435
  }
4439
4436
  return isProviderSetupResult(value, expectedHarness) ? value : void 0;
4440
4437
  }
4441
- function defaultCommandExecutor3(command, args, options) {
4438
+ function executeThothMemCommand(command, args, options) {
4442
4439
  const result = spawnSync3(command, [...args], {
4443
4440
  windowsHide: true,
4441
+ windowsVerbatimArguments: process.platform === "win32",
4444
4442
  cwd: options.cwd,
4445
4443
  encoding: "utf8",
4446
4444
  stdio: ["ignore", "pipe", "pipe"],
@@ -4466,9 +4464,9 @@ function invalidResult(harness, command, args, exitCode, error) {
4466
4464
  status: "invalid",
4467
4465
  changed: false,
4468
4466
  harness,
4469
- steps: [],
4467
+ actions: [],
4468
+ warnings: [],
4470
4469
  diagnostics: [],
4471
- manualActions: [],
4472
4470
  receipt: null,
4473
4471
  command,
4474
4472
  args,
@@ -4477,24 +4475,32 @@ function invalidResult(harness, command, args, exitCode, error) {
4477
4475
  };
4478
4476
  }
4479
4477
  function getThothMemSetupCommand(harness, dryRun, options = {}) {
4480
- return getNpxCommand(
4481
- [
4482
- "-y",
4483
- "thoth-mem@latest",
4484
- "setup",
4485
- harness,
4486
- ...dryRun ? ["--plan"] : [],
4487
- "--json"
4488
- ],
4489
- options
4490
- );
4478
+ const args = [
4479
+ "-y",
4480
+ THOTH_MEM_PACKAGE_SPEC,
4481
+ "setup",
4482
+ harness,
4483
+ ...dryRun ? ["--plan"] : [],
4484
+ "--json"
4485
+ ];
4486
+ if ((options.platform ?? process.platform) !== "win32") {
4487
+ return { command: "npx", args };
4488
+ }
4489
+ return {
4490
+ command: options.commandShell ?? process.env.ComSpec ?? "cmd.exe",
4491
+ // Node must not escape this cmd command line: quoted > is a literal,
4492
+ // not redirection, and npx must receive the selector without quote characters.
4493
+ args: [
4494
+ `/d /s /c "npx ${args.map((arg) => arg === THOTH_MEM_PACKAGE_SPEC ? `"${arg}"` : arg).join(" ")}"`
4495
+ ]
4496
+ };
4491
4497
  }
4492
4498
  function runThothMemSetup(options) {
4493
4499
  const { command, args } = getThothMemSetupCommand(
4494
4500
  options.harness,
4495
4501
  options.dryRun ?? false
4496
4502
  );
4497
- const executor = options.commandExecutor ?? defaultCommandExecutor3;
4503
+ const executor = options.commandExecutor ?? executeThothMemCommand;
4498
4504
  let commandResult;
4499
4505
  try {
4500
4506
  commandResult = executor(command, args, {
@@ -4531,34 +4537,37 @@ function runThothMemSetup(options) {
4531
4537
  command,
4532
4538
  args,
4533
4539
  commandResult.exitCode,
4534
- stderr ? `thoth-mem returned invalid JSON evidence: ${stderr.slice(0, 2e3)}` : "thoth-mem returned invalid JSON evidence."
4540
+ stderr ? `thoth-mem setup failed or returned invalid JSON evidence: ${stderr.slice(0, 2e3)}` : "thoth-mem returned invalid JSON evidence."
4535
4541
  );
4536
4542
  }
4537
4543
  const expectedExitCode = STATUS_EXIT_CODE[provider.status];
4538
- const evidenceValid = commandResult.exitCode === expectedExitCode;
4544
+ const expectedStatus = options.dryRun ? "planned" : "complete";
4545
+ const exitConsistent = commandResult.exitCode === expectedExitCode;
4546
+ const modeConsistent = expectedExitCode !== 0 || provider.status === expectedStatus;
4547
+ const evidenceValid = exitConsistent && modeConsistent;
4539
4548
  return {
4540
- success: evidenceValid && provider.status === "complete",
4549
+ success: evidenceValid && provider.status === expectedStatus,
4541
4550
  evidenceValid,
4542
4551
  status: provider.status,
4543
4552
  changed: provider.changed,
4544
- harness: provider.harness,
4545
- target: provider.target,
4546
- steps: provider.steps,
4547
- diagnostics: provider.diagnostics,
4548
- manualActions: provider.manual_actions,
4549
- receipt: provider.receipt,
4553
+ harness: options.harness,
4554
+ actions: provider.actions,
4555
+ warnings: provider.warnings,
4556
+ diagnostics: provider.diagnostics ?? [],
4557
+ restartRequired: provider.restartRequired,
4558
+ receipt: provider.receiptPath,
4550
4559
  command,
4551
4560
  args,
4552
4561
  exitCode: commandResult.exitCode,
4553
4562
  ...!evidenceValid ? {
4554
- error: `thoth-mem exit code ${String(commandResult.exitCode)} contradicts reported status ${provider.status} (expected ${expectedExitCode}).`
4563
+ error: !exitConsistent ? `thoth-mem exit code ${String(commandResult.exitCode)} contradicts reported status ${provider.status} (expected ${expectedExitCode}).` : `thoth-mem reported ${provider.status}; expected ${expectedStatus} for this setup mode.`
4555
4564
  } : {}
4556
4565
  };
4557
4566
  }
4558
4567
 
4559
4568
  // src/cli/install-completion.ts
4560
- function isConsistentProviderSuccess(result, harness) {
4561
- return result.success && result.evidenceValid && result.status === "complete" && result.exitCode === 0 && result.harness === harness;
4569
+ function isConsistentProviderSuccess(result, harness, dryRun) {
4570
+ return result.success && result.evidenceValid && result.status === (dryRun ? "planned" : "complete") && result.exitCode === 0 && result.harness === harness;
4562
4571
  }
4563
4572
  function recordHarnessInstallCompletion(options) {
4564
4573
  const ledgerPath = getInstallLedgerPath(options.ledgerOptions);
@@ -4603,12 +4612,12 @@ function finalizeHarnessInstall(options) {
4603
4612
  dryRun: options.dryRun,
4604
4613
  cwd: options.cwd
4605
4614
  });
4606
- if (!isConsistentProviderSuccess(provider, options.harness)) {
4615
+ if (!isConsistentProviderSuccess(provider, options.harness, options.dryRun)) {
4607
4616
  return {
4608
4617
  success: false,
4609
4618
  provider,
4610
4619
  ledger: { status: "not-attempted", path: ledgerPath },
4611
- error: provider.error ?? "thoth-mem setup did not return consistent complete evidence."
4620
+ error: provider.error ?? "thoth-mem setup did not return consistent setup evidence."
4612
4621
  };
4613
4622
  }
4614
4623
  const completion = recordHarnessInstallCompletion({
@@ -4629,6 +4638,19 @@ import { spawnSync as spawnSync4 } from "child_process";
4629
4638
  import { existsSync as existsSync6 } from "fs";
4630
4639
  import { homedir as homedir3 } from "os";
4631
4640
  import { join as join6 } from "path";
4641
+
4642
+ // src/cli/npx-command.ts
4643
+ function getNpxCommand(args, options = {}) {
4644
+ if ((options.platform ?? process.platform) !== "win32") {
4645
+ return { command: "npx", args: [...args] };
4646
+ }
4647
+ return {
4648
+ command: options.commandShell ?? process.env.ComSpec ?? "cmd.exe",
4649
+ args: ["/d", "/s", "/c", ["npx", ...args].join(" ")]
4650
+ };
4651
+ }
4652
+
4653
+ // src/cli/skills.ts
4632
4654
  var SKILLS_CLI_AGENT = {
4633
4655
  opencode: "opencode",
4634
4656
  codex: "codex",
@@ -4773,12 +4795,26 @@ function getInstallCompletionEvidence(completion, options) {
4773
4795
  code: `${options.codePrefix}-provider-diagnostic`,
4774
4796
  message
4775
4797
  })),
4776
- ...completion.provider.manualActions.map((message) => ({
4798
+ ...completion.provider.warnings.map((message) => ({
4777
4799
  severity: "important",
4778
- code: `${options.codePrefix}-provider-manual-action`,
4800
+ code: `${options.codePrefix}-provider-warning`,
4779
4801
  message
4780
4802
  }))
4781
4803
  ];
4804
+ for (const action of completion.provider.actions) {
4805
+ targets.push({
4806
+ kind: "surface",
4807
+ label: "thoth-mem action",
4808
+ observed: action
4809
+ });
4810
+ }
4811
+ if (completion.provider.restartRequired) {
4812
+ warnings.push({
4813
+ severity: "important",
4814
+ code: `${options.codePrefix}-provider-restart`,
4815
+ message: "thoth-mem: restart required"
4816
+ });
4817
+ }
4782
4818
  if (completion.provider.receipt) {
4783
4819
  targets.push({
4784
4820
  kind: "file",
@@ -6386,19 +6422,18 @@ function resolveOpenCodeEffort(input) {
6386
6422
  }
6387
6423
 
6388
6424
  // src/cli/owned-skills.ts
6425
+ import { randomBytes } from "crypto";
6389
6426
  import {
6390
- cpSync,
6391
6427
  existsSync as existsSync9,
6392
6428
  lstatSync as lstatSync5,
6393
6429
  mkdirSync as mkdirSync4,
6394
- mkdtempSync,
6395
6430
  readFileSync as readFileSync8,
6431
+ realpathSync as realpathSync2,
6396
6432
  renameSync as renameSync3,
6397
- rmSync as rmSync4,
6398
6433
  statSync
6399
6434
  } from "fs";
6400
6435
  import { homedir as homedir4 } from "os";
6401
- import { dirname as dirname6, join as join7 } from "path";
6436
+ import { dirname as dirname6, join as join7, resolve as resolve5, sep as sep2 } from "path";
6402
6437
  import { fileURLToPath as fileURLToPath4 } from "url";
6403
6438
 
6404
6439
  // src/harness/core/owned-skills.ts
@@ -6491,7 +6526,7 @@ function getOpenCodeOwnedSkillEntries(options = {}) {
6491
6526
  return THOTH_OWNED_SKILL_NAMES.map((name) => ({
6492
6527
  name,
6493
6528
  sourcePath: join7(packageRoot, "skills", name),
6494
- destinationPath: join7(homeDir, ".config", "opencode", "skills", name)
6529
+ destinationPath: join7(getConfigDir(homeDir), "skills", name)
6495
6530
  }));
6496
6531
  }
6497
6532
  function getPiOwnedSkillEntries(options) {
@@ -6513,56 +6548,126 @@ function validateCanonicalSkill(entry) {
6513
6548
  throw new Error(`Canonical owned skill contract is missing: ${entry.name}`);
6514
6549
  }
6515
6550
  }
6516
- function installOwnedSkills(skills) {
6517
- const firstSkill = skills[0];
6518
- if (!firstSkill) return;
6519
- const globalRoot = dirname6(firstSkill.destinationPath);
6520
- mkdirSync4(globalRoot, { recursive: true });
6521
- const transactionRoot = mkdtempSync(join7(globalRoot, ".thoth-agents-owned-"));
6522
- const stagedRoot = join7(transactionRoot, "staged");
6523
- const backupRoot = join7(transactionRoot, "backup");
6524
- mkdirSync4(stagedRoot, { recursive: true });
6525
- mkdirSync4(backupRoot, { recursive: true });
6526
- try {
6527
- for (const skill of skills) {
6528
- cpSync(skill.sourcePath, join7(stagedRoot, skill.name), {
6529
- recursive: true,
6530
- force: true
6531
- });
6551
+ function getLegacyOpenCodeSkillBackupRoot(homeDir = homedir4()) {
6552
+ return resolve5(
6553
+ process.env.XDG_DATA_HOME || join7(homeDir, ".local", "share"),
6554
+ "thoth-agents",
6555
+ ".opencode-legacy-skills"
6556
+ );
6557
+ }
6558
+ function inspectLegacyOpenCodeSkills(homeDir = homedir4()) {
6559
+ const seen = /* @__PURE__ */ new Set();
6560
+ const copies = [];
6561
+ for (const [label, root] of [
6562
+ ["resolved", getConfigDir(homeDir)],
6563
+ ["legacy", join7(homeDir, ".config", "opencode")]
6564
+ ]) {
6565
+ for (const directory of ["skills", "skill"]) {
6566
+ for (const name of THOTH_OWNED_SKILL_NAMES) {
6567
+ const sourcePath = join7(root, directory, name);
6568
+ if (!existsSync9(sourcePath)) continue;
6569
+ const real = realpathSync2(sourcePath);
6570
+ if (seen.has(real)) continue;
6571
+ seen.add(real);
6572
+ copies.push({ name, sourcePath, label: `${label}-${directory}` });
6573
+ }
6532
6574
  }
6533
- for (const skill of skills) {
6534
- const stagedPath = join7(stagedRoot, skill.name);
6535
- const backupPath = join7(backupRoot, skill.name);
6536
- const hadExistingDestination = existsSync9(skill.destinationPath);
6537
- if (hadExistingDestination) {
6538
- renameSync3(skill.destinationPath, backupPath);
6575
+ }
6576
+ return copies;
6577
+ }
6578
+ function planOrMoveLegacySkills(options) {
6579
+ const backupRoot = getLegacyOpenCodeSkillBackupRoot(options.homeDir);
6580
+ const copies = inspectLegacyOpenCodeSkills(options.homeDir);
6581
+ const notices = [];
6582
+ const legacyMoves = [];
6583
+ if (!copies.length) return { backupRoot, notices, legacyMoves };
6584
+ const confirmed = options.moveLegacyOpenCodeSkills && !options.dryRun;
6585
+ let run = join7(backupRoot, "<new-run>");
6586
+ if (confirmed) {
6587
+ try {
6588
+ if (!resolve5(backupRoot).split(sep2).includes(".opencode-legacy-skills"))
6589
+ throw new Error(
6590
+ "Backup path is missing the required hidden .opencode-legacy-skills segment"
6591
+ );
6592
+ mkdirSync4(backupRoot, { recursive: true });
6593
+ let created = false;
6594
+ for (let attempt = 0; attempt < 10; attempt++) {
6595
+ run = join7(
6596
+ backupRoot,
6597
+ `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${randomBytes(8).toString("hex")}`
6598
+ );
6599
+ try {
6600
+ mkdirSync4(run, { mode: 448 });
6601
+ created = true;
6602
+ break;
6603
+ } catch (error) {
6604
+ if (error.code !== "EEXIST") throw error;
6605
+ }
6539
6606
  }
6607
+ if (!created)
6608
+ throw new Error("Unable to create an exclusive backup run directory");
6609
+ } catch (error) {
6610
+ notices.push(`Legacy skill move refused: ${String(error)}`);
6611
+ notices.push(
6612
+ ...copies.map(
6613
+ ({ sourcePath }) => legacyOpenCodeSkillNotice(sourcePath)
6614
+ )
6615
+ );
6616
+ return { backupRoot, notices, legacyMoves };
6617
+ }
6618
+ }
6619
+ for (const copy of copies) {
6620
+ const destinationPath = join7(run, copy.label, copy.name);
6621
+ const move = {
6622
+ sourcePath: copy.sourcePath,
6623
+ destinationPath,
6624
+ status: "planned"
6625
+ };
6626
+ legacyMoves.push(move);
6627
+ if (!confirmed) {
6628
+ notices.push(legacyOpenCodeSkillNotice(copy.sourcePath));
6629
+ notices.push(
6630
+ `Planned move: ${copy.sourcePath} -> ${destinationPath} (backup root: ${backupRoot})`
6631
+ );
6632
+ continue;
6633
+ }
6634
+ try {
6635
+ mkdirSync4(dirname6(destinationPath), { recursive: true });
6540
6636
  try {
6541
- renameSync3(stagedPath, skill.destinationPath);
6637
+ lstatSync5(destinationPath);
6638
+ throw new Error(
6639
+ `Backup destination already exists: ${destinationPath}`
6640
+ );
6542
6641
  } catch (error) {
6543
- if (existsSync9(skill.destinationPath)) {
6544
- rmSync4(skill.destinationPath, { recursive: true, force: true });
6545
- }
6546
- if (hadExistingDestination && existsSync9(backupPath)) {
6547
- renameSync3(backupPath, skill.destinationPath);
6548
- }
6549
- throw error;
6550
- }
6551
- if (existsSync9(backupPath)) {
6552
- rmSync4(backupPath, { recursive: true, force: true });
6642
+ if (error.code !== "ENOENT") throw error;
6553
6643
  }
6644
+ renameSync3(copy.sourcePath, destinationPath);
6645
+ move.status = "moved";
6646
+ notices.push(
6647
+ `Moved legacy OpenCode skill: ${copy.sourcePath} -> ${destinationPath} (backup: ${run})`
6648
+ );
6649
+ } catch (error) {
6650
+ move.status = "failed";
6651
+ notices.push(
6652
+ `Legacy skill move failed; original left intact: ${copy.sourcePath}: ${String(error)}. ${legacyOpenCodeSkillNotice(copy.sourcePath)}`
6653
+ );
6554
6654
  }
6555
- } finally {
6556
- rmSync4(transactionRoot, { recursive: true, force: true });
6557
6655
  }
6656
+ return { backupRoot, notices, legacyMoves };
6657
+ }
6658
+ function legacyOpenCodeSkillNotice(path) {
6659
+ return `Warning: legacy OpenCode skill copies shadow the packaged skills: ${path}. Confirm the backup move interactively or pass --move-legacy-opencode-skills to install/update.`;
6558
6660
  }
6559
6661
  function syncOpenCodeOwnedSkills(options = {}) {
6560
6662
  const skills = getOpenCodeOwnedSkillEntries(options);
6561
6663
  try {
6562
6664
  for (const skill of skills) validateCanonicalSkill(skill);
6563
- if (options.dryRun) return { success: true, status: "planned", skills };
6564
- installOwnedSkills(skills);
6565
- return { success: true, status: "installed", skills };
6665
+ return {
6666
+ success: true,
6667
+ status: options.dryRun ? "planned" : "installed",
6668
+ skills,
6669
+ ...planOrMoveLegacySkills(options)
6670
+ };
6566
6671
  } catch (error) {
6567
6672
  return {
6568
6673
  success: false,
@@ -6604,12 +6709,184 @@ function inspectPiPackageSkills(options) {
6604
6709
  }
6605
6710
  }
6606
6711
 
6712
+ // src/cli/system.ts
6713
+ import { spawnSync as spawnSync5 } from "child_process";
6714
+ import { statSync as statSync2 } from "fs";
6715
+ var cachedOpenCodePath = null;
6716
+ var OPENCODE_PROBE_TIMEOUT_MS = 1500;
6717
+ function quoteWindowsShellCommand(command) {
6718
+ if (!/[\s&()^<>"|]/.test(command)) {
6719
+ return command;
6720
+ }
6721
+ return `"${command.replace(/"/g, '\\"')}"`;
6722
+ }
6723
+ function getOpenCodeVersionInvocation(opencodePath, platform = process.platform) {
6724
+ const options = {
6725
+ stdout: "pipe",
6726
+ stderr: "pipe"
6727
+ };
6728
+ if (platform === "win32") {
6729
+ return {
6730
+ command: [`${quoteWindowsShellCommand(opencodePath)} --version`],
6731
+ options: { ...options, shell: true }
6732
+ };
6733
+ }
6734
+ return { command: [opencodePath, "--version"], options };
6735
+ }
6736
+ function getOpenCodePaths() {
6737
+ const home = process.env.HOME || process.env.USERPROFILE || "";
6738
+ return [
6739
+ // PATH (try this first)
6740
+ "opencode",
6741
+ // User local installations (Linux & macOS)
6742
+ `${home}/.local/bin/opencode`,
6743
+ `${home}/.opencode/bin/opencode`,
6744
+ `${home}/bin/opencode`,
6745
+ // System-wide installations
6746
+ "/usr/local/bin/opencode",
6747
+ "/opt/opencode/bin/opencode",
6748
+ "/usr/bin/opencode",
6749
+ "/bin/opencode",
6750
+ // macOS specific
6751
+ "/Applications/OpenCode.app/Contents/MacOS/opencode",
6752
+ `${home}/Applications/OpenCode.app/Contents/MacOS/opencode`,
6753
+ // Homebrew (macOS & Linux)
6754
+ "/opt/homebrew/bin/opencode",
6755
+ "/home/linuxbrew/.linuxbrew/bin/opencode",
6756
+ `${home}/homebrew/bin/opencode`,
6757
+ // macOS user Library
6758
+ `${home}/Library/Application Support/opencode/bin/opencode`,
6759
+ // Snap (Linux)
6760
+ "/snap/bin/opencode",
6761
+ "/var/snap/opencode/current/bin/opencode",
6762
+ // Flatpak (Linux)
6763
+ "/var/lib/flatpak/exports/bin/ai.opencode.OpenCode",
6764
+ `${home}/.local/share/flatpak/exports/bin/ai.opencode.OpenCode`,
6765
+ // Nix (Linux/macOS)
6766
+ "/nix/store/opencode/bin/opencode",
6767
+ `${home}/.nix-profile/bin/opencode`,
6768
+ "/run/current-system/sw/bin/opencode",
6769
+ // Cargo (Rust toolchain)
6770
+ `${home}/.cargo/bin/opencode`,
6771
+ // npm/npx global
6772
+ `${home}/.npm-global/bin/opencode`,
6773
+ "/usr/local/lib/node_modules/opencode/bin/opencode",
6774
+ // Yarn global
6775
+ `${home}/.yarn/bin/opencode`,
6776
+ // PNPM
6777
+ `${home}/.pnpm-global/bin/opencode`
6778
+ ];
6779
+ }
6780
+ function resolveOpenCodePath() {
6781
+ if (cachedOpenCodePath) {
6782
+ return cachedOpenCodePath;
6783
+ }
6784
+ const paths = getOpenCodePaths();
6785
+ for (const opencodePath of paths) {
6786
+ if (opencodePath === "opencode") continue;
6787
+ try {
6788
+ const stat = statSync2(opencodePath);
6789
+ if (stat.isFile()) {
6790
+ cachedOpenCodePath = opencodePath;
6791
+ return opencodePath;
6792
+ }
6793
+ } catch {
6794
+ }
6795
+ }
6796
+ return "opencode";
6797
+ }
6798
+ async function isOpenCodeInstalled() {
6799
+ const paths = getOpenCodePaths();
6800
+ for (const opencodePath of paths) {
6801
+ try {
6802
+ const invocation = getOpenCodeVersionInvocation(opencodePath);
6803
+ const proc = spawn(invocation.command, {
6804
+ ...invocation.options,
6805
+ windowsHide: true
6806
+ });
6807
+ let timer;
6808
+ const timedOut = /* @__PURE__ */ Symbol("timeout");
6809
+ try {
6810
+ const outcome = await Promise.race([
6811
+ proc.exited.then(() => proc.exitCode),
6812
+ new Promise((resolve11) => {
6813
+ timer = setTimeout(
6814
+ () => resolve11(timedOut),
6815
+ OPENCODE_PROBE_TIMEOUT_MS
6816
+ );
6817
+ })
6818
+ ]);
6819
+ if (outcome === timedOut) {
6820
+ proc.kill();
6821
+ continue;
6822
+ }
6823
+ if (outcome === 0) {
6824
+ cachedOpenCodePath = opencodePath;
6825
+ return true;
6826
+ }
6827
+ } finally {
6828
+ if (timer) {
6829
+ clearTimeout(timer);
6830
+ }
6831
+ }
6832
+ } catch {
6833
+ }
6834
+ }
6835
+ return false;
6836
+ }
6837
+ var OPENCODE_VERSION_REQUIREMENT = "OpenCode 2.2.6 or later is required; install or upgrade OpenCode before applying configuration.";
6838
+ function isSupportedOpenCodeVersion(output) {
6839
+ const match = /^(?:opencode\s+)?v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/iu.exec(
6840
+ output?.trim() ?? ""
6841
+ );
6842
+ if (!match) return false;
6843
+ const [, major, minor, patch] = match;
6844
+ return Number(major) > 2 || Number(major) === 2 && (Number(minor) > 2 || Number(minor) === 2 && Number(patch) >= 6);
6845
+ }
6846
+ function getOpenCodeVersionSync() {
6847
+ try {
6848
+ const invocation = getOpenCodeVersionInvocation(resolveOpenCodePath());
6849
+ const [command, ...args] = invocation.command;
6850
+ if (!command) return null;
6851
+ const result = spawnSync5(command, args, {
6852
+ shell: invocation.options.shell,
6853
+ windowsHide: true,
6854
+ encoding: "utf8",
6855
+ timeout: OPENCODE_PROBE_TIMEOUT_MS
6856
+ });
6857
+ return result.status === 0 ? result.stdout.trim() : null;
6858
+ } catch {
6859
+ return null;
6860
+ }
6861
+ }
6862
+ async function getOpenCodeVersion() {
6863
+ const opencodePath = resolveOpenCodePath();
6864
+ try {
6865
+ const invocation = getOpenCodeVersionInvocation(opencodePath);
6866
+ const proc = spawn(invocation.command, {
6867
+ ...invocation.options,
6868
+ windowsHide: true
6869
+ });
6870
+ const output = await new Response(proc.stdout).text();
6871
+ await proc.exited;
6872
+ if (proc.exitCode === 0) {
6873
+ return output.trim();
6874
+ }
6875
+ } catch {
6876
+ }
6877
+ return null;
6878
+ }
6879
+ function getOpenCodePath() {
6880
+ const path = resolveOpenCodePath();
6881
+ return path === "opencode" ? null : path;
6882
+ }
6883
+
6607
6884
  // src/cli/operations/opencode.ts
6608
6885
  var PACKAGE_NAME = "thoth-agents";
6609
6886
  var EXECUTING_PACKAGE_VERSION = resolveExecutingPackageVersion();
6610
6887
  var EXPECTED_PLUGIN = EXECUTING_PACKAGE_VERSION.ok ? `${PACKAGE_NAME}@${EXECUTING_PACKAGE_VERSION.version}` : null;
6611
- var OPENAI_PRESET = "openai";
6612
- var APPLIED_MODELS_PRESET = "agents";
6888
+ var OPENAI_PROFILE = "openai";
6889
+ var APPLIED_MODELS_PROFILE = "agents";
6613
6890
  var ROLE_NAMES = [...ALL_AGENT_NAMES];
6614
6891
  var issuedOpenCodePlans = /* @__PURE__ */ new WeakMap();
6615
6892
  var NonCanonicalDataError = class extends Error {
@@ -6813,7 +7090,7 @@ var opencodeOperationAdapter = {
6813
7090
  id: "opencode",
6814
7091
  displayName: "OpenCode",
6815
7092
  available: true,
6816
- description: "OpenCode plugin and configuration surfaces.",
7093
+ description: "OpenCode 2.2.6 or later: pinned plugin and configuration surfaces.",
6817
7094
  actions: openCodeActions
6818
7095
  };
6819
7096
  function targetForMainConfig(state) {
@@ -6822,7 +7099,7 @@ function targetForMainConfig(state) {
6822
7099
  path: getExistingConfigPath(),
6823
7100
  label: "OpenCode config",
6824
7101
  ...state ? { state } : {},
6825
- expected: EXPECTED_PLUGIN ? `plugin includes ${EXPECTED_PLUGIN}` : "plugin version unavailable"
7102
+ expected: EXPECTED_PLUGIN ? `plugins includes ${EXPECTED_PLUGIN}` : "plugins version unavailable"
6826
7103
  };
6827
7104
  }
6828
7105
  function targetForLiteConfig(state) {
@@ -6831,7 +7108,26 @@ function targetForLiteConfig(state) {
6831
7108
  path: getExistingLiteConfigPath(),
6832
7109
  label: "thoth-agents config",
6833
7110
  ...state ? { state } : {},
6834
- expected: `six-role ${OPENAI_PRESET} roster`
7111
+ expected: `six-role ${OPENAI_PROFILE} roster`
7112
+ };
7113
+ }
7114
+ function targetForProfiles() {
7115
+ return {
7116
+ kind: "config",
7117
+ path: openCodeProfileStore().paths.global,
7118
+ label: "OpenCode model profiles",
7119
+ expected: "complete active model profile"
7120
+ };
7121
+ }
7122
+ function shippedProfilePlanItem() {
7123
+ return {
7124
+ title: "Sync built-in OpenAI model profile (preserve customized roles and valid active selection)",
7125
+ target: targetForProfiles(),
7126
+ preview: JSON.stringify(
7127
+ { version: 1, profiles: { openai: { agents: MODEL_MAPPINGS.openai } } },
7128
+ null,
7129
+ 2
7130
+ )
6835
7131
  };
6836
7132
  }
6837
7133
  function titleCaseSkillName(value) {
@@ -6866,18 +7162,21 @@ function openCodeSkillTargets(context2) {
6866
7162
  observed: installed ? "required global skill installed" : "required global skill missing"
6867
7163
  };
6868
7164
  });
7165
+ const legacyCopies = inspectLegacyOpenCodeSkills(homeDir);
6869
7166
  const ownedTargets = getOpenCodeOwnedSkillEntries({
6870
7167
  homeDir
6871
7168
  }).map((skill) => {
6872
- const path = join8(skill.destinationPath, "SKILL.md");
7169
+ const path = join8(skill.sourcePath, "SKILL.md");
6873
7170
  const installed = existsSync10(path);
6874
7171
  return {
6875
7172
  kind: "skill",
6876
7173
  path,
6877
7174
  label: titleCaseSkillName(skill.name),
6878
7175
  state: installed ? "installed" : "missing",
6879
- expected: "required global OpenCode skill",
6880
- observed: installed ? "thoth-owned global skill installed" : "thoth-owned global skill missing"
7176
+ expected: "package-provided OpenCode skill",
7177
+ observed: `${installed ? "package-provided skill available" : "package-provided skill missing"}${legacyCopies.filter((copy) => copy.name === skill.name).map(
7178
+ (copy) => `; Warning: ${copy.sourcePath} shadows packaged skills; planned move to ${getLegacyOpenCodeSkillBackupRoot(homeDir)}/<new-run>/${copy.label}/${copy.name}; confirm with --move-legacy-opencode-skills`
7179
+ ).join("")}`
6881
7180
  };
6882
7181
  });
6883
7182
  const diagnostics = [];
@@ -6896,55 +7195,39 @@ function openCodeSkillTargets(context2) {
6896
7195
  };
6897
7196
  }
6898
7197
  function configPluginMarker(config) {
6899
- const plugins = Array.isArray(config?.plugin) ? config.plugin : [];
6900
- return plugins.length > 0 ? `plugin: ${JSON.stringify(plugins)}` : "plugin: []";
7198
+ return `plugins: ${JSON.stringify(pluginEntries(config?.plugins))}; legacy plugin: ${JSON.stringify(pluginEntries(config?.plugin))}`;
6901
7199
  }
6902
7200
  function hasExpectedPlugin(config) {
6903
- return EXPECTED_PLUGIN !== null && Array.isArray(config?.plugin) && config.plugin.includes(EXPECTED_PLUGIN);
7201
+ const current = pluginEntries(config?.plugins);
7202
+ return EXPECTED_PLUGIN !== null && current.filter(isManagedOpenCodePlugin).length === 1 && current.includes(EXPECTED_PLUGIN) && !pluginEntries(config?.plugin).some(isManagedOpenCodePlugin);
6904
7203
  }
6905
7204
  function hasManagedPluginDrift(config) {
6906
- return Array.isArray(config?.plugin) && config.plugin.some(
6907
- (plugin) => plugin === PACKAGE_NAME || plugin.startsWith(`${PACKAGE_NAME}@`)
7205
+ return [...pluginEntries(config?.plugins), ...pluginEntries(config?.plugin)].some(
7206
+ isManagedOpenCodePlugin
6908
7207
  ) && !hasExpectedPlugin(config);
6909
7208
  }
6910
- function liteConfigMarker(config) {
6911
- const preset = typeof config?.preset === "string" ? config.preset : void 0;
6912
- const presets = configRecord(config?.presets);
6913
- const managedPreset = preset === OPENAI_PRESET || preset === APPLIED_MODELS_PRESET ? configRecord(presets[preset]) : {};
6914
- const legacyAgents = configRecord(config?.agents);
6915
- const roles = ROLE_NAMES.filter(
6916
- (role) => role in managedPreset || role in legacyAgents
6917
- );
6918
- return `preset: ${preset ?? "none"}; observed roles: ${roles.join(", ")}`;
7209
+ function liteConfigMarker(_config) {
7210
+ const { name, profile } = activeOpenCodeProfile();
7211
+ const roles = ROLE_NAMES.filter((role) => role in (profile?.agents ?? {}));
7212
+ return `profile: ${name ?? "none"}; observed roles: ${roles.join(", ")}`;
6919
7213
  }
6920
- function hasManagedSevenAgentPreset(config) {
6921
- if (!config || config.preset !== OPENAI_PRESET && config.preset !== APPLIED_MODELS_PRESET) {
6922
- return false;
6923
- }
6924
- const presets = configRecord(config.presets);
6925
- return hasCompleteAgentRoster(presets[config.preset]);
7214
+ function hasManagedProfile(_config) {
7215
+ const { name, profile } = activeOpenCodeProfile();
7216
+ return (name === OPENAI_PROFILE || name === APPLIED_MODELS_PROFILE) && hasCompleteAgentRoster(profile?.agents);
6926
7217
  }
6927
- function hasSelectedNamedPreset(config) {
6928
- if (!config || typeof config.preset !== "string" || config.preset.length === 0 || config.preset === OPENAI_PRESET || config.preset === APPLIED_MODELS_PRESET) {
6929
- return false;
6930
- }
6931
- if (!config.presets || typeof config.presets !== "object" || Array.isArray(config.presets)) {
6932
- return false;
6933
- }
6934
- const presets = config.presets;
6935
- if (!Object.hasOwn(presets, config.preset)) return false;
6936
- const selectedPreset = presets[config.preset];
6937
- return selectedPreset !== null && typeof selectedPreset === "object" && !Array.isArray(selectedPreset);
7218
+ function hasSelectedNamedProfile(_config) {
7219
+ const { name, profile } = activeOpenCodeProfile();
7220
+ return !!profile && name !== OPENAI_PROFILE && name !== APPLIED_MODELS_PROFILE;
6938
7221
  }
6939
7222
  function hasLegacySevenAgentRoster(config) {
6940
- return config?.preset === APPLIED_MODELS_PRESET && !hasManagedSevenAgentPreset(config) && hasCompleteAgentRoster(config.agents);
7223
+ return !hasManagedProfile(config) && hasCompleteAgentRoster(config?.agents);
6941
7224
  }
6942
- var ACTIVE_PRESET_SELECTED_CODE = "opencode-active-preset-selected";
6943
- function activePresetSelectedDiagnostic() {
7225
+ var ACTIVE_PROFILE_SELECTED_CODE = "opencode-active-profile-selected";
7226
+ function activeProfileSelectedDiagnostic() {
6944
7227
  return {
6945
7228
  severity: "important",
6946
- message: "A valid named preset is active; model configuration preserves it while sync or install may repair the managed roster.",
6947
- code: ACTIVE_PRESET_SELECTED_CODE
7229
+ message: "A valid named profile is active; model configuration preserves it while sync or install may repair the managed roster.",
7230
+ code: ACTIVE_PROFILE_SELECTED_CODE
6948
7231
  };
6949
7232
  }
6950
7233
  var REPAIRABLE_DIAGNOSTIC_CODES = /* @__PURE__ */ new Set([
@@ -6952,7 +7235,7 @@ var REPAIRABLE_DIAGNOSTIC_CODES = /* @__PURE__ */ new Set([
6952
7235
  "opencode-main-config-missing",
6953
7236
  "opencode-lite-config-missing",
6954
7237
  "opencode-roster-drift",
6955
- ACTIVE_PRESET_SELECTED_CODE,
7238
+ ACTIVE_PROFILE_SELECTED_CODE,
6956
7239
  "opencode-required-skills-missing"
6957
7240
  ]);
6958
7241
  var MODEL_PRECONFIG_SKILL_CODES = /* @__PURE__ */ new Set([
@@ -6985,7 +7268,7 @@ function targetsForBlockingDiagnostic(diagnostic, status) {
6985
7268
  if (code === "opencode-plugin-drift" || code === "opencode-main-config-missing" || code === "opencode-config-parse-error" || code === "opencode-unmanaged-config") {
6986
7269
  return main ? [main] : [];
6987
7270
  }
6988
- if (code === "opencode-roster-drift" || code === ACTIVE_PRESET_SELECTED_CODE || code === "opencode-lite-config-missing" || code === "thoth-config-parse-error" || code === "opencode-roster-unrecognized") {
7271
+ if (code === "opencode-roster-drift" || code === ACTIVE_PROFILE_SELECTED_CODE || code === "opencode-lite-config-missing" || code === "thoth-config-parse-error" || code === "opencode-roster-unrecognized") {
6989
7272
  return lite ? [lite] : [];
6990
7273
  }
6991
7274
  if (code === "opencode-required-skills-missing") {
@@ -7013,7 +7296,7 @@ function blockingDetails(action, status, includeRepairable = false) {
7013
7296
  const diagnostics = status.diagnostics.filter((diagnostic) => {
7014
7297
  if (diagnostic.severity === "critical") return true;
7015
7298
  if (diagnostic.severity === "minor") return false;
7016
- if (!includeRepairable && action === "model-config" && diagnostic.code === ACTIVE_PRESET_SELECTED_CODE) {
7299
+ if (!includeRepairable && action === "model-config" && diagnostic.code === ACTIVE_PROFILE_SELECTED_CODE) {
7017
7300
  return false;
7018
7301
  }
7019
7302
  if (!includeRepairable && allowModelPreconfiguration && diagnostic.code !== void 0 && MODEL_PRECONFIG_SKILL_CODES.has(diagnostic.code)) {
@@ -7094,16 +7377,16 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
7094
7377
  };
7095
7378
  if (!mainExists || !hasExpectedPlugin(main.config)) {
7096
7379
  if (hasManagedPluginDrift(main.config)) {
7097
- const currentRoster = hasManagedSevenAgentPreset(lite.config);
7380
+ const currentRoster = hasManagedProfile(lite.config);
7098
7381
  const legacyRoster = hasLegacySevenAgentRoster(lite.config);
7099
- const selectedNamedPreset = hasSelectedNamedPreset(lite.config);
7100
- const recognizedRoster = currentRoster || legacyRoster || selectedNamedPreset;
7382
+ const selectedNamedProfile = hasSelectedNamedProfile(lite.config);
7383
+ const recognizedRoster = currentRoster || legacyRoster || selectedNamedProfile;
7101
7384
  let liteState = "unknown";
7102
7385
  if (!liteExists) {
7103
7386
  liteState = "missing";
7104
7387
  } else if (currentRoster) {
7105
7388
  liteState = "installed";
7106
- } else if (legacyRoster || selectedNamedPreset) {
7389
+ } else if (legacyRoster || selectedNamedProfile) {
7107
7390
  liteState = "drift";
7108
7391
  }
7109
7392
  return {
@@ -7132,7 +7415,7 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
7132
7415
  code: "opencode-roster-drift"
7133
7416
  }
7134
7417
  ] : [],
7135
- ...selectedNamedPreset && !currentRoster ? [activePresetSelectedDiagnostic()] : [],
7418
+ ...selectedNamedProfile && !currentRoster ? [activeProfileSelectedDiagnostic()] : [],
7136
7419
  ...!liteExists ? [
7137
7420
  {
7138
7421
  severity: "important",
@@ -7160,10 +7443,10 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
7160
7443
  };
7161
7444
  }
7162
7445
  if (!mainExists && liteExists) {
7163
- const currentRoster = hasManagedSevenAgentPreset(lite.config);
7446
+ const currentRoster = hasManagedProfile(lite.config);
7164
7447
  const legacyRoster = hasLegacySevenAgentRoster(lite.config);
7165
- const selectedNamedPreset = hasSelectedNamedPreset(lite.config);
7166
- if (!currentRoster && !legacyRoster && !selectedNamedPreset) {
7448
+ const selectedNamedProfile = hasSelectedNamedProfile(lite.config);
7449
+ if (!currentRoster && !legacyRoster && !selectedNamedProfile) {
7167
7450
  return {
7168
7451
  harness: "opencode",
7169
7452
  displayName: "OpenCode",
@@ -7185,12 +7468,12 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
7185
7468
  actions: openCodeActions
7186
7469
  };
7187
7470
  }
7188
- if (selectedNamedPreset && !currentRoster) {
7471
+ if (selectedNamedProfile && !currentRoster) {
7189
7472
  return {
7190
7473
  harness: "opencode",
7191
7474
  displayName: "OpenCode",
7192
7475
  state: "drift",
7193
- summary: "OpenCode plugin is missing and a valid named preset is active.",
7476
+ summary: "OpenCode plugin is missing and a valid named profile is active.",
7194
7477
  targets: [
7195
7478
  { ...mainTarget, state: "missing" },
7196
7479
  { ...liteTarget, state: "drift" },
@@ -7202,7 +7485,7 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
7202
7485
  message: "The managed OpenCode main config is missing and must be restored by install or sync.",
7203
7486
  code: "opencode-main-config-missing"
7204
7487
  },
7205
- activePresetSelectedDiagnostic(),
7488
+ activeProfileSelectedDiagnostic(),
7206
7489
  ...skillStatus.diagnostics
7207
7490
  ],
7208
7491
  actions: openCodeActions
@@ -7303,20 +7586,20 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
7303
7586
  actions: openCodeActions
7304
7587
  };
7305
7588
  }
7306
- if (!hasManagedSevenAgentPreset(lite.config)) {
7307
- const selectedNamedPreset = hasSelectedNamedPreset(lite.config);
7589
+ if (!hasManagedProfile(lite.config)) {
7590
+ const selectedNamedProfile = hasSelectedNamedProfile(lite.config);
7308
7591
  return {
7309
7592
  harness: "opencode",
7310
7593
  displayName: "OpenCode",
7311
7594
  state: "drift",
7312
- summary: selectedNamedPreset ? "A valid named OpenCode preset is active outside the managed six-role roster." : "thoth-agents config does not match the expected six-role roster.",
7595
+ summary: selectedNamedProfile ? "A valid named OpenCode profile is active outside the managed six-role roster." : "thoth-agents config does not match the expected six-role roster.",
7313
7596
  targets: [
7314
7597
  { ...mainTarget, state: "installed" },
7315
7598
  { ...liteTarget, state: "drift" },
7316
7599
  ...skillStatus.targets
7317
7600
  ],
7318
7601
  diagnostics: [
7319
- selectedNamedPreset ? activePresetSelectedDiagnostic() : {
7602
+ selectedNamedProfile ? activeProfileSelectedDiagnostic() : {
7320
7603
  severity: "important",
7321
7604
  message: "Roster drift requires repair before applying generated plans.",
7322
7605
  code: "opencode-roster-drift"
@@ -7347,8 +7630,18 @@ function getOpenCodeStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
7347
7630
  const managedStatus = getOpenCodeManagedStatus(context2);
7348
7631
  return {
7349
7632
  ...managedStatus,
7633
+ summary: `${managedStatus.summary} Requires OpenCode 2.2.6 or later.`,
7350
7634
  targets: [
7351
7635
  ...managedStatus.targets,
7636
+ {
7637
+ ...targetForProfiles(),
7638
+ observed: JSON.stringify({
7639
+ active: activeOpenCodeProfile().name ?? "default",
7640
+ agents: effectiveOpenCodeProfile(
7641
+ parseConfig(getExistingLiteConfigPath()).config
7642
+ ).agents
7643
+ })
7644
+ },
7352
7645
  getCliManagedInstallVersionTarget("opencode", context2)
7353
7646
  ],
7354
7647
  providerCapability: classifyProviderCapabilityEvidence(evidence)
@@ -7479,7 +7772,6 @@ function buildOpenCodeCompletePlan(action, context2) {
7479
7772
  const expectedPlugin = `${PACKAGE_NAME}@${version}`;
7480
7773
  const generatedConfig = generateLiteConfig({
7481
7774
  agent: "opencode",
7482
- hasTmux: false,
7483
7775
  dryRun: true,
7484
7776
  reset: false
7485
7777
  });
@@ -7500,16 +7792,10 @@ function buildOpenCodeCompletePlan(action, context2) {
7500
7792
  title: `Ensure OpenCode plugin points at ${expectedPlugin}`,
7501
7793
  target: {
7502
7794
  ...targetForMainConfig(),
7503
- expected: `plugin includes ${expectedPlugin}`
7795
+ expected: `plugins includes ${expectedPlugin}`
7504
7796
  },
7505
7797
  state: getOpenCodeStatus(context2).state,
7506
- preview: `plugin: ["${expectedPlugin}"]`,
7507
- backup: defaultBackup(getExistingConfigPath())
7508
- },
7509
- {
7510
- title: "Disable OpenCode default agents",
7511
- target: targetForMainConfig(),
7512
- preview: "agent.explore.disable = true; agent.general.disable = true",
7798
+ preview: `plugins: ["${expectedPlugin}"]`,
7513
7799
  backup: defaultBackup(getExistingConfigPath())
7514
7800
  },
7515
7801
  {
@@ -7518,8 +7804,9 @@ function buildOpenCodeCompletePlan(action, context2) {
7518
7804
  preview: JSON.stringify(generatedConfig, null, 2),
7519
7805
  backup: defaultBackup(getExistingLiteConfigPath())
7520
7806
  },
7807
+ shippedProfilePlanItem(),
7521
7808
  {
7522
- title: "Synchronize global thoth-owned OpenCode skills",
7809
+ title: "Inspect package-provided thoth-owned OpenCode skills",
7523
7810
  target: {
7524
7811
  kind: "skill",
7525
7812
  label: "Thoth-owned OpenCode skills",
@@ -7585,7 +7872,6 @@ function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
7585
7872
  }
7586
7873
  const generatedConfig = generateLiteConfig({
7587
7874
  agent: "opencode",
7588
- hasTmux: false,
7589
7875
  dryRun: true,
7590
7876
  reset: false
7591
7877
  });
@@ -7598,19 +7884,13 @@ function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
7598
7884
  "opencode-sync-preview",
7599
7885
  "sync",
7600
7886
  "Sync OpenCode managed configuration",
7601
- "Preview OpenCode plugin entry, default-agent disablement, and thoth-agents config sync.",
7887
+ "Preview pinned OpenCode plugins and thoth-agents config sync (requires OpenCode 2.2.6 or later).",
7602
7888
  [
7603
7889
  {
7604
7890
  title: `Ensure OpenCode plugin points at ${PACKAGE_NAME}@${packageVersion.version}`,
7605
7891
  target: targetForMainConfig(),
7606
7892
  state: getOpenCodeStatus(context2).state,
7607
- preview: `plugin: ["${PACKAGE_NAME}@${packageVersion.version}"]`,
7608
- backup: defaultBackup(getExistingConfigPath())
7609
- },
7610
- {
7611
- title: "Disable OpenCode default agents",
7612
- target: targetForMainConfig(),
7613
- preview: "agent.explore.disable = true; agent.general.disable = true",
7893
+ preview: `plugins: ["${PACKAGE_NAME}@${packageVersion.version}"]`,
7614
7894
  backup: defaultBackup(getExistingConfigPath())
7615
7895
  },
7616
7896
  {
@@ -7619,8 +7899,9 @@ function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
7619
7899
  preview: JSON.stringify(generatedConfig, null, 2),
7620
7900
  backup: defaultBackup(litePath)
7621
7901
  },
7902
+ shippedProfilePlanItem(),
7622
7903
  {
7623
- title: "Synchronize global thoth-owned OpenCode skills",
7904
+ title: "Inspect package-provided thoth-owned OpenCode skills",
7624
7905
  target: {
7625
7906
  kind: "skill",
7626
7907
  label: "Thoth-owned OpenCode skills",
@@ -7676,7 +7957,7 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
7676
7957
  const items = normalizedRoles.map(({ role, model, variant, effort }) => {
7677
7958
  return {
7678
7959
  title: `Set ${role} OpenCode model override`,
7679
- target: targetForLiteConfig(),
7960
+ target: targetForProfiles(),
7680
7961
  preview: JSON.stringify({
7681
7962
  [role]: {
7682
7963
  model,
@@ -7699,8 +7980,21 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
7699
7980
  "opencode-model-config-preview",
7700
7981
  "model-config",
7701
7982
  "Configure OpenCode role model overrides",
7702
- "Preview thoth-agents role model overrides in the plugin config agents map.",
7703
- items,
7983
+ "Preview complete agents profile materialization and global activation.",
7984
+ [
7985
+ ...items,
7986
+ {
7987
+ title: "Materialize and globally activate the complete agents profile",
7988
+ target: targetForProfiles(),
7989
+ preview: "All six effective roles; active: agents"
7990
+ },
7991
+ {
7992
+ title: "Remove unsupported preset keys, preserving root configuration",
7993
+ target: targetForLiteConfig(),
7994
+ preview: "Retain unrelated config keys; remove preset/presets",
7995
+ backup: defaultBackup(getExistingLiteConfigPath())
7996
+ }
7997
+ ],
7704
7998
  _context
7705
7999
  );
7706
8000
  const effortWarnings = items.flatMap((item) => item.warnings ?? []);
@@ -7725,7 +8019,7 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
7725
8019
  const unsupportedRoles = input.roles.filter(
7726
8020
  (role) => !ROLE_NAMES.includes(role.role)
7727
8021
  );
7728
- if (input.roles.length === 0 || unsupportedRoles.length > 0) {
8022
+ if (unsupportedRoles.length > 0) {
7729
8023
  return {
7730
8024
  ...plan,
7731
8025
  canApply: false,
@@ -7733,7 +8027,7 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
7733
8027
  ...plan.warnings,
7734
8028
  {
7735
8029
  severity: "critical",
7736
- message: input.roles.length === 0 ? "Model plan must include at least one role override." : `Unsupported OpenCode model role(s): ${unsupportedRoles.map((role) => role.role).join(", ")}.`,
8030
+ message: `Unsupported OpenCode model role(s): ${unsupportedRoles.map((role) => role.role).join(", ")}.`,
7737
8031
  code: "opencode-model-roster-incomplete"
7738
8032
  }
7739
8033
  ]
@@ -7857,8 +8151,8 @@ function validateApplyPlan(plan) {
7857
8151
  return null;
7858
8152
  }
7859
8153
  function invalidModelPayloadReason(roles) {
7860
- if (!Array.isArray(roles) || roles.length === 0) {
7861
- return "Model payload must contain at least one role override.";
8154
+ if (!Array.isArray(roles)) {
8155
+ return "Model payload must be a role override array.";
7862
8156
  }
7863
8157
  const seen = /* @__PURE__ */ new Set();
7864
8158
  for (const role of roles) {
@@ -7887,89 +8181,33 @@ function hasCompleteAgentRoster(value) {
7887
8181
  (role) => typeof configRecord(roles[role]).model === "string"
7888
8182
  );
7889
8183
  }
7890
- function effectiveRolePreset(base, generated) {
7891
- const presets = configRecord(base.presets);
7892
- const generatedPresets = configRecord(generated.presets);
7893
- const defaults = configRecord(generatedPresets[OPENAI_PRESET]);
7894
- const selected = typeof base.preset === "string" ? configRecord(presets[base.preset]) : {};
7895
- const selectedIsMaterializedAgentsPreset = base.preset === APPLIED_MODELS_PRESET && hasCompleteAgentRoster(selected);
7896
- const rootAgents = configRecord(base.agents);
7897
- return Object.fromEntries(
7898
- ROLE_NAMES.map((role) => [
7899
- role,
7900
- {
7901
- ...selectedIsMaterializedAgentsPreset ? {} : configRecord(defaults[role]),
7902
- ...configRecord(selected[role]),
7903
- ...configRecord(rootAgents[role])
7904
- }
7905
- ])
7906
- );
7907
- }
7908
8184
  function applyModelPlan(roles) {
7909
- const roleModels = new Map(
7910
- roles.map((role) => [
7911
- role.role,
7912
- { model: role.model, variant: role.variant }
7913
- ])
7914
- );
7915
8185
  ensureConfigDir();
7916
8186
  const targetPath = getExistingLiteConfigPath();
7917
8187
  const parsed = parseConfig(targetPath);
7918
- const generated = generateLiteConfig({
7919
- agent: "opencode",
7920
- hasTmux: false,
7921
- dryRun: false,
7922
- reset: false
7923
- });
7924
- const base = parsed.config ?? generated;
7925
- const agents = { ...configRecord(base.agents) };
7926
- const appliedPreset = effectiveRolePreset(base, generated);
8188
+ if (parsed.error) throw new Error(parsed.error);
8189
+ const base = parsed.config ?? generateLiteConfig({ agent: "opencode", reset: false });
8190
+ const profile = effectiveOpenCodeProfile(base);
7927
8191
  const statePath = getOpenCodeManagedModelStatePath();
7928
8192
  const state = readManagedModelState(statePath, 1);
7929
8193
  const configuredEfforts = { ...state.configuredEfforts ?? {} };
7930
8194
  const warnings = [];
7931
- for (const [role, override] of roleModels) {
7932
- const current = {
7933
- ...configRecord(agents[role])
7934
- };
7935
- const presetRole = {
7936
- ...configRecord(appliedPreset[role])
8195
+ for (const override of roles) {
8196
+ const role = override.role;
8197
+ profile.agents[role] = {
8198
+ model: override.model,
8199
+ ...override.variant !== null ? { variant: override.variant } : {}
7937
8200
  };
7938
- const currentVariant = typeof current.variant === "string" ? current.variant : void 0;
7939
- const trackedVariant = configuredEfforts[role];
7940
- const userOwnsVariant = override.variant === null && currentVariant !== void 0 && (trackedVariant === void 0 || currentVariant !== trackedVariant);
7941
- current.model = override.model;
7942
- presetRole.model = override.model;
7943
- if (userOwnsVariant) {
7944
- presetRole.variant = currentVariant;
7945
- delete configuredEfforts[role];
7946
- warnings.push({
7947
- severity: "important",
7948
- code: "opencode-effort-user-owned",
7949
- message: `Preserved user-owned OpenCode variant ${currentVariant} for ${role}.`
7950
- });
7951
- } else if (override.variant === null) {
7952
- delete current.variant;
7953
- delete presetRole.variant;
7954
- delete configuredEfforts[role];
7955
- } else {
7956
- current.variant = override.variant;
7957
- presetRole.variant = override.variant;
7958
- configuredEfforts[role] = override.variant;
7959
- }
7960
- agents[role] = current;
7961
- appliedPreset[role] = presetRole;
8201
+ if (override.variant === null) delete configuredEfforts[role];
8202
+ else configuredEfforts[role] = override.variant;
7962
8203
  }
7963
- base.agents = agents;
7964
- base.presets = {
7965
- ...configRecord(base.presets),
7966
- [APPLIED_MODELS_PRESET]: appliedPreset
7967
- };
7968
- base.preset = APPLIED_MODELS_PRESET;
7969
- writeConfig(targetPath, base);
8204
+ const store = openCodeProfileStore();
8205
+ store.save("global", APPLIED_MODELS_PROFILE, profile);
8206
+ store.setGlobalActive(APPLIED_MODELS_PROFILE);
8207
+ writeConfig(targetPath, withoutLegacyPresets(base));
7970
8208
  writeManagedModelState(statePath, 1, {
7971
8209
  models: state.models,
7972
- configuredEfforts: Object.keys(configuredEfforts).length > 0 ? configuredEfforts : void 0
8210
+ configuredEfforts: Object.keys(configuredEfforts).length ? configuredEfforts : void 0
7973
8211
  });
7974
8212
  return {
7975
8213
  harness: "opencode",
@@ -7979,7 +8217,11 @@ function applyModelPlan(roles) {
7979
8217
  changedTargets: [
7980
8218
  {
7981
8219
  ...targetForLiteConfig("installed"),
7982
- observed: "agents role overrides updated"
8220
+ observed: "root configuration preserved; unsupported preset keys removed"
8221
+ },
8222
+ {
8223
+ ...targetForProfiles(),
8224
+ observed: "complete agents profile activated"
7983
8225
  }
7984
8226
  ],
7985
8227
  backups: existsSync10(`${targetPath}.bak`) ? [{ path: `${targetPath}.bak`, label: "managed backup" }] : [],
@@ -8014,16 +8256,25 @@ function applyRequiredSkills(context2) {
8014
8256
  function applyOwnedSkills(context2) {
8015
8257
  const syncOwned = context2.syncOpenCodeOwnedSkills ?? syncOpenCodeOwnedSkills;
8016
8258
  const result = syncOwned({
8259
+ moveLegacyOpenCodeSkills: context2.moveLegacyOpenCodeSkills,
8017
8260
  homeDir: homeDirFromContext(context2)
8018
8261
  });
8019
- if (result.success) return { success: true, warnings: [] };
8262
+ if (result.success)
8263
+ return {
8264
+ success: true,
8265
+ warnings: (result.notices ?? []).map((message) => ({
8266
+ severity: "minor",
8267
+ code: "opencode-legacy-skill-copy",
8268
+ message
8269
+ }))
8270
+ };
8020
8271
  return {
8021
8272
  success: false,
8022
8273
  warnings: [
8023
8274
  {
8024
8275
  severity: "critical",
8025
8276
  code: "opencode-owned-skills-sync-failed",
8026
- message: `Failed to synchronize thoth-owned OpenCode skills: ${result.error ?? "unknown error"}.`
8277
+ message: `Failed to inspect package-provided thoth-owned OpenCode skills: ${result.error ?? "unknown error"}.`
8027
8278
  }
8028
8279
  ]
8029
8280
  };
@@ -8074,6 +8325,16 @@ function applyOpenCodePlan(plan) {
8074
8325
  issued.context
8075
8326
  );
8076
8327
  }
8328
+ const hostVersion = (issued.runtimeContext.getOpenCodeVersion ?? getOpenCodeVersionSync)();
8329
+ if (!isSupportedOpenCodeVersion(hostVersion)) {
8330
+ return rejectPlan3(
8331
+ plan,
8332
+ `${OPENCODE_VERSION_REQUIREMENT} Detected: ${hostVersion ?? "unavailable"}.`,
8333
+ "critical",
8334
+ "opencode-host-version-unsupported",
8335
+ issued.context
8336
+ );
8337
+ }
8077
8338
  if (issued.payload.kind === "model") {
8078
8339
  const invalidPayload = invalidModelPayloadReason(issued.payload.roles);
8079
8340
  if (invalidPayload) {
@@ -8085,7 +8346,17 @@ function applyOpenCodePlan(plan) {
8085
8346
  issued.context
8086
8347
  );
8087
8348
  }
8088
- return applyModelPlan(issued.payload.roles);
8349
+ try {
8350
+ return applyModelPlan(issued.payload.roles);
8351
+ } catch (error) {
8352
+ return rejectPlan3(
8353
+ plan,
8354
+ `Failed to apply OpenCode model profile: ${String(error)}`,
8355
+ "critical",
8356
+ "opencode-profile-write-failed",
8357
+ issued.context
8358
+ );
8359
+ }
8089
8360
  }
8090
8361
  const changedTargets = [];
8091
8362
  const backups = [];
@@ -8093,8 +8364,7 @@ function applyOpenCodePlan(plan) {
8093
8364
  const updateMainConfig = issued.runtimeContext.updateOpenCodeMainConfig ?? updateOpenCodeMainConfig;
8094
8365
  const mainConfigResult = updateMainConfig({
8095
8366
  ensurePlugin: true,
8096
- pluginVersion: issued.payload.version,
8097
- disableDefaults: true
8367
+ pluginVersion: issued.payload.version
8098
8368
  });
8099
8369
  if (!mainConfigResult.success) {
8100
8370
  return rejectPlan3(
@@ -8107,7 +8377,7 @@ function applyOpenCodePlan(plan) {
8107
8377
  }
8108
8378
  changedTargets.push({
8109
8379
  ...targetForMainConfig("installed"),
8110
- observed: `plugin includes ${PACKAGE_NAME}@${issued.payload.version}`
8380
+ observed: `plugins includes ${PACKAGE_NAME}@${issued.payload.version}`
8111
8381
  });
8112
8382
  if (existsSync10(`${mainConfigResult.configPath}.bak`)) {
8113
8383
  backups.push({
@@ -8119,7 +8389,6 @@ function applyOpenCodePlan(plan) {
8119
8389
  const liteResult = writeLite(
8120
8390
  {
8121
8391
  agent: "opencode",
8122
- hasTmux: false,
8123
8392
  dryRun: false,
8124
8393
  reset: false
8125
8394
  },
@@ -8135,6 +8404,10 @@ function applyOpenCodePlan(plan) {
8135
8404
  ...targetForLiteConfig("installed"),
8136
8405
  observed: "six-role roster written"
8137
8406
  });
8407
+ changedTargets.push({
8408
+ ...targetForProfiles(),
8409
+ observed: "built-in openai profile synchronized"
8410
+ });
8138
8411
  if (existsSync10(`${liteResult.configPath}.bak`)) {
8139
8412
  backups.push({
8140
8413
  path: `${liteResult.configPath}.bak`,
@@ -8147,14 +8420,14 @@ function applyOpenCodePlan(plan) {
8147
8420
  kind: "skill",
8148
8421
  label: "Thoth-owned OpenCode skills",
8149
8422
  state: ownedSkills.success ? "installed" : "drift",
8150
- observed: ownedSkills.success ? "thoth-owned global skills synchronized" : "thoth-owned global skill synchronization failed"
8423
+ observed: ownedSkills.success ? "package-provided skills available" : "package-provided skill inspection failed"
8151
8424
  });
8152
8425
  if (!ownedSkills.success) {
8153
8426
  return {
8154
8427
  harness: "opencode",
8155
8428
  action: plan.action,
8156
8429
  applied: false,
8157
- summary: "OpenCode configuration was written, but thoth-owned skills failed to synchronize.",
8430
+ summary: "OpenCode configuration was written, but package-provided skills failed inspection.",
8158
8431
  changedTargets,
8159
8432
  backups,
8160
8433
  warnings,
@@ -8224,28 +8497,28 @@ function applyOpenCodePlan(plan) {
8224
8497
  }
8225
8498
 
8226
8499
  // src/cli/operations/pi.ts
8227
- import { spawnSync as spawnSync7 } from "child_process";
8500
+ import { spawnSync as spawnSync8 } from "child_process";
8228
8501
  import { createHash as createHash7 } from "crypto";
8229
8502
  import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
8230
8503
  import { basename as basename4, dirname as dirname11, isAbsolute as isAbsolute5, join as join15 } from "path";
8231
8504
 
8232
8505
  // src/cli/pi-install.ts
8233
- import { spawnSync as spawnSync6 } from "child_process";
8506
+ import { spawnSync as spawnSync7 } from "child_process";
8234
8507
  import { createHash as createHash6 } from "crypto";
8235
8508
  import {
8236
8509
  existsSync as existsSync14,
8237
8510
  lstatSync as lstatSync6,
8238
8511
  readdirSync as readdirSync3,
8239
8512
  readFileSync as readFileSync13,
8240
- realpathSync as realpathSync3,
8241
- statSync as statSync3
8513
+ realpathSync as realpathSync4,
8514
+ statSync as statSync4
8242
8515
  } from "fs";
8243
- import { basename as basename3, dirname as dirname10, isAbsolute as isAbsolute4, join as join14, resolve as resolve9, sep as sep3 } from "path";
8516
+ import { basename as basename3, dirname as dirname10, isAbsolute as isAbsolute4, join as join14, resolve as resolve10, sep as sep4 } from "path";
8244
8517
  import { fileURLToPath as fileURLToPath5 } from "url";
8245
8518
  import { lt } from "semver";
8246
8519
 
8247
8520
  // src/cli/pi-external-package.ts
8248
- import { readFileSync as readFileSync9, realpathSync as realpathSync2 } from "fs";
8521
+ import { readFileSync as readFileSync9, realpathSync as realpathSync3 } from "fs";
8249
8522
  import { isAbsolute as isAbsolute2, join as join9 } from "path";
8250
8523
  import { satisfies, valid } from "semver";
8251
8524
  function npmPackageName(source) {
@@ -8274,7 +8547,7 @@ function piExternalSourceMatches(candidate, expectedSource) {
8274
8547
  if (candidate.source === expectedSource) return true;
8275
8548
  if (!isAbsolute2(expectedSource) || !candidate.installedPath) return false;
8276
8549
  try {
8277
- return realpathSync2(candidate.installedPath) === realpathSync2(expectedSource);
8550
+ return realpathSync3(candidate.installedPath) === realpathSync3(expectedSource);
8278
8551
  } catch {
8279
8552
  return false;
8280
8553
  }
@@ -8360,7 +8633,7 @@ function inspectPiExternalPackage(packages, spec, requireManagedSource = true) {
8360
8633
  }
8361
8634
 
8362
8635
  // src/cli/pi-git-source.ts
8363
- import { resolve as resolve5, sep as sep2 } from "path";
8636
+ import { resolve as resolve6, sep as sep3 } from "path";
8364
8637
  import hostedGitInfo from "hosted-git-info";
8365
8638
  function splitRef(url) {
8366
8639
  const scpLikeMatch = url.match(/^git@([^:]+):(.+)$/);
@@ -8502,9 +8775,9 @@ function projectGitPackagePath(source, projectRoot) {
8502
8775
  return void 0;
8503
8776
  const parsed = parseGitUrl(source);
8504
8777
  if (!parsed) return void 0;
8505
- const installRoot = resolve5(projectRoot, "git");
8506
- const installedPath = resolve5(installRoot, parsed.host, parsed.path);
8507
- if (installedPath !== installRoot && !installedPath.startsWith(`${installRoot}${sep2}`))
8778
+ const installRoot = resolve6(projectRoot, "git");
8779
+ const installedPath = resolve6(installRoot, parsed.host, parsed.path);
8780
+ if (installedPath !== installRoot && !installedPath.startsWith(`${installRoot}${sep3}`))
8508
8781
  return void 0;
8509
8782
  return installedPath;
8510
8783
  }
@@ -8517,7 +8790,7 @@ import {
8517
8790
  readdirSync as readdirSync2,
8518
8791
  readFileSync as readFileSync10,
8519
8792
  renameSync as renameSync4,
8520
- statSync as statSync2,
8793
+ statSync as statSync3,
8521
8794
  writeFileSync as writeFileSync3
8522
8795
  } from "fs";
8523
8796
  import { dirname as dirname7, join as join10 } from "path";
@@ -8561,7 +8834,7 @@ function migrateLegacyPiResources(options) {
8561
8834
  if (!existsSync11(source)) continue;
8562
8835
  const canonical = join10(options.packageRoot, "skills", name);
8563
8836
  const backup = `${source}.thoth-agents-legacy.bak`;
8564
- if (!statSync2(source).isDirectory() || !identicalTrees(source, canonical) || existsSync11(backup)) {
8837
+ if (!statSync3(source).isDirectory() || !identicalTrees(source, canonical) || existsSync11(backup)) {
8565
8838
  manualActions.push(
8566
8839
  `Legacy Pi skill ${name} is modified or has ambiguous backup ownership; remove it manually after review.`
8567
8840
  );
@@ -8611,16 +8884,16 @@ function migrateLegacyPiResources(options) {
8611
8884
  }
8612
8885
 
8613
8886
  // src/cli/pi-native-probe.ts
8614
- import { spawnSync as spawnSync5 } from "child_process";
8887
+ import { spawnSync as spawnSync6 } from "child_process";
8615
8888
  import {
8616
8889
  existsSync as existsSync12,
8617
- mkdtempSync as mkdtempSync2,
8890
+ mkdtempSync,
8618
8891
  readFileSync as readFileSync11,
8619
- rmSync as rmSync5,
8892
+ rmSync as rmSync4,
8620
8893
  writeFileSync as writeFileSync4
8621
8894
  } from "fs";
8622
8895
  import { tmpdir } from "os";
8623
- import { dirname as dirname8, join as join11, resolve as resolve6, win32 } from "path";
8896
+ import { dirname as dirname8, join as join11, resolve as resolve7, win32 } from "path";
8624
8897
  function resolvePiWindowsCliFromShim(shimPath, shimContents) {
8625
8898
  const target = shimContents.match(
8626
8899
  /"([^"\r\n]*@earendil-works[\\/]+pi-coding-agent[\\/]+dist[\\/]+bundle[\\/]+cli\.js)"\s+%\*/i
@@ -8633,7 +8906,7 @@ function resolvePiWindowsCliFromShim(shimPath, shimContents) {
8633
8906
  return win32.isAbsolute(expanded) ? win32.normalize(expanded) : void 0;
8634
8907
  }
8635
8908
  function findPiWindowsCli() {
8636
- const located = spawnSync5("where.exe", ["pi.cmd"], {
8909
+ const located = spawnSync6("where.exe", ["pi.cmd"], {
8637
8910
  windowsHide: true,
8638
8911
  encoding: "utf8"
8639
8912
  }).stdout?.split(/\r?\n/).find(Boolean);
@@ -8667,7 +8940,7 @@ function defaultExecutor(command, args, env) {
8667
8940
  executableArgs = [cli, ...args];
8668
8941
  }
8669
8942
  }
8670
- const result = spawnSync5(executable, executableArgs, {
8943
+ const result = spawnSync6(executable, executableArgs, {
8671
8944
  windowsHide: true,
8672
8945
  encoding: "utf8",
8673
8946
  timeout: 3e4,
@@ -8685,7 +8958,7 @@ function occurrences(text, marker) {
8685
8958
  }
8686
8959
  function observePiNativeRoot(options) {
8687
8960
  const ownsIsolatedRoot = options.piHome === void 0;
8688
- const isolatedRoot = options.piHome ?? mkdtempSync2(join11(tmpdir(), "thoth-pi-observer-"));
8961
+ const isolatedRoot = options.piHome ?? mkdtempSync(join11(tmpdir(), "thoth-pi-observer-"));
8689
8962
  const observerPath = join11(
8690
8963
  isolatedRoot,
8691
8964
  `observer-${process.pid}-${Date.now()}.mjs`
@@ -8765,7 +9038,7 @@ export default function observer(pi) {
8765
9038
  "--offline",
8766
9039
  "--no-extensions",
8767
9040
  "--extension",
8768
- resolve6(options.extensionPath),
9041
+ resolve7(options.extensionPath),
8769
9042
  "--extension",
8770
9043
  observerPath,
8771
9044
  "--provider",
@@ -8838,8 +9111,8 @@ ${result.stderr}`.split(/\r?\n/).flatMap((line) => {
8838
9111
  };
8839
9112
  } finally {
8840
9113
  if (ownsIsolatedRoot)
8841
- rmSync5(isolatedRoot, { recursive: true, force: true });
8842
- else rmSync5(observerPath, { force: true });
9114
+ rmSync4(isolatedRoot, { recursive: true, force: true });
9115
+ else rmSync4(observerPath, { force: true });
8843
9116
  }
8844
9117
  }
8845
9118
 
@@ -8849,11 +9122,11 @@ import {
8849
9122
  mkdirSync as mkdirSync6,
8850
9123
  readFileSync as readFileSync12,
8851
9124
  renameSync as renameSync5,
8852
- rmSync as rmSync6,
9125
+ rmSync as rmSync5,
8853
9126
  writeFileSync as writeFileSync5
8854
9127
  } from "fs";
8855
9128
  import { homedir as homedir5 } from "os";
8856
- import { dirname as dirname9, isAbsolute as isAbsolute3, join as join12, normalize, resolve as resolve7 } from "path";
9129
+ import { dirname as dirname9, isAbsolute as isAbsolute3, join as join12, normalize, resolve as resolve8 } from "path";
8857
9130
  var keys = [
8858
9131
  "schemaVersion",
8859
9132
  "owner",
@@ -8868,8 +9141,8 @@ var keys = [
8868
9141
  var semver = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
8869
9142
  var digest = /^[0-9a-f]{64}$/;
8870
9143
  function piPackagePathsEqual(left, right) {
8871
- const normalizedLeft = normalize(resolve7(left));
8872
- const normalizedRight = normalize(resolve7(right));
9144
+ const normalizedLeft = normalize(resolve8(left));
9145
+ const normalizedRight = normalize(resolve8(right));
8873
9146
  return process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight;
8874
9147
  }
8875
9148
  function getPiPackageReceiptPath(options = {}) {
@@ -8926,7 +9199,7 @@ function writePiPackageReceipt(receipt, options = {}) {
8926
9199
  try {
8927
9200
  renameSync5(temporary, path);
8928
9201
  } catch (error) {
8929
- rmSync6(temporary, { force: true });
9202
+ rmSync5(temporary, { force: true });
8930
9203
  throw error;
8931
9204
  }
8932
9205
  return { success: true, path };
@@ -8961,17 +9234,17 @@ function classifyPiPackageOwnership(input) {
8961
9234
 
8962
9235
  // src/cli/pi-paths.ts
8963
9236
  import { homedir as homedir6 } from "os";
8964
- import { join as join13, resolve as resolve8 } from "path";
9237
+ import { join as join13, resolve as resolve9 } from "path";
8965
9238
  function resolvePiPaths(options = {}) {
8966
9239
  const env = options.env ?? process.env;
8967
- const homeDir = resolve8(
9240
+ const homeDir = resolve9(
8968
9241
  options.homeDir ?? env.HOME ?? env.USERPROFILE ?? homedir6()
8969
9242
  );
8970
9243
  const defaultPiRoot = join13(homeDir, ".pi", "agent");
8971
9244
  const configured = env.PI_CODING_AGENT_DIR?.trim();
8972
- const piRoot = configured ? resolve8(configured) : defaultPiRoot;
8973
- const cwd = resolve8(options.cwd ?? process.cwd());
8974
- const configRoot = env.XDG_CONFIG_HOME?.trim() ? resolve8(env.XDG_CONFIG_HOME) : join13(homeDir, ".config");
9245
+ const piRoot = configured ? resolve9(configured) : defaultPiRoot;
9246
+ const cwd = resolve9(options.cwd ?? process.cwd());
9247
+ const configRoot = env.XDG_CONFIG_HOME?.trim() ? resolve9(env.XDG_CONFIG_HOME) : join13(homeDir, ".config");
8975
9248
  return {
8976
9249
  homeDir,
8977
9250
  piRoot,
@@ -9299,7 +9572,7 @@ function projectLocalPackagePath(source, projectRoot, homeDir) {
9299
9572
  else if (path.startsWith("~/") || process.platform === "win32" && path.startsWith("~\\"))
9300
9573
  path = join14(homeDir, path.slice(2));
9301
9574
  else if (path.startsWith("file://")) path = fileURLToPath5(path);
9302
- return resolve9(projectRoot, path);
9575
+ return resolve10(projectRoot, path);
9303
9576
  }
9304
9577
  function legacyPiNpmPackagePath(name, settings, execute) {
9305
9578
  try {
@@ -9310,7 +9583,7 @@ function legacyPiNpmPackagePath(name, settings, execute) {
9310
9583
  if (/[\0\r\n&|<>^%"'`$;!]/.test(command)) return void 0;
9311
9584
  const manager = basename3(command).replace(/\.(cmd|exe)$/i, "");
9312
9585
  if (!["npm", "pnpm", "bun"].includes(manager)) return void 0;
9313
- if (/\s/.test(command) && !statSync3(command, { throwIfNoEntry: false })?.isFile())
9586
+ if (/\s/.test(command) && !statSync4(command, { throwIfNoEntry: false })?.isFile())
9314
9587
  return void 0;
9315
9588
  const query = (args) => {
9316
9589
  const result = execute(command, args);
@@ -9357,7 +9630,7 @@ function resolvePiPackage(candidate, options, userSettings) {
9357
9630
  candidate = { ...candidate, resourceOnly };
9358
9631
  if (candidate.installedPath) return candidate;
9359
9632
  const paths = resolvePiPaths(options);
9360
- const baseDir = candidate.scope === "user" ? paths.piRoot : join14(resolve9(options.cwd ?? process.cwd()), ".pi");
9633
+ const baseDir = candidate.scope === "user" ? paths.piRoot : join14(resolve10(options.cwd ?? process.cwd()), ".pi");
9361
9634
  const parsed = parsePiPackageSource(candidate.source, baseDir);
9362
9635
  const npmName = parsed.type === "npm" ? parsed.name : void 0;
9363
9636
  let path = npmName ? join14(baseDir, "npm", "node_modules", npmName) : parsed.type === "git" ? parsed.path : projectLocalPackagePath(candidate.source, baseDir, paths.homeDir);
@@ -9365,7 +9638,7 @@ function resolvePiPackage(candidate, options, userSettings) {
9365
9638
  path = legacyPiNpmPackagePath(
9366
9639
  npmName,
9367
9640
  userSettings,
9368
- options.commandExecutor ?? defaultCommandExecutor4
9641
+ options.commandExecutor ?? defaultCommandExecutor3
9369
9642
  ) ?? path;
9370
9643
  return {
9371
9644
  ...candidate,
@@ -9381,7 +9654,7 @@ function resolvePiListedPackages(output, options) {
9381
9654
  const packages = parsePiPackageList(output);
9382
9655
  const paths = resolvePiPaths(options);
9383
9656
  const settings = readJsonObject(paths.settingsPath);
9384
- const projectRoot = join14(resolve9(options.cwd ?? process.cwd()), ".pi");
9657
+ const projectRoot = join14(resolve10(options.cwd ?? process.cwd()), ".pi");
9385
9658
  let projectSettings;
9386
9659
  for (const { scope, source, installedPath } of packages) {
9387
9660
  const baseDir = scope === "user" ? paths.piRoot : projectRoot;
@@ -9407,7 +9680,7 @@ function resolvePiListedPackages(output, options) {
9407
9680
  });
9408
9681
  }
9409
9682
  function readPiProjectPackages(options) {
9410
- const projectRoot = join14(resolve9(options.cwd ?? process.cwd()), ".pi");
9683
+ const projectRoot = join14(resolve10(options.cwd ?? process.cwd()), ".pi");
9411
9684
  return resolvePiConfiguredPackages(
9412
9685
  readJsonObject(join14(projectRoot, "settings.json")),
9413
9686
  "project",
@@ -9435,28 +9708,28 @@ function resolvePiPreservationPackages(output, options) {
9435
9708
  ];
9436
9709
  }
9437
9710
  function scanPiProjectInstalledIncumbents(options, configuredPackages) {
9438
- const projectRoot = join14(resolve9(options.cwd ?? process.cwd()), ".pi");
9711
+ const projectRoot = join14(resolve10(options.cwd ?? process.cwd()), ".pi");
9439
9712
  const incumbents = [];
9440
9713
  const visited = /* @__PURE__ */ new Set();
9441
9714
  const pathKey = (path) => process.platform === "win32" ? path.toLowerCase() : path;
9442
9715
  const knownPaths = new Set(
9443
9716
  configuredPackages.flatMap(
9444
- ({ installedPath }) => installedPath ? [pathKey(realpathSync3(installedPath))] : []
9717
+ ({ installedPath }) => installedPath ? [pathKey(realpathSync4(installedPath))] : []
9445
9718
  )
9446
9719
  );
9447
9720
  for (const root of [join14(projectRoot, "git"), join14(projectRoot, "npm")]) {
9448
9721
  if (!existsSync14(root)) continue;
9449
- const rootKey = pathKey(realpathSync3(root));
9722
+ const rootKey = pathKey(realpathSync4(root));
9450
9723
  const pending = [root];
9451
9724
  while (pending.length > 0) {
9452
9725
  const path = pending.pop();
9453
- if (!path || !statSync3(path, { throwIfNoEntry: false })?.isDirectory())
9726
+ if (!path || !statSync4(path, { throwIfNoEntry: false })?.isDirectory())
9454
9727
  continue;
9455
- const key = pathKey(realpathSync3(path));
9728
+ const key = pathKey(realpathSync4(path));
9456
9729
  if (visited.has(key)) continue;
9457
9730
  visited.add(key);
9458
9731
  const manifestPath = join14(path, "package.json");
9459
- if (existsSync14(manifestPath) && statSync3(manifestPath).isFile()) {
9732
+ if (existsSync14(manifestPath) && statSync4(manifestPath).isFile()) {
9460
9733
  const candidate = {
9461
9734
  scope: "project",
9462
9735
  source: path,
@@ -9471,7 +9744,7 @@ function scanPiProjectInstalledIncumbents(options, configuredPackages) {
9471
9744
  identityLimitation: `Installed manifest at ${path} identifies ${identity.packageName}, but no configured settings source maps to this directory.`
9472
9745
  });
9473
9746
  }
9474
- if (key !== rootKey && !key.startsWith(`${rootKey}${sep3}`)) continue;
9747
+ if (key !== rootKey && !key.startsWith(`${rootKey}${sep4}`)) continue;
9475
9748
  for (const entry of readdirSync3(path, { withFileTypes: true }))
9476
9749
  if (entry.isDirectory() || entry.isSymbolicLink())
9477
9750
  pending.push(join14(path, entry.name));
@@ -9585,7 +9858,7 @@ function buildPiSetupPlan(options = {}) {
9585
9858
  if (options.runtimePackageRoot) {
9586
9859
  const runtimeRoot = options.runtimePackageRoot;
9587
9860
  const spec = getPiExternalPackageSpecs(options)[0];
9588
- const sourceIsNormalizedAbsolutePath = isAbsolute4(runtimeRoot) && resolve9(runtimeRoot) === runtimeRoot;
9861
+ const sourceIsNormalizedAbsolutePath = isAbsolute4(runtimeRoot) && resolve10(runtimeRoot) === runtimeRoot;
9589
9862
  const inspection = sourceIsNormalizedAbsolutePath ? inspectPiExternalPackage(
9590
9863
  [{ scope: "user", source: runtimeRoot, installedPath: runtimeRoot }],
9591
9864
  spec
@@ -9643,7 +9916,7 @@ function buildPiSetupPlan(options = {}) {
9643
9916
  blockers.push(error instanceof Error ? error.message : String(error));
9644
9917
  }
9645
9918
  const projectSettingsPath = join14(
9646
- resolve9(options.cwd ?? process.cwd()),
9919
+ resolve10(options.cwd ?? process.cwd()),
9647
9920
  ".pi",
9648
9921
  "settings.json"
9649
9922
  );
@@ -9935,7 +10208,7 @@ function getPiFirstPartyPackages(packages, knownSource) {
9935
10208
  function matchesInstallSource(candidate, installSource) {
9936
10209
  return isAbsolute4(installSource) ? candidate.installedPath !== void 0 && piPackagePathsEqual(candidate.installedPath, installSource) : candidate.source === installSource;
9937
10210
  }
9938
- function defaultCommandExecutor4(command, args) {
10211
+ function defaultCommandExecutor3(command, args) {
9939
10212
  let executable = command;
9940
10213
  let executableArgs = [...args];
9941
10214
  if (process.platform === "win32" && command === "pi") {
@@ -9945,7 +10218,7 @@ function defaultCommandExecutor4(command, args) {
9945
10218
  executableArgs = [cli, ...args];
9946
10219
  }
9947
10220
  }
9948
- const result = spawnSync6(executable, executableArgs, {
10221
+ const result = spawnSync7(executable, executableArgs, {
9949
10222
  windowsHide: true,
9950
10223
  encoding: "utf8",
9951
10224
  timeout: PI_COMMAND_TIMEOUT_MS,
@@ -9973,7 +10246,7 @@ function applyPiSetup(plan) {
9973
10246
  };
9974
10247
  if (plan.dryRun)
9975
10248
  return { success: true, changed: [], diagnostics, installedPackages: [] };
9976
- const execute = plan.options.commandExecutor ?? defaultCommandExecutor4;
10249
+ const execute = plan.options.commandExecutor ?? defaultCommandExecutor3;
9977
10250
  const changed = [];
9978
10251
  const installedPackages = [];
9979
10252
  let receiptCommitted = false;
@@ -10613,7 +10886,7 @@ function statusFromPlan(plan, context2, evidence = {}) {
10613
10886
  description: item.kind === "package" ? "Validated installed-package manifest evidence; this does not prove live tool availability." : void 0
10614
10887
  }));
10615
10888
  const execute = context2.piCommandExecutor ?? ((command, args) => {
10616
- const result = spawnSync7(command, [...args], {
10889
+ const result = spawnSync8(command, [...args], {
10617
10890
  windowsHide: true,
10618
10891
  encoding: "utf8",
10619
10892
  timeout: 5e3,
@@ -11371,11 +11644,19 @@ function applyPiPlan(plan) {
11371
11644
  homeDir: source.context.homeDir
11372
11645
  }
11373
11646
  });
11374
- if (!completion.success)
11375
- return reject(
11647
+ const completionEvidence = getInstallCompletionEvidence(completion, {
11648
+ codePrefix: "pi",
11649
+ version: source.version,
11650
+ fallbackError: "Pi provider setup or ledger commit failed."
11651
+ });
11652
+ if (!completion.success) {
11653
+ const rejected = reject(
11376
11654
  completion.error ?? "Pi provider setup or ledger commit failed.",
11377
- [...setupTargets, ...skillTargets]
11655
+ [...setupTargets, ...skillTargets, ...completionEvidence.targets]
11378
11656
  );
11657
+ rejected.warnings.push(...completionEvidence.warnings);
11658
+ return rejected;
11659
+ }
11379
11660
  return {
11380
11661
  harness: "pi",
11381
11662
  action: plan.action,
@@ -11384,12 +11665,7 @@ function applyPiPlan(plan) {
11384
11665
  changedTargets: [
11385
11666
  ...setupTargets,
11386
11667
  ...skillTargets,
11387
- {
11388
- kind: "file",
11389
- path: completion.ledger.path,
11390
- label: "CLI-managed install version",
11391
- state: "installed"
11392
- }
11668
+ ...completionEvidence.targets
11393
11669
  ],
11394
11670
  diagnosticTargets: packageSkills.skills.map((skill) => ({
11395
11671
  kind: "skill",
@@ -11398,7 +11674,7 @@ function applyPiPlan(plan) {
11398
11674
  state: "installed"
11399
11675
  })),
11400
11676
  backups: [],
11401
- warnings: [],
11677
+ warnings: completionEvidence.warnings,
11402
11678
  disclaimers: disclaimers()
11403
11679
  };
11404
11680
  }
@@ -11902,35 +12178,15 @@ function getClaudeCodeModelRoles(_source = claudeCodeContext) {
11902
12178
  function getPiModelRoles() {
11903
12179
  return defaultPiModelRoles(context);
11904
12180
  }
11905
- function readRoleField(config, role, field) {
11906
- if (!config || typeof config !== "object" || Array.isArray(config)) {
11907
- return void 0;
11908
- }
11909
- const record = config;
11910
- const value = record[role];
11911
- if (!value || typeof value !== "object" || Array.isArray(value)) {
11912
- return void 0;
11913
- }
11914
- const fieldValue = value[field];
11915
- return typeof fieldValue === "string" && fieldValue.length > 0 ? fieldValue : void 0;
11916
- }
11917
12181
  function getOpenCodeModelRoles() {
11918
12182
  const parsed = parseConfig(getExistingLiteConfigPath());
11919
- const agents = parsed.config?.agents && typeof parsed.config.agents === "object" ? parsed.config.agents : void 0;
11920
- const presets = parsed.config?.presets && typeof parsed.config.presets === "object" && !Array.isArray(parsed.config.presets) ? parsed.config.presets : {};
11921
- const presetName = typeof parsed.config?.preset === "string" ? parsed.config.preset : void 0;
11922
- const selectedPreset = presetName !== void 0 ? presets[presetName] : void 0;
11923
- const activePreset = selectedPreset && typeof selectedPreset === "object" && !Array.isArray(selectedPreset) ? selectedPreset : void 0;
12183
+ const profile = effectiveOpenCodeProfile(parsed.config);
11924
12184
  return ALL_AGENT_NAMES.map((role) => {
11925
- const defaultModel = getDefaultOpenCodeModel(role);
11926
- const configuredModel = readRoleField(agents, role, "model") ?? readRoleField(activePreset, role, "model");
11927
- const model = configuredModel ?? defaultModel;
11928
- const configuredVariant = readRoleField(agents, role, "variant") ?? readRoleField(activePreset, role, "variant");
11929
- const variant = configuredVariant ?? (configuredModel === void 0 ? getDefaultOpenCodeVariant(role) : void 0);
12185
+ const value = profile.agents[role];
11930
12186
  return {
11931
12187
  role,
11932
- model,
11933
- effort: variant ? { kind: "effort", value: variant } : { kind: "inherit" }
12188
+ model: value?.model ?? getDefaultOpenCodeModel(role),
12189
+ effort: value?.variant ? { kind: "effort", value: value.variant } : { kind: "inherit" }
11934
12190
  };
11935
12191
  });
11936
12192
  }
@@ -11962,6 +12218,7 @@ function buildRestoreModelPlan(harness, options, source = context) {
11962
12218
  return buildTuiModelPlan(harness, roles, source);
11963
12219
  }
11964
12220
  var defaultTuiOperations = {
12221
+ legacyOpenCodeSkillCopies: () => inspectLegacyOpenCodeSkills().map((copy) => copy.sourcePath),
11965
12222
  status(harness, evidence) {
11966
12223
  if (harness === "opencode") return getOpenCodeStatus(context, evidence);
11967
12224
  if (harness === "claude") {
@@ -11983,8 +12240,12 @@ var defaultTuiOperations = {
11983
12240
  },
11984
12241
  plan(harness, action, options) {
11985
12242
  if (harness === "opencode") {
11986
- if (action === "install") return buildOpenCodeInstallPlan(context);
11987
- if (action === "update") return buildOpenCodeUpdatePlan(context);
12243
+ const consentContext2 = {
12244
+ ...context,
12245
+ moveLegacyOpenCodeSkills: options?.moveLegacyOpenCodeSkills === true
12246
+ };
12247
+ if (action === "install") return buildOpenCodeInstallPlan(consentContext2);
12248
+ if (action === "update") return buildOpenCodeUpdatePlan(consentContext2);
11988
12249
  if (action === "sync") return buildOpenCodeSyncPlan(context);
11989
12250
  return buildTuiModelPlan(harness, getOpenCodeModelRoles());
11990
12251
  }
@@ -12061,8 +12322,15 @@ export {
12061
12322
  buildClaudeCodeSetupPlan,
12062
12323
  applyClaudeCodeSetup,
12063
12324
  formatClaudeCodeSetupPlan,
12325
+ isOpenCodeInstalled,
12326
+ OPENCODE_VERSION_REQUIREMENT,
12327
+ isSupportedOpenCodeVersion,
12328
+ getOpenCodeVersion,
12329
+ getOpenCodePath,
12064
12330
  recordHarnessInstallCompletion,
12065
12331
  finalizeHarnessInstall,
12332
+ getLegacyOpenCodeSkillBackupRoot,
12333
+ inspectLegacyOpenCodeSkills,
12066
12334
  syncOpenCodeOwnedSkills,
12067
12335
  inspectPiPackageSkills,
12068
12336
  buildPiSetupPlan,