thoth-agents 0.11.0 → 0.12.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 (68) hide show
  1. package/README.md +5 -3
  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-5WPP6IY7.js → chunk-7F33WUSD.js} +1 -1
  6. package/dist/{chunk-RWQPMWEM.js → chunk-JLU3FSBT.js} +17 -2
  7. package/dist/chunk-MIDGZE34.js +107 -0
  8. package/dist/{chunk-DNPKVFOB.js → chunk-NY2IPJ7V.js} +78 -87
  9. package/dist/{chunk-HACUJFKI.js → chunk-QOA2AETX.js} +2 -228
  10. package/dist/{chunk-I5TXGNSG.js → chunk-SEF6PFM6.js} +504 -211
  11. package/dist/cli/config-io.d.ts +2 -2
  12. package/dist/cli/index.js +92 -39
  13. package/dist/cli/install.d.ts +1 -0
  14. package/dist/cli/opencode-skill-consent.d.ts +5 -0
  15. package/dist/cli/operations/opencode.d.ts +2 -1
  16. package/dist/cli/operations/types.d.ts +1 -0
  17. package/dist/cli/owned-skills.d.ts +16 -0
  18. package/dist/cli/paths.d.ts +1 -1
  19. package/dist/cli/providers.d.ts +1 -1
  20. package/dist/cli/system.d.ts +3 -1
  21. package/dist/cli/thoth-mem-install.d.ts +10 -10
  22. package/dist/cli/tui/index.js +53 -5
  23. package/dist/cli/tui/operations.d.ts +2 -0
  24. package/dist/cli/types.d.ts +2 -4
  25. package/dist/config/loader.d.ts +1 -1
  26. package/dist/config/schema.d.ts +0 -31
  27. package/dist/harness/adapters/opencode.d.ts +1 -1
  28. package/dist/hooks/delegate-task-retry/hook.d.ts +8 -8
  29. package/dist/hooks/foreground-fallback/index.d.ts +14 -69
  30. package/dist/hooks/index.d.ts +0 -2
  31. package/dist/hooks/json-error-recovery/hook.d.ts +2 -15
  32. package/dist/hooks/oracle-gate-verdict/hook.d.ts +4 -11
  33. package/dist/index.d.ts +7 -4
  34. package/dist/index.js +822 -1421
  35. package/dist/opencode/agents.d.ts +3 -0
  36. package/dist/opencode/command.d.ts +3 -0
  37. package/dist/opencode/context.d.ts +20 -0
  38. package/dist/opencode/delegation-hooks.d.ts +3 -0
  39. package/dist/opencode/mcp.d.ts +3 -0
  40. package/dist/opencode/permission-rules.d.ts +9 -0
  41. package/dist/opencode/permissions.d.ts +3 -0
  42. package/dist/opencode/session-hooks.d.ts +3 -0
  43. package/dist/opencode/session-state.d.ts +39 -0
  44. package/dist/opencode/skills.d.ts +3 -0
  45. package/dist/opencode/tools.d.ts +3 -0
  46. package/dist/pi.js +3 -3
  47. package/dist/tools/ast-grep/cli.d.ts +1 -0
  48. package/dist/tools/ast-grep/index.d.ts +108 -2
  49. package/dist/tools/ast-grep/tools.d.ts +107 -3
  50. package/dist/tools/define-tool.d.ts +23 -0
  51. package/dist/tools/lsp/tools.d.ts +97 -5
  52. package/dist/utils/index.d.ts +0 -1
  53. package/package.json +14 -3
  54. package/plugin/.claude-plugin/plugin.json +1 -1
  55. package/plugin/.codex-plugin/plugin.json +1 -1
  56. package/plugin/skills/thoth-sdd/SKILL.md +8 -2
  57. package/plugin/skills/thoth-sdd/scripts/validate-result.mjs +28 -2
  58. package/skills/thoth-sdd/SKILL.md +8 -2
  59. package/skills/thoth-sdd/scripts/validate-result.mjs +28 -2
  60. package/thoth-agents.schema.json +0 -26
  61. package/dist/chunk-62L3ORXN.js +0 -30
  62. package/dist/delegation/project-id.d.ts +0 -10
  63. package/dist/hooks/auto-update-checker/checker.d.ts +0 -23
  64. package/dist/hooks/auto-update-checker/constants.d.ts +0 -11
  65. package/dist/hooks/auto-update-checker/index.d.ts +0 -17
  66. package/dist/hooks/auto-update-checker/types.d.ts +0 -22
  67. package/dist/utils/tmux-session-manager.d.ts +0 -63
  68. 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-7F33WUSD.js";
20
21
  import {
21
22
  ALL_AGENT_NAMES,
22
23
  CLAUDE_CODE_PROMPT_DIALECT,
@@ -33,22 +34,25 @@ import {
33
34
  generateLiteConfig,
34
35
  getAgentOverride,
35
36
  getAgentPackContract,
37
+ getConfigDir,
36
38
  getDefaultOpenCodeModel,
37
39
  getDefaultOpenCodeVariant,
38
40
  getExistingConfigPath,
39
41
  getExistingLiteConfigPath,
40
42
  getOpenCodeManagedModelStatePath,
41
43
  getPrimaryModelId,
44
+ isManagedOpenCodePlugin,
42
45
  isPiSpecialistRole,
43
46
  isValidPackageVersion,
44
47
  parseConfig,
45
48
  piSpecialistName,
49
+ pluginEntries,
46
50
  renderAgentRoutingDescription,
47
51
  resolveExecutingPackageVersion,
48
52
  updateOpenCodeMainConfig,
49
53
  writeConfig,
50
54
  writeLiteConfig
51
- } from "./chunk-DNPKVFOB.js";
55
+ } from "./chunk-NY2IPJ7V.js";
52
56
 
53
57
  // src/cli/model-effort.ts
54
58
  function normalizeEffortSelection(value) {
@@ -4387,47 +4391,35 @@ function recordCompletedInstall(options) {
4387
4391
 
4388
4392
  // src/cli/thoth-mem-install.ts
4389
4393
  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
4394
+ var THOTH_MEM_PACKAGE_SPEC = "thoth-mem@>=0.7.0";
4403
4395
  var THOTH_MEM_SETUP_TIMEOUT_MS = 12e4;
4404
4396
  var STATUS_EXIT_CODE = {
4397
+ planned: 0,
4405
4398
  complete: 0,
4406
- failed: 1,
4407
- partial: 2,
4408
- requires_user_action: 3
4399
+ unsupported: 1,
4400
+ "requires-user-action": 1
4409
4401
  };
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
4402
  function isRecord6(value) {
4421
4403
  return typeof value === "object" && value !== null && !Array.isArray(value);
4422
4404
  }
4423
4405
  function isStringArray(value) {
4424
4406
  return Array.isArray(value) && value.every((item) => typeof item === "string");
4425
4407
  }
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
4408
  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");
4409
+ 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") {
4410
+ return false;
4411
+ }
4412
+ if (expectedHarness === "opencode") {
4413
+ return typeof value.plugin === "string" && typeof value.opencodeVersion === "string" && typeof value.configPath === "string" && typeof value.skillPath === "string" && typeof value.providerConfigPath === "string";
4414
+ }
4415
+ if (value.host !== expectedHarness || typeof value.source !== "string" || typeof value.version !== "string" || !isRecord6(value.verification)) {
4416
+ return false;
4417
+ }
4418
+ const verification = value.verification;
4419
+ if (expectedHarness === "pi") {
4420
+ return typeof value.piVersion === "string" && typeof verification.package === "boolean" && typeof verification.source === "boolean" && typeof verification.manifest === "boolean";
4421
+ }
4422
+ 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
4423
  }
4432
4424
  function parseProviderSetupResult(stdout, expectedHarness) {
4433
4425
  let value;
@@ -4438,9 +4430,10 @@ function parseProviderSetupResult(stdout, expectedHarness) {
4438
4430
  }
4439
4431
  return isProviderSetupResult(value, expectedHarness) ? value : void 0;
4440
4432
  }
4441
- function defaultCommandExecutor3(command, args, options) {
4433
+ function executeThothMemCommand(command, args, options) {
4442
4434
  const result = spawnSync3(command, [...args], {
4443
4435
  windowsHide: true,
4436
+ windowsVerbatimArguments: process.platform === "win32",
4444
4437
  cwd: options.cwd,
4445
4438
  encoding: "utf8",
4446
4439
  stdio: ["ignore", "pipe", "pipe"],
@@ -4466,9 +4459,9 @@ function invalidResult(harness, command, args, exitCode, error) {
4466
4459
  status: "invalid",
4467
4460
  changed: false,
4468
4461
  harness,
4469
- steps: [],
4462
+ actions: [],
4463
+ warnings: [],
4470
4464
  diagnostics: [],
4471
- manualActions: [],
4472
4465
  receipt: null,
4473
4466
  command,
4474
4467
  args,
@@ -4477,24 +4470,32 @@ function invalidResult(harness, command, args, exitCode, error) {
4477
4470
  };
4478
4471
  }
4479
4472
  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
- );
4473
+ const args = [
4474
+ "-y",
4475
+ THOTH_MEM_PACKAGE_SPEC,
4476
+ "setup",
4477
+ harness,
4478
+ ...dryRun ? ["--plan"] : [],
4479
+ "--json"
4480
+ ];
4481
+ if ((options.platform ?? process.platform) !== "win32") {
4482
+ return { command: "npx", args };
4483
+ }
4484
+ return {
4485
+ command: options.commandShell ?? process.env.ComSpec ?? "cmd.exe",
4486
+ // Node must not escape this cmd command line: quoted > is a literal,
4487
+ // not redirection, and npx must receive the selector without quote characters.
4488
+ args: [
4489
+ `/d /s /c "npx ${args.map((arg) => arg === THOTH_MEM_PACKAGE_SPEC ? `"${arg}"` : arg).join(" ")}"`
4490
+ ]
4491
+ };
4491
4492
  }
4492
4493
  function runThothMemSetup(options) {
4493
4494
  const { command, args } = getThothMemSetupCommand(
4494
4495
  options.harness,
4495
4496
  options.dryRun ?? false
4496
4497
  );
4497
- const executor = options.commandExecutor ?? defaultCommandExecutor3;
4498
+ const executor = options.commandExecutor ?? executeThothMemCommand;
4498
4499
  let commandResult;
4499
4500
  try {
4500
4501
  commandResult = executor(command, args, {
@@ -4531,34 +4532,37 @@ function runThothMemSetup(options) {
4531
4532
  command,
4532
4533
  args,
4533
4534
  commandResult.exitCode,
4534
- stderr ? `thoth-mem returned invalid JSON evidence: ${stderr.slice(0, 2e3)}` : "thoth-mem returned invalid JSON evidence."
4535
+ stderr ? `thoth-mem setup failed or returned invalid JSON evidence: ${stderr.slice(0, 2e3)}` : "thoth-mem returned invalid JSON evidence."
4535
4536
  );
4536
4537
  }
4537
4538
  const expectedExitCode = STATUS_EXIT_CODE[provider.status];
4538
- const evidenceValid = commandResult.exitCode === expectedExitCode;
4539
+ const expectedStatus = options.dryRun ? "planned" : "complete";
4540
+ const exitConsistent = commandResult.exitCode === expectedExitCode;
4541
+ const modeConsistent = expectedExitCode !== 0 || provider.status === expectedStatus;
4542
+ const evidenceValid = exitConsistent && modeConsistent;
4539
4543
  return {
4540
- success: evidenceValid && provider.status === "complete",
4544
+ success: evidenceValid && provider.status === expectedStatus,
4541
4545
  evidenceValid,
4542
4546
  status: provider.status,
4543
4547
  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,
4548
+ harness: options.harness,
4549
+ actions: provider.actions,
4550
+ warnings: provider.warnings,
4551
+ diagnostics: provider.diagnostics ?? [],
4552
+ restartRequired: provider.restartRequired,
4553
+ receipt: provider.receiptPath,
4550
4554
  command,
4551
4555
  args,
4552
4556
  exitCode: commandResult.exitCode,
4553
4557
  ...!evidenceValid ? {
4554
- error: `thoth-mem exit code ${String(commandResult.exitCode)} contradicts reported status ${provider.status} (expected ${expectedExitCode}).`
4558
+ 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
4559
  } : {}
4556
4560
  };
4557
4561
  }
4558
4562
 
4559
4563
  // 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;
4564
+ function isConsistentProviderSuccess(result, harness, dryRun) {
4565
+ return result.success && result.evidenceValid && result.status === (dryRun ? "planned" : "complete") && result.exitCode === 0 && result.harness === harness;
4562
4566
  }
4563
4567
  function recordHarnessInstallCompletion(options) {
4564
4568
  const ledgerPath = getInstallLedgerPath(options.ledgerOptions);
@@ -4603,12 +4607,12 @@ function finalizeHarnessInstall(options) {
4603
4607
  dryRun: options.dryRun,
4604
4608
  cwd: options.cwd
4605
4609
  });
4606
- if (!isConsistentProviderSuccess(provider, options.harness)) {
4610
+ if (!isConsistentProviderSuccess(provider, options.harness, options.dryRun)) {
4607
4611
  return {
4608
4612
  success: false,
4609
4613
  provider,
4610
4614
  ledger: { status: "not-attempted", path: ledgerPath },
4611
- error: provider.error ?? "thoth-mem setup did not return consistent complete evidence."
4615
+ error: provider.error ?? "thoth-mem setup did not return consistent setup evidence."
4612
4616
  };
4613
4617
  }
4614
4618
  const completion = recordHarnessInstallCompletion({
@@ -4629,6 +4633,19 @@ import { spawnSync as spawnSync4 } from "child_process";
4629
4633
  import { existsSync as existsSync6 } from "fs";
4630
4634
  import { homedir as homedir3 } from "os";
4631
4635
  import { join as join6 } from "path";
4636
+
4637
+ // src/cli/npx-command.ts
4638
+ function getNpxCommand(args, options = {}) {
4639
+ if ((options.platform ?? process.platform) !== "win32") {
4640
+ return { command: "npx", args: [...args] };
4641
+ }
4642
+ return {
4643
+ command: options.commandShell ?? process.env.ComSpec ?? "cmd.exe",
4644
+ args: ["/d", "/s", "/c", ["npx", ...args].join(" ")]
4645
+ };
4646
+ }
4647
+
4648
+ // src/cli/skills.ts
4632
4649
  var SKILLS_CLI_AGENT = {
4633
4650
  opencode: "opencode",
4634
4651
  codex: "codex",
@@ -4773,12 +4790,26 @@ function getInstallCompletionEvidence(completion, options) {
4773
4790
  code: `${options.codePrefix}-provider-diagnostic`,
4774
4791
  message
4775
4792
  })),
4776
- ...completion.provider.manualActions.map((message) => ({
4793
+ ...completion.provider.warnings.map((message) => ({
4777
4794
  severity: "important",
4778
- code: `${options.codePrefix}-provider-manual-action`,
4795
+ code: `${options.codePrefix}-provider-warning`,
4779
4796
  message
4780
4797
  }))
4781
4798
  ];
4799
+ for (const action of completion.provider.actions) {
4800
+ targets.push({
4801
+ kind: "surface",
4802
+ label: "thoth-mem action",
4803
+ observed: action
4804
+ });
4805
+ }
4806
+ if (completion.provider.restartRequired) {
4807
+ warnings.push({
4808
+ severity: "important",
4809
+ code: `${options.codePrefix}-provider-restart`,
4810
+ message: "thoth-mem: restart required"
4811
+ });
4812
+ }
4782
4813
  if (completion.provider.receipt) {
4783
4814
  targets.push({
4784
4815
  kind: "file",
@@ -6386,19 +6417,18 @@ function resolveOpenCodeEffort(input) {
6386
6417
  }
6387
6418
 
6388
6419
  // src/cli/owned-skills.ts
6420
+ import { randomBytes } from "crypto";
6389
6421
  import {
6390
- cpSync,
6391
6422
  existsSync as existsSync9,
6392
6423
  lstatSync as lstatSync5,
6393
6424
  mkdirSync as mkdirSync4,
6394
- mkdtempSync,
6395
6425
  readFileSync as readFileSync8,
6426
+ realpathSync as realpathSync2,
6396
6427
  renameSync as renameSync3,
6397
- rmSync as rmSync4,
6398
6428
  statSync
6399
6429
  } from "fs";
6400
6430
  import { homedir as homedir4 } from "os";
6401
- import { dirname as dirname6, join as join7 } from "path";
6431
+ import { dirname as dirname6, join as join7, resolve as resolve5, sep as sep2 } from "path";
6402
6432
  import { fileURLToPath as fileURLToPath4 } from "url";
6403
6433
 
6404
6434
  // src/harness/core/owned-skills.ts
@@ -6491,7 +6521,7 @@ function getOpenCodeOwnedSkillEntries(options = {}) {
6491
6521
  return THOTH_OWNED_SKILL_NAMES.map((name) => ({
6492
6522
  name,
6493
6523
  sourcePath: join7(packageRoot, "skills", name),
6494
- destinationPath: join7(homeDir, ".config", "opencode", "skills", name)
6524
+ destinationPath: join7(getConfigDir(homeDir), "skills", name)
6495
6525
  }));
6496
6526
  }
6497
6527
  function getPiOwnedSkillEntries(options) {
@@ -6513,56 +6543,126 @@ function validateCanonicalSkill(entry) {
6513
6543
  throw new Error(`Canonical owned skill contract is missing: ${entry.name}`);
6514
6544
  }
6515
6545
  }
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
- });
6546
+ function getLegacyOpenCodeSkillBackupRoot(homeDir = homedir4()) {
6547
+ return resolve5(
6548
+ process.env.XDG_DATA_HOME || join7(homeDir, ".local", "share"),
6549
+ "thoth-agents",
6550
+ ".opencode-legacy-skills"
6551
+ );
6552
+ }
6553
+ function inspectLegacyOpenCodeSkills(homeDir = homedir4()) {
6554
+ const seen = /* @__PURE__ */ new Set();
6555
+ const copies = [];
6556
+ for (const [label, root] of [
6557
+ ["resolved", getConfigDir(homeDir)],
6558
+ ["legacy", join7(homeDir, ".config", "opencode")]
6559
+ ]) {
6560
+ for (const directory of ["skills", "skill"]) {
6561
+ for (const name of THOTH_OWNED_SKILL_NAMES) {
6562
+ const sourcePath = join7(root, directory, name);
6563
+ if (!existsSync9(sourcePath)) continue;
6564
+ const real = realpathSync2(sourcePath);
6565
+ if (seen.has(real)) continue;
6566
+ seen.add(real);
6567
+ copies.push({ name, sourcePath, label: `${label}-${directory}` });
6568
+ }
6532
6569
  }
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);
6570
+ }
6571
+ return copies;
6572
+ }
6573
+ function planOrMoveLegacySkills(options) {
6574
+ const backupRoot = getLegacyOpenCodeSkillBackupRoot(options.homeDir);
6575
+ const copies = inspectLegacyOpenCodeSkills(options.homeDir);
6576
+ const notices = [];
6577
+ const legacyMoves = [];
6578
+ if (!copies.length) return { backupRoot, notices, legacyMoves };
6579
+ const confirmed = options.moveLegacyOpenCodeSkills && !options.dryRun;
6580
+ let run = join7(backupRoot, "<new-run>");
6581
+ if (confirmed) {
6582
+ try {
6583
+ if (!resolve5(backupRoot).split(sep2).includes(".opencode-legacy-skills"))
6584
+ throw new Error(
6585
+ "Backup path is missing the required hidden .opencode-legacy-skills segment"
6586
+ );
6587
+ mkdirSync4(backupRoot, { recursive: true });
6588
+ let created = false;
6589
+ for (let attempt = 0; attempt < 10; attempt++) {
6590
+ run = join7(
6591
+ backupRoot,
6592
+ `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${randomBytes(8).toString("hex")}`
6593
+ );
6594
+ try {
6595
+ mkdirSync4(run, { mode: 448 });
6596
+ created = true;
6597
+ break;
6598
+ } catch (error) {
6599
+ if (error.code !== "EEXIST") throw error;
6600
+ }
6539
6601
  }
6602
+ if (!created)
6603
+ throw new Error("Unable to create an exclusive backup run directory");
6604
+ } catch (error) {
6605
+ notices.push(`Legacy skill move refused: ${String(error)}`);
6606
+ notices.push(
6607
+ ...copies.map(
6608
+ ({ sourcePath }) => legacyOpenCodeSkillNotice(sourcePath)
6609
+ )
6610
+ );
6611
+ return { backupRoot, notices, legacyMoves };
6612
+ }
6613
+ }
6614
+ for (const copy of copies) {
6615
+ const destinationPath = join7(run, copy.label, copy.name);
6616
+ const move = {
6617
+ sourcePath: copy.sourcePath,
6618
+ destinationPath,
6619
+ status: "planned"
6620
+ };
6621
+ legacyMoves.push(move);
6622
+ if (!confirmed) {
6623
+ notices.push(legacyOpenCodeSkillNotice(copy.sourcePath));
6624
+ notices.push(
6625
+ `Planned move: ${copy.sourcePath} -> ${destinationPath} (backup root: ${backupRoot})`
6626
+ );
6627
+ continue;
6628
+ }
6629
+ try {
6630
+ mkdirSync4(dirname6(destinationPath), { recursive: true });
6540
6631
  try {
6541
- renameSync3(stagedPath, skill.destinationPath);
6632
+ lstatSync5(destinationPath);
6633
+ throw new Error(
6634
+ `Backup destination already exists: ${destinationPath}`
6635
+ );
6542
6636
  } 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 });
6637
+ if (error.code !== "ENOENT") throw error;
6553
6638
  }
6639
+ renameSync3(copy.sourcePath, destinationPath);
6640
+ move.status = "moved";
6641
+ notices.push(
6642
+ `Moved legacy OpenCode skill: ${copy.sourcePath} -> ${destinationPath} (backup: ${run})`
6643
+ );
6644
+ } catch (error) {
6645
+ move.status = "failed";
6646
+ notices.push(
6647
+ `Legacy skill move failed; original left intact: ${copy.sourcePath}: ${String(error)}. ${legacyOpenCodeSkillNotice(copy.sourcePath)}`
6648
+ );
6554
6649
  }
6555
- } finally {
6556
- rmSync4(transactionRoot, { recursive: true, force: true });
6557
6650
  }
6651
+ return { backupRoot, notices, legacyMoves };
6652
+ }
6653
+ function legacyOpenCodeSkillNotice(path) {
6654
+ 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
6655
  }
6559
6656
  function syncOpenCodeOwnedSkills(options = {}) {
6560
6657
  const skills = getOpenCodeOwnedSkillEntries(options);
6561
6658
  try {
6562
6659
  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 };
6660
+ return {
6661
+ success: true,
6662
+ status: options.dryRun ? "planned" : "installed",
6663
+ skills,
6664
+ ...planOrMoveLegacySkills(options)
6665
+ };
6566
6666
  } catch (error) {
6567
6667
  return {
6568
6668
  success: false,
@@ -6604,6 +6704,178 @@ function inspectPiPackageSkills(options) {
6604
6704
  }
6605
6705
  }
6606
6706
 
6707
+ // src/cli/system.ts
6708
+ import { spawnSync as spawnSync5 } from "child_process";
6709
+ import { statSync as statSync2 } from "fs";
6710
+ var cachedOpenCodePath = null;
6711
+ var OPENCODE_PROBE_TIMEOUT_MS = 1500;
6712
+ function quoteWindowsShellCommand(command) {
6713
+ if (!/[\s&()^<>"|]/.test(command)) {
6714
+ return command;
6715
+ }
6716
+ return `"${command.replace(/"/g, '\\"')}"`;
6717
+ }
6718
+ function getOpenCodeVersionInvocation(opencodePath, platform = process.platform) {
6719
+ const options = {
6720
+ stdout: "pipe",
6721
+ stderr: "pipe"
6722
+ };
6723
+ if (platform === "win32") {
6724
+ return {
6725
+ command: [`${quoteWindowsShellCommand(opencodePath)} --version`],
6726
+ options: { ...options, shell: true }
6727
+ };
6728
+ }
6729
+ return { command: [opencodePath, "--version"], options };
6730
+ }
6731
+ function getOpenCodePaths() {
6732
+ const home = process.env.HOME || process.env.USERPROFILE || "";
6733
+ return [
6734
+ // PATH (try this first)
6735
+ "opencode",
6736
+ // User local installations (Linux & macOS)
6737
+ `${home}/.local/bin/opencode`,
6738
+ `${home}/.opencode/bin/opencode`,
6739
+ `${home}/bin/opencode`,
6740
+ // System-wide installations
6741
+ "/usr/local/bin/opencode",
6742
+ "/opt/opencode/bin/opencode",
6743
+ "/usr/bin/opencode",
6744
+ "/bin/opencode",
6745
+ // macOS specific
6746
+ "/Applications/OpenCode.app/Contents/MacOS/opencode",
6747
+ `${home}/Applications/OpenCode.app/Contents/MacOS/opencode`,
6748
+ // Homebrew (macOS & Linux)
6749
+ "/opt/homebrew/bin/opencode",
6750
+ "/home/linuxbrew/.linuxbrew/bin/opencode",
6751
+ `${home}/homebrew/bin/opencode`,
6752
+ // macOS user Library
6753
+ `${home}/Library/Application Support/opencode/bin/opencode`,
6754
+ // Snap (Linux)
6755
+ "/snap/bin/opencode",
6756
+ "/var/snap/opencode/current/bin/opencode",
6757
+ // Flatpak (Linux)
6758
+ "/var/lib/flatpak/exports/bin/ai.opencode.OpenCode",
6759
+ `${home}/.local/share/flatpak/exports/bin/ai.opencode.OpenCode`,
6760
+ // Nix (Linux/macOS)
6761
+ "/nix/store/opencode/bin/opencode",
6762
+ `${home}/.nix-profile/bin/opencode`,
6763
+ "/run/current-system/sw/bin/opencode",
6764
+ // Cargo (Rust toolchain)
6765
+ `${home}/.cargo/bin/opencode`,
6766
+ // npm/npx global
6767
+ `${home}/.npm-global/bin/opencode`,
6768
+ "/usr/local/lib/node_modules/opencode/bin/opencode",
6769
+ // Yarn global
6770
+ `${home}/.yarn/bin/opencode`,
6771
+ // PNPM
6772
+ `${home}/.pnpm-global/bin/opencode`
6773
+ ];
6774
+ }
6775
+ function resolveOpenCodePath() {
6776
+ if (cachedOpenCodePath) {
6777
+ return cachedOpenCodePath;
6778
+ }
6779
+ const paths = getOpenCodePaths();
6780
+ for (const opencodePath of paths) {
6781
+ if (opencodePath === "opencode") continue;
6782
+ try {
6783
+ const stat = statSync2(opencodePath);
6784
+ if (stat.isFile()) {
6785
+ cachedOpenCodePath = opencodePath;
6786
+ return opencodePath;
6787
+ }
6788
+ } catch {
6789
+ }
6790
+ }
6791
+ return "opencode";
6792
+ }
6793
+ async function isOpenCodeInstalled() {
6794
+ const paths = getOpenCodePaths();
6795
+ for (const opencodePath of paths) {
6796
+ try {
6797
+ const invocation = getOpenCodeVersionInvocation(opencodePath);
6798
+ const proc = spawn(invocation.command, {
6799
+ ...invocation.options,
6800
+ windowsHide: true
6801
+ });
6802
+ let timer;
6803
+ const timedOut = /* @__PURE__ */ Symbol("timeout");
6804
+ try {
6805
+ const outcome = await Promise.race([
6806
+ proc.exited.then(() => proc.exitCode),
6807
+ new Promise((resolve11) => {
6808
+ timer = setTimeout(
6809
+ () => resolve11(timedOut),
6810
+ OPENCODE_PROBE_TIMEOUT_MS
6811
+ );
6812
+ })
6813
+ ]);
6814
+ if (outcome === timedOut) {
6815
+ proc.kill();
6816
+ continue;
6817
+ }
6818
+ if (outcome === 0) {
6819
+ cachedOpenCodePath = opencodePath;
6820
+ return true;
6821
+ }
6822
+ } finally {
6823
+ if (timer) {
6824
+ clearTimeout(timer);
6825
+ }
6826
+ }
6827
+ } catch {
6828
+ }
6829
+ }
6830
+ return false;
6831
+ }
6832
+ var OPENCODE_VERSION_REQUIREMENT = "OpenCode 2.2.6 or later is required; install or upgrade OpenCode before applying configuration.";
6833
+ function isSupportedOpenCodeVersion(output) {
6834
+ 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(
6835
+ output?.trim() ?? ""
6836
+ );
6837
+ if (!match) return false;
6838
+ const [, major, minor, patch] = match;
6839
+ return Number(major) > 2 || Number(major) === 2 && (Number(minor) > 2 || Number(minor) === 2 && Number(patch) >= 6);
6840
+ }
6841
+ function getOpenCodeVersionSync() {
6842
+ try {
6843
+ const invocation = getOpenCodeVersionInvocation(resolveOpenCodePath());
6844
+ const [command, ...args] = invocation.command;
6845
+ if (!command) return null;
6846
+ const result = spawnSync5(command, args, {
6847
+ shell: invocation.options.shell,
6848
+ windowsHide: true,
6849
+ encoding: "utf8",
6850
+ timeout: OPENCODE_PROBE_TIMEOUT_MS
6851
+ });
6852
+ return result.status === 0 ? result.stdout.trim() : null;
6853
+ } catch {
6854
+ return null;
6855
+ }
6856
+ }
6857
+ async function getOpenCodeVersion() {
6858
+ const opencodePath = resolveOpenCodePath();
6859
+ try {
6860
+ const invocation = getOpenCodeVersionInvocation(opencodePath);
6861
+ const proc = spawn(invocation.command, {
6862
+ ...invocation.options,
6863
+ windowsHide: true
6864
+ });
6865
+ const output = await new Response(proc.stdout).text();
6866
+ await proc.exited;
6867
+ if (proc.exitCode === 0) {
6868
+ return output.trim();
6869
+ }
6870
+ } catch {
6871
+ }
6872
+ return null;
6873
+ }
6874
+ function getOpenCodePath() {
6875
+ const path = resolveOpenCodePath();
6876
+ return path === "opencode" ? null : path;
6877
+ }
6878
+
6607
6879
  // src/cli/operations/opencode.ts
6608
6880
  var PACKAGE_NAME = "thoth-agents";
6609
6881
  var EXECUTING_PACKAGE_VERSION = resolveExecutingPackageVersion();
@@ -6813,7 +7085,7 @@ var opencodeOperationAdapter = {
6813
7085
  id: "opencode",
6814
7086
  displayName: "OpenCode",
6815
7087
  available: true,
6816
- description: "OpenCode plugin and configuration surfaces.",
7088
+ description: "OpenCode 2.2.6 or later: pinned plugin and configuration surfaces.",
6817
7089
  actions: openCodeActions
6818
7090
  };
6819
7091
  function targetForMainConfig(state) {
@@ -6822,7 +7094,7 @@ function targetForMainConfig(state) {
6822
7094
  path: getExistingConfigPath(),
6823
7095
  label: "OpenCode config",
6824
7096
  ...state ? { state } : {},
6825
- expected: EXPECTED_PLUGIN ? `plugin includes ${EXPECTED_PLUGIN}` : "plugin version unavailable"
7097
+ expected: EXPECTED_PLUGIN ? `plugins includes ${EXPECTED_PLUGIN}` : "plugins version unavailable"
6826
7098
  };
6827
7099
  }
6828
7100
  function targetForLiteConfig(state) {
@@ -6866,18 +7138,21 @@ function openCodeSkillTargets(context2) {
6866
7138
  observed: installed ? "required global skill installed" : "required global skill missing"
6867
7139
  };
6868
7140
  });
7141
+ const legacyCopies = inspectLegacyOpenCodeSkills(homeDir);
6869
7142
  const ownedTargets = getOpenCodeOwnedSkillEntries({
6870
7143
  homeDir
6871
7144
  }).map((skill) => {
6872
- const path = join8(skill.destinationPath, "SKILL.md");
7145
+ const path = join8(skill.sourcePath, "SKILL.md");
6873
7146
  const installed = existsSync10(path);
6874
7147
  return {
6875
7148
  kind: "skill",
6876
7149
  path,
6877
7150
  label: titleCaseSkillName(skill.name),
6878
7151
  state: installed ? "installed" : "missing",
6879
- expected: "required global OpenCode skill",
6880
- observed: installed ? "thoth-owned global skill installed" : "thoth-owned global skill missing"
7152
+ expected: "package-provided OpenCode skill",
7153
+ observed: `${installed ? "package-provided skill available" : "package-provided skill missing"}${legacyCopies.filter((copy) => copy.name === skill.name).map(
7154
+ (copy) => `; Warning: ${copy.sourcePath} shadows packaged skills; planned move to ${getLegacyOpenCodeSkillBackupRoot(homeDir)}/<new-run>/${copy.label}/${copy.name}; confirm with --move-legacy-opencode-skills`
7155
+ ).join("")}`
6881
7156
  };
6882
7157
  });
6883
7158
  const diagnostics = [];
@@ -6896,15 +7171,15 @@ function openCodeSkillTargets(context2) {
6896
7171
  };
6897
7172
  }
6898
7173
  function configPluginMarker(config) {
6899
- const plugins = Array.isArray(config?.plugin) ? config.plugin : [];
6900
- return plugins.length > 0 ? `plugin: ${JSON.stringify(plugins)}` : "plugin: []";
7174
+ return `plugins: ${JSON.stringify(pluginEntries(config?.plugins))}; legacy plugin: ${JSON.stringify(pluginEntries(config?.plugin))}`;
6901
7175
  }
6902
7176
  function hasExpectedPlugin(config) {
6903
- return EXPECTED_PLUGIN !== null && Array.isArray(config?.plugin) && config.plugin.includes(EXPECTED_PLUGIN);
7177
+ const current = pluginEntries(config?.plugins);
7178
+ return EXPECTED_PLUGIN !== null && current.filter(isManagedOpenCodePlugin).length === 1 && current.includes(EXPECTED_PLUGIN) && !pluginEntries(config?.plugin).some(isManagedOpenCodePlugin);
6904
7179
  }
6905
7180
  function hasManagedPluginDrift(config) {
6906
- return Array.isArray(config?.plugin) && config.plugin.some(
6907
- (plugin) => plugin === PACKAGE_NAME || plugin.startsWith(`${PACKAGE_NAME}@`)
7181
+ return [...pluginEntries(config?.plugins), ...pluginEntries(config?.plugin)].some(
7182
+ isManagedOpenCodePlugin
6908
7183
  ) && !hasExpectedPlugin(config);
6909
7184
  }
6910
7185
  function liteConfigMarker(config) {
@@ -7347,6 +7622,7 @@ function getOpenCodeStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
7347
7622
  const managedStatus = getOpenCodeManagedStatus(context2);
7348
7623
  return {
7349
7624
  ...managedStatus,
7625
+ summary: `${managedStatus.summary} Requires OpenCode 2.2.6 or later.`,
7350
7626
  targets: [
7351
7627
  ...managedStatus.targets,
7352
7628
  getCliManagedInstallVersionTarget("opencode", context2)
@@ -7479,7 +7755,6 @@ function buildOpenCodeCompletePlan(action, context2) {
7479
7755
  const expectedPlugin = `${PACKAGE_NAME}@${version}`;
7480
7756
  const generatedConfig = generateLiteConfig({
7481
7757
  agent: "opencode",
7482
- hasTmux: false,
7483
7758
  dryRun: true,
7484
7759
  reset: false
7485
7760
  });
@@ -7500,16 +7775,10 @@ function buildOpenCodeCompletePlan(action, context2) {
7500
7775
  title: `Ensure OpenCode plugin points at ${expectedPlugin}`,
7501
7776
  target: {
7502
7777
  ...targetForMainConfig(),
7503
- expected: `plugin includes ${expectedPlugin}`
7778
+ expected: `plugins includes ${expectedPlugin}`
7504
7779
  },
7505
7780
  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",
7781
+ preview: `plugins: ["${expectedPlugin}"]`,
7513
7782
  backup: defaultBackup(getExistingConfigPath())
7514
7783
  },
7515
7784
  {
@@ -7519,7 +7788,7 @@ function buildOpenCodeCompletePlan(action, context2) {
7519
7788
  backup: defaultBackup(getExistingLiteConfigPath())
7520
7789
  },
7521
7790
  {
7522
- title: "Synchronize global thoth-owned OpenCode skills",
7791
+ title: "Inspect package-provided thoth-owned OpenCode skills",
7523
7792
  target: {
7524
7793
  kind: "skill",
7525
7794
  label: "Thoth-owned OpenCode skills",
@@ -7585,7 +7854,6 @@ function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
7585
7854
  }
7586
7855
  const generatedConfig = generateLiteConfig({
7587
7856
  agent: "opencode",
7588
- hasTmux: false,
7589
7857
  dryRun: true,
7590
7858
  reset: false
7591
7859
  });
@@ -7598,19 +7866,13 @@ function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
7598
7866
  "opencode-sync-preview",
7599
7867
  "sync",
7600
7868
  "Sync OpenCode managed configuration",
7601
- "Preview OpenCode plugin entry, default-agent disablement, and thoth-agents config sync.",
7869
+ "Preview pinned OpenCode plugins and thoth-agents config sync (requires OpenCode 2.2.6 or later).",
7602
7870
  [
7603
7871
  {
7604
7872
  title: `Ensure OpenCode plugin points at ${PACKAGE_NAME}@${packageVersion.version}`,
7605
7873
  target: targetForMainConfig(),
7606
7874
  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",
7875
+ preview: `plugins: ["${PACKAGE_NAME}@${packageVersion.version}"]`,
7614
7876
  backup: defaultBackup(getExistingConfigPath())
7615
7877
  },
7616
7878
  {
@@ -7620,7 +7882,7 @@ function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
7620
7882
  backup: defaultBackup(litePath)
7621
7883
  },
7622
7884
  {
7623
- title: "Synchronize global thoth-owned OpenCode skills",
7885
+ title: "Inspect package-provided thoth-owned OpenCode skills",
7624
7886
  target: {
7625
7887
  kind: "skill",
7626
7888
  label: "Thoth-owned OpenCode skills",
@@ -7917,7 +8179,6 @@ function applyModelPlan(roles) {
7917
8179
  const parsed = parseConfig(targetPath);
7918
8180
  const generated = generateLiteConfig({
7919
8181
  agent: "opencode",
7920
- hasTmux: false,
7921
8182
  dryRun: false,
7922
8183
  reset: false
7923
8184
  });
@@ -8014,16 +8275,25 @@ function applyRequiredSkills(context2) {
8014
8275
  function applyOwnedSkills(context2) {
8015
8276
  const syncOwned = context2.syncOpenCodeOwnedSkills ?? syncOpenCodeOwnedSkills;
8016
8277
  const result = syncOwned({
8278
+ moveLegacyOpenCodeSkills: context2.moveLegacyOpenCodeSkills,
8017
8279
  homeDir: homeDirFromContext(context2)
8018
8280
  });
8019
- if (result.success) return { success: true, warnings: [] };
8281
+ if (result.success)
8282
+ return {
8283
+ success: true,
8284
+ warnings: (result.notices ?? []).map((message) => ({
8285
+ severity: "minor",
8286
+ code: "opencode-legacy-skill-copy",
8287
+ message
8288
+ }))
8289
+ };
8020
8290
  return {
8021
8291
  success: false,
8022
8292
  warnings: [
8023
8293
  {
8024
8294
  severity: "critical",
8025
8295
  code: "opencode-owned-skills-sync-failed",
8026
- message: `Failed to synchronize thoth-owned OpenCode skills: ${result.error ?? "unknown error"}.`
8296
+ message: `Failed to inspect package-provided thoth-owned OpenCode skills: ${result.error ?? "unknown error"}.`
8027
8297
  }
8028
8298
  ]
8029
8299
  };
@@ -8074,6 +8344,16 @@ function applyOpenCodePlan(plan) {
8074
8344
  issued.context
8075
8345
  );
8076
8346
  }
8347
+ const hostVersion = (issued.runtimeContext.getOpenCodeVersion ?? getOpenCodeVersionSync)();
8348
+ if (!isSupportedOpenCodeVersion(hostVersion)) {
8349
+ return rejectPlan3(
8350
+ plan,
8351
+ `${OPENCODE_VERSION_REQUIREMENT} Detected: ${hostVersion ?? "unavailable"}.`,
8352
+ "critical",
8353
+ "opencode-host-version-unsupported",
8354
+ issued.context
8355
+ );
8356
+ }
8077
8357
  if (issued.payload.kind === "model") {
8078
8358
  const invalidPayload = invalidModelPayloadReason(issued.payload.roles);
8079
8359
  if (invalidPayload) {
@@ -8093,8 +8373,7 @@ function applyOpenCodePlan(plan) {
8093
8373
  const updateMainConfig = issued.runtimeContext.updateOpenCodeMainConfig ?? updateOpenCodeMainConfig;
8094
8374
  const mainConfigResult = updateMainConfig({
8095
8375
  ensurePlugin: true,
8096
- pluginVersion: issued.payload.version,
8097
- disableDefaults: true
8376
+ pluginVersion: issued.payload.version
8098
8377
  });
8099
8378
  if (!mainConfigResult.success) {
8100
8379
  return rejectPlan3(
@@ -8107,7 +8386,7 @@ function applyOpenCodePlan(plan) {
8107
8386
  }
8108
8387
  changedTargets.push({
8109
8388
  ...targetForMainConfig("installed"),
8110
- observed: `plugin includes ${PACKAGE_NAME}@${issued.payload.version}`
8389
+ observed: `plugins includes ${PACKAGE_NAME}@${issued.payload.version}`
8111
8390
  });
8112
8391
  if (existsSync10(`${mainConfigResult.configPath}.bak`)) {
8113
8392
  backups.push({
@@ -8119,7 +8398,6 @@ function applyOpenCodePlan(plan) {
8119
8398
  const liteResult = writeLite(
8120
8399
  {
8121
8400
  agent: "opencode",
8122
- hasTmux: false,
8123
8401
  dryRun: false,
8124
8402
  reset: false
8125
8403
  },
@@ -8147,14 +8425,14 @@ function applyOpenCodePlan(plan) {
8147
8425
  kind: "skill",
8148
8426
  label: "Thoth-owned OpenCode skills",
8149
8427
  state: ownedSkills.success ? "installed" : "drift",
8150
- observed: ownedSkills.success ? "thoth-owned global skills synchronized" : "thoth-owned global skill synchronization failed"
8428
+ observed: ownedSkills.success ? "package-provided skills available" : "package-provided skill inspection failed"
8151
8429
  });
8152
8430
  if (!ownedSkills.success) {
8153
8431
  return {
8154
8432
  harness: "opencode",
8155
8433
  action: plan.action,
8156
8434
  applied: false,
8157
- summary: "OpenCode configuration was written, but thoth-owned skills failed to synchronize.",
8435
+ summary: "OpenCode configuration was written, but package-provided skills failed inspection.",
8158
8436
  changedTargets,
8159
8437
  backups,
8160
8438
  warnings,
@@ -8224,28 +8502,28 @@ function applyOpenCodePlan(plan) {
8224
8502
  }
8225
8503
 
8226
8504
  // src/cli/operations/pi.ts
8227
- import { spawnSync as spawnSync7 } from "child_process";
8505
+ import { spawnSync as spawnSync8 } from "child_process";
8228
8506
  import { createHash as createHash7 } from "crypto";
8229
8507
  import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
8230
8508
  import { basename as basename4, dirname as dirname11, isAbsolute as isAbsolute5, join as join15 } from "path";
8231
8509
 
8232
8510
  // src/cli/pi-install.ts
8233
- import { spawnSync as spawnSync6 } from "child_process";
8511
+ import { spawnSync as spawnSync7 } from "child_process";
8234
8512
  import { createHash as createHash6 } from "crypto";
8235
8513
  import {
8236
8514
  existsSync as existsSync14,
8237
8515
  lstatSync as lstatSync6,
8238
8516
  readdirSync as readdirSync3,
8239
8517
  readFileSync as readFileSync13,
8240
- realpathSync as realpathSync3,
8241
- statSync as statSync3
8518
+ realpathSync as realpathSync4,
8519
+ statSync as statSync4
8242
8520
  } from "fs";
8243
- import { basename as basename3, dirname as dirname10, isAbsolute as isAbsolute4, join as join14, resolve as resolve9, sep as sep3 } from "path";
8521
+ import { basename as basename3, dirname as dirname10, isAbsolute as isAbsolute4, join as join14, resolve as resolve10, sep as sep4 } from "path";
8244
8522
  import { fileURLToPath as fileURLToPath5 } from "url";
8245
8523
  import { lt } from "semver";
8246
8524
 
8247
8525
  // src/cli/pi-external-package.ts
8248
- import { readFileSync as readFileSync9, realpathSync as realpathSync2 } from "fs";
8526
+ import { readFileSync as readFileSync9, realpathSync as realpathSync3 } from "fs";
8249
8527
  import { isAbsolute as isAbsolute2, join as join9 } from "path";
8250
8528
  import { satisfies, valid } from "semver";
8251
8529
  function npmPackageName(source) {
@@ -8274,7 +8552,7 @@ function piExternalSourceMatches(candidate, expectedSource) {
8274
8552
  if (candidate.source === expectedSource) return true;
8275
8553
  if (!isAbsolute2(expectedSource) || !candidate.installedPath) return false;
8276
8554
  try {
8277
- return realpathSync2(candidate.installedPath) === realpathSync2(expectedSource);
8555
+ return realpathSync3(candidate.installedPath) === realpathSync3(expectedSource);
8278
8556
  } catch {
8279
8557
  return false;
8280
8558
  }
@@ -8360,7 +8638,7 @@ function inspectPiExternalPackage(packages, spec, requireManagedSource = true) {
8360
8638
  }
8361
8639
 
8362
8640
  // src/cli/pi-git-source.ts
8363
- import { resolve as resolve5, sep as sep2 } from "path";
8641
+ import { resolve as resolve6, sep as sep3 } from "path";
8364
8642
  import hostedGitInfo from "hosted-git-info";
8365
8643
  function splitRef(url) {
8366
8644
  const scpLikeMatch = url.match(/^git@([^:]+):(.+)$/);
@@ -8502,9 +8780,9 @@ function projectGitPackagePath(source, projectRoot) {
8502
8780
  return void 0;
8503
8781
  const parsed = parseGitUrl(source);
8504
8782
  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}`))
8783
+ const installRoot = resolve6(projectRoot, "git");
8784
+ const installedPath = resolve6(installRoot, parsed.host, parsed.path);
8785
+ if (installedPath !== installRoot && !installedPath.startsWith(`${installRoot}${sep3}`))
8508
8786
  return void 0;
8509
8787
  return installedPath;
8510
8788
  }
@@ -8517,7 +8795,7 @@ import {
8517
8795
  readdirSync as readdirSync2,
8518
8796
  readFileSync as readFileSync10,
8519
8797
  renameSync as renameSync4,
8520
- statSync as statSync2,
8798
+ statSync as statSync3,
8521
8799
  writeFileSync as writeFileSync3
8522
8800
  } from "fs";
8523
8801
  import { dirname as dirname7, join as join10 } from "path";
@@ -8561,7 +8839,7 @@ function migrateLegacyPiResources(options) {
8561
8839
  if (!existsSync11(source)) continue;
8562
8840
  const canonical = join10(options.packageRoot, "skills", name);
8563
8841
  const backup = `${source}.thoth-agents-legacy.bak`;
8564
- if (!statSync2(source).isDirectory() || !identicalTrees(source, canonical) || existsSync11(backup)) {
8842
+ if (!statSync3(source).isDirectory() || !identicalTrees(source, canonical) || existsSync11(backup)) {
8565
8843
  manualActions.push(
8566
8844
  `Legacy Pi skill ${name} is modified or has ambiguous backup ownership; remove it manually after review.`
8567
8845
  );
@@ -8611,16 +8889,16 @@ function migrateLegacyPiResources(options) {
8611
8889
  }
8612
8890
 
8613
8891
  // src/cli/pi-native-probe.ts
8614
- import { spawnSync as spawnSync5 } from "child_process";
8892
+ import { spawnSync as spawnSync6 } from "child_process";
8615
8893
  import {
8616
8894
  existsSync as existsSync12,
8617
- mkdtempSync as mkdtempSync2,
8895
+ mkdtempSync,
8618
8896
  readFileSync as readFileSync11,
8619
- rmSync as rmSync5,
8897
+ rmSync as rmSync4,
8620
8898
  writeFileSync as writeFileSync4
8621
8899
  } from "fs";
8622
8900
  import { tmpdir } from "os";
8623
- import { dirname as dirname8, join as join11, resolve as resolve6, win32 } from "path";
8901
+ import { dirname as dirname8, join as join11, resolve as resolve7, win32 } from "path";
8624
8902
  function resolvePiWindowsCliFromShim(shimPath, shimContents) {
8625
8903
  const target = shimContents.match(
8626
8904
  /"([^"\r\n]*@earendil-works[\\/]+pi-coding-agent[\\/]+dist[\\/]+bundle[\\/]+cli\.js)"\s+%\*/i
@@ -8633,7 +8911,7 @@ function resolvePiWindowsCliFromShim(shimPath, shimContents) {
8633
8911
  return win32.isAbsolute(expanded) ? win32.normalize(expanded) : void 0;
8634
8912
  }
8635
8913
  function findPiWindowsCli() {
8636
- const located = spawnSync5("where.exe", ["pi.cmd"], {
8914
+ const located = spawnSync6("where.exe", ["pi.cmd"], {
8637
8915
  windowsHide: true,
8638
8916
  encoding: "utf8"
8639
8917
  }).stdout?.split(/\r?\n/).find(Boolean);
@@ -8667,7 +8945,7 @@ function defaultExecutor(command, args, env) {
8667
8945
  executableArgs = [cli, ...args];
8668
8946
  }
8669
8947
  }
8670
- const result = spawnSync5(executable, executableArgs, {
8948
+ const result = spawnSync6(executable, executableArgs, {
8671
8949
  windowsHide: true,
8672
8950
  encoding: "utf8",
8673
8951
  timeout: 3e4,
@@ -8685,7 +8963,7 @@ function occurrences(text, marker) {
8685
8963
  }
8686
8964
  function observePiNativeRoot(options) {
8687
8965
  const ownsIsolatedRoot = options.piHome === void 0;
8688
- const isolatedRoot = options.piHome ?? mkdtempSync2(join11(tmpdir(), "thoth-pi-observer-"));
8966
+ const isolatedRoot = options.piHome ?? mkdtempSync(join11(tmpdir(), "thoth-pi-observer-"));
8689
8967
  const observerPath = join11(
8690
8968
  isolatedRoot,
8691
8969
  `observer-${process.pid}-${Date.now()}.mjs`
@@ -8765,7 +9043,7 @@ export default function observer(pi) {
8765
9043
  "--offline",
8766
9044
  "--no-extensions",
8767
9045
  "--extension",
8768
- resolve6(options.extensionPath),
9046
+ resolve7(options.extensionPath),
8769
9047
  "--extension",
8770
9048
  observerPath,
8771
9049
  "--provider",
@@ -8838,8 +9116,8 @@ ${result.stderr}`.split(/\r?\n/).flatMap((line) => {
8838
9116
  };
8839
9117
  } finally {
8840
9118
  if (ownsIsolatedRoot)
8841
- rmSync5(isolatedRoot, { recursive: true, force: true });
8842
- else rmSync5(observerPath, { force: true });
9119
+ rmSync4(isolatedRoot, { recursive: true, force: true });
9120
+ else rmSync4(observerPath, { force: true });
8843
9121
  }
8844
9122
  }
8845
9123
 
@@ -8849,11 +9127,11 @@ import {
8849
9127
  mkdirSync as mkdirSync6,
8850
9128
  readFileSync as readFileSync12,
8851
9129
  renameSync as renameSync5,
8852
- rmSync as rmSync6,
9130
+ rmSync as rmSync5,
8853
9131
  writeFileSync as writeFileSync5
8854
9132
  } from "fs";
8855
9133
  import { homedir as homedir5 } from "os";
8856
- import { dirname as dirname9, isAbsolute as isAbsolute3, join as join12, normalize, resolve as resolve7 } from "path";
9134
+ import { dirname as dirname9, isAbsolute as isAbsolute3, join as join12, normalize, resolve as resolve8 } from "path";
8857
9135
  var keys = [
8858
9136
  "schemaVersion",
8859
9137
  "owner",
@@ -8868,8 +9146,8 @@ var keys = [
8868
9146
  var semver = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
8869
9147
  var digest = /^[0-9a-f]{64}$/;
8870
9148
  function piPackagePathsEqual(left, right) {
8871
- const normalizedLeft = normalize(resolve7(left));
8872
- const normalizedRight = normalize(resolve7(right));
9149
+ const normalizedLeft = normalize(resolve8(left));
9150
+ const normalizedRight = normalize(resolve8(right));
8873
9151
  return process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight;
8874
9152
  }
8875
9153
  function getPiPackageReceiptPath(options = {}) {
@@ -8926,7 +9204,7 @@ function writePiPackageReceipt(receipt, options = {}) {
8926
9204
  try {
8927
9205
  renameSync5(temporary, path);
8928
9206
  } catch (error) {
8929
- rmSync6(temporary, { force: true });
9207
+ rmSync5(temporary, { force: true });
8930
9208
  throw error;
8931
9209
  }
8932
9210
  return { success: true, path };
@@ -8961,17 +9239,17 @@ function classifyPiPackageOwnership(input) {
8961
9239
 
8962
9240
  // src/cli/pi-paths.ts
8963
9241
  import { homedir as homedir6 } from "os";
8964
- import { join as join13, resolve as resolve8 } from "path";
9242
+ import { join as join13, resolve as resolve9 } from "path";
8965
9243
  function resolvePiPaths(options = {}) {
8966
9244
  const env = options.env ?? process.env;
8967
- const homeDir = resolve8(
9245
+ const homeDir = resolve9(
8968
9246
  options.homeDir ?? env.HOME ?? env.USERPROFILE ?? homedir6()
8969
9247
  );
8970
9248
  const defaultPiRoot = join13(homeDir, ".pi", "agent");
8971
9249
  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");
9250
+ const piRoot = configured ? resolve9(configured) : defaultPiRoot;
9251
+ const cwd = resolve9(options.cwd ?? process.cwd());
9252
+ const configRoot = env.XDG_CONFIG_HOME?.trim() ? resolve9(env.XDG_CONFIG_HOME) : join13(homeDir, ".config");
8975
9253
  return {
8976
9254
  homeDir,
8977
9255
  piRoot,
@@ -9299,7 +9577,7 @@ function projectLocalPackagePath(source, projectRoot, homeDir) {
9299
9577
  else if (path.startsWith("~/") || process.platform === "win32" && path.startsWith("~\\"))
9300
9578
  path = join14(homeDir, path.slice(2));
9301
9579
  else if (path.startsWith("file://")) path = fileURLToPath5(path);
9302
- return resolve9(projectRoot, path);
9580
+ return resolve10(projectRoot, path);
9303
9581
  }
9304
9582
  function legacyPiNpmPackagePath(name, settings, execute) {
9305
9583
  try {
@@ -9310,7 +9588,7 @@ function legacyPiNpmPackagePath(name, settings, execute) {
9310
9588
  if (/[\0\r\n&|<>^%"'`$;!]/.test(command)) return void 0;
9311
9589
  const manager = basename3(command).replace(/\.(cmd|exe)$/i, "");
9312
9590
  if (!["npm", "pnpm", "bun"].includes(manager)) return void 0;
9313
- if (/\s/.test(command) && !statSync3(command, { throwIfNoEntry: false })?.isFile())
9591
+ if (/\s/.test(command) && !statSync4(command, { throwIfNoEntry: false })?.isFile())
9314
9592
  return void 0;
9315
9593
  const query = (args) => {
9316
9594
  const result = execute(command, args);
@@ -9357,7 +9635,7 @@ function resolvePiPackage(candidate, options, userSettings) {
9357
9635
  candidate = { ...candidate, resourceOnly };
9358
9636
  if (candidate.installedPath) return candidate;
9359
9637
  const paths = resolvePiPaths(options);
9360
- const baseDir = candidate.scope === "user" ? paths.piRoot : join14(resolve9(options.cwd ?? process.cwd()), ".pi");
9638
+ const baseDir = candidate.scope === "user" ? paths.piRoot : join14(resolve10(options.cwd ?? process.cwd()), ".pi");
9361
9639
  const parsed = parsePiPackageSource(candidate.source, baseDir);
9362
9640
  const npmName = parsed.type === "npm" ? parsed.name : void 0;
9363
9641
  let path = npmName ? join14(baseDir, "npm", "node_modules", npmName) : parsed.type === "git" ? parsed.path : projectLocalPackagePath(candidate.source, baseDir, paths.homeDir);
@@ -9365,7 +9643,7 @@ function resolvePiPackage(candidate, options, userSettings) {
9365
9643
  path = legacyPiNpmPackagePath(
9366
9644
  npmName,
9367
9645
  userSettings,
9368
- options.commandExecutor ?? defaultCommandExecutor4
9646
+ options.commandExecutor ?? defaultCommandExecutor3
9369
9647
  ) ?? path;
9370
9648
  return {
9371
9649
  ...candidate,
@@ -9381,7 +9659,7 @@ function resolvePiListedPackages(output, options) {
9381
9659
  const packages = parsePiPackageList(output);
9382
9660
  const paths = resolvePiPaths(options);
9383
9661
  const settings = readJsonObject(paths.settingsPath);
9384
- const projectRoot = join14(resolve9(options.cwd ?? process.cwd()), ".pi");
9662
+ const projectRoot = join14(resolve10(options.cwd ?? process.cwd()), ".pi");
9385
9663
  let projectSettings;
9386
9664
  for (const { scope, source, installedPath } of packages) {
9387
9665
  const baseDir = scope === "user" ? paths.piRoot : projectRoot;
@@ -9407,7 +9685,7 @@ function resolvePiListedPackages(output, options) {
9407
9685
  });
9408
9686
  }
9409
9687
  function readPiProjectPackages(options) {
9410
- const projectRoot = join14(resolve9(options.cwd ?? process.cwd()), ".pi");
9688
+ const projectRoot = join14(resolve10(options.cwd ?? process.cwd()), ".pi");
9411
9689
  return resolvePiConfiguredPackages(
9412
9690
  readJsonObject(join14(projectRoot, "settings.json")),
9413
9691
  "project",
@@ -9435,28 +9713,28 @@ function resolvePiPreservationPackages(output, options) {
9435
9713
  ];
9436
9714
  }
9437
9715
  function scanPiProjectInstalledIncumbents(options, configuredPackages) {
9438
- const projectRoot = join14(resolve9(options.cwd ?? process.cwd()), ".pi");
9716
+ const projectRoot = join14(resolve10(options.cwd ?? process.cwd()), ".pi");
9439
9717
  const incumbents = [];
9440
9718
  const visited = /* @__PURE__ */ new Set();
9441
9719
  const pathKey = (path) => process.platform === "win32" ? path.toLowerCase() : path;
9442
9720
  const knownPaths = new Set(
9443
9721
  configuredPackages.flatMap(
9444
- ({ installedPath }) => installedPath ? [pathKey(realpathSync3(installedPath))] : []
9722
+ ({ installedPath }) => installedPath ? [pathKey(realpathSync4(installedPath))] : []
9445
9723
  )
9446
9724
  );
9447
9725
  for (const root of [join14(projectRoot, "git"), join14(projectRoot, "npm")]) {
9448
9726
  if (!existsSync14(root)) continue;
9449
- const rootKey = pathKey(realpathSync3(root));
9727
+ const rootKey = pathKey(realpathSync4(root));
9450
9728
  const pending = [root];
9451
9729
  while (pending.length > 0) {
9452
9730
  const path = pending.pop();
9453
- if (!path || !statSync3(path, { throwIfNoEntry: false })?.isDirectory())
9731
+ if (!path || !statSync4(path, { throwIfNoEntry: false })?.isDirectory())
9454
9732
  continue;
9455
- const key = pathKey(realpathSync3(path));
9733
+ const key = pathKey(realpathSync4(path));
9456
9734
  if (visited.has(key)) continue;
9457
9735
  visited.add(key);
9458
9736
  const manifestPath = join14(path, "package.json");
9459
- if (existsSync14(manifestPath) && statSync3(manifestPath).isFile()) {
9737
+ if (existsSync14(manifestPath) && statSync4(manifestPath).isFile()) {
9460
9738
  const candidate = {
9461
9739
  scope: "project",
9462
9740
  source: path,
@@ -9471,7 +9749,7 @@ function scanPiProjectInstalledIncumbents(options, configuredPackages) {
9471
9749
  identityLimitation: `Installed manifest at ${path} identifies ${identity.packageName}, but no configured settings source maps to this directory.`
9472
9750
  });
9473
9751
  }
9474
- if (key !== rootKey && !key.startsWith(`${rootKey}${sep3}`)) continue;
9752
+ if (key !== rootKey && !key.startsWith(`${rootKey}${sep4}`)) continue;
9475
9753
  for (const entry of readdirSync3(path, { withFileTypes: true }))
9476
9754
  if (entry.isDirectory() || entry.isSymbolicLink())
9477
9755
  pending.push(join14(path, entry.name));
@@ -9585,7 +9863,7 @@ function buildPiSetupPlan(options = {}) {
9585
9863
  if (options.runtimePackageRoot) {
9586
9864
  const runtimeRoot = options.runtimePackageRoot;
9587
9865
  const spec = getPiExternalPackageSpecs(options)[0];
9588
- const sourceIsNormalizedAbsolutePath = isAbsolute4(runtimeRoot) && resolve9(runtimeRoot) === runtimeRoot;
9866
+ const sourceIsNormalizedAbsolutePath = isAbsolute4(runtimeRoot) && resolve10(runtimeRoot) === runtimeRoot;
9589
9867
  const inspection = sourceIsNormalizedAbsolutePath ? inspectPiExternalPackage(
9590
9868
  [{ scope: "user", source: runtimeRoot, installedPath: runtimeRoot }],
9591
9869
  spec
@@ -9643,7 +9921,7 @@ function buildPiSetupPlan(options = {}) {
9643
9921
  blockers.push(error instanceof Error ? error.message : String(error));
9644
9922
  }
9645
9923
  const projectSettingsPath = join14(
9646
- resolve9(options.cwd ?? process.cwd()),
9924
+ resolve10(options.cwd ?? process.cwd()),
9647
9925
  ".pi",
9648
9926
  "settings.json"
9649
9927
  );
@@ -9935,7 +10213,7 @@ function getPiFirstPartyPackages(packages, knownSource) {
9935
10213
  function matchesInstallSource(candidate, installSource) {
9936
10214
  return isAbsolute4(installSource) ? candidate.installedPath !== void 0 && piPackagePathsEqual(candidate.installedPath, installSource) : candidate.source === installSource;
9937
10215
  }
9938
- function defaultCommandExecutor4(command, args) {
10216
+ function defaultCommandExecutor3(command, args) {
9939
10217
  let executable = command;
9940
10218
  let executableArgs = [...args];
9941
10219
  if (process.platform === "win32" && command === "pi") {
@@ -9945,7 +10223,7 @@ function defaultCommandExecutor4(command, args) {
9945
10223
  executableArgs = [cli, ...args];
9946
10224
  }
9947
10225
  }
9948
- const result = spawnSync6(executable, executableArgs, {
10226
+ const result = spawnSync7(executable, executableArgs, {
9949
10227
  windowsHide: true,
9950
10228
  encoding: "utf8",
9951
10229
  timeout: PI_COMMAND_TIMEOUT_MS,
@@ -9973,7 +10251,7 @@ function applyPiSetup(plan) {
9973
10251
  };
9974
10252
  if (plan.dryRun)
9975
10253
  return { success: true, changed: [], diagnostics, installedPackages: [] };
9976
- const execute = plan.options.commandExecutor ?? defaultCommandExecutor4;
10254
+ const execute = plan.options.commandExecutor ?? defaultCommandExecutor3;
9977
10255
  const changed = [];
9978
10256
  const installedPackages = [];
9979
10257
  let receiptCommitted = false;
@@ -10613,7 +10891,7 @@ function statusFromPlan(plan, context2, evidence = {}) {
10613
10891
  description: item.kind === "package" ? "Validated installed-package manifest evidence; this does not prove live tool availability." : void 0
10614
10892
  }));
10615
10893
  const execute = context2.piCommandExecutor ?? ((command, args) => {
10616
- const result = spawnSync7(command, [...args], {
10894
+ const result = spawnSync8(command, [...args], {
10617
10895
  windowsHide: true,
10618
10896
  encoding: "utf8",
10619
10897
  timeout: 5e3,
@@ -11371,11 +11649,19 @@ function applyPiPlan(plan) {
11371
11649
  homeDir: source.context.homeDir
11372
11650
  }
11373
11651
  });
11374
- if (!completion.success)
11375
- return reject(
11652
+ const completionEvidence = getInstallCompletionEvidence(completion, {
11653
+ codePrefix: "pi",
11654
+ version: source.version,
11655
+ fallbackError: "Pi provider setup or ledger commit failed."
11656
+ });
11657
+ if (!completion.success) {
11658
+ const rejected = reject(
11376
11659
  completion.error ?? "Pi provider setup or ledger commit failed.",
11377
- [...setupTargets, ...skillTargets]
11660
+ [...setupTargets, ...skillTargets, ...completionEvidence.targets]
11378
11661
  );
11662
+ rejected.warnings.push(...completionEvidence.warnings);
11663
+ return rejected;
11664
+ }
11379
11665
  return {
11380
11666
  harness: "pi",
11381
11667
  action: plan.action,
@@ -11384,12 +11670,7 @@ function applyPiPlan(plan) {
11384
11670
  changedTargets: [
11385
11671
  ...setupTargets,
11386
11672
  ...skillTargets,
11387
- {
11388
- kind: "file",
11389
- path: completion.ledger.path,
11390
- label: "CLI-managed install version",
11391
- state: "installed"
11392
- }
11673
+ ...completionEvidence.targets
11393
11674
  ],
11394
11675
  diagnosticTargets: packageSkills.skills.map((skill) => ({
11395
11676
  kind: "skill",
@@ -11398,7 +11679,7 @@ function applyPiPlan(plan) {
11398
11679
  state: "installed"
11399
11680
  })),
11400
11681
  backups: [],
11401
- warnings: [],
11682
+ warnings: completionEvidence.warnings,
11402
11683
  disclaimers: disclaimers()
11403
11684
  };
11404
11685
  }
@@ -11962,6 +12243,7 @@ function buildRestoreModelPlan(harness, options, source = context) {
11962
12243
  return buildTuiModelPlan(harness, roles, source);
11963
12244
  }
11964
12245
  var defaultTuiOperations = {
12246
+ legacyOpenCodeSkillCopies: () => inspectLegacyOpenCodeSkills().map((copy) => copy.sourcePath),
11965
12247
  status(harness, evidence) {
11966
12248
  if (harness === "opencode") return getOpenCodeStatus(context, evidence);
11967
12249
  if (harness === "claude") {
@@ -11983,8 +12265,12 @@ var defaultTuiOperations = {
11983
12265
  },
11984
12266
  plan(harness, action, options) {
11985
12267
  if (harness === "opencode") {
11986
- if (action === "install") return buildOpenCodeInstallPlan(context);
11987
- if (action === "update") return buildOpenCodeUpdatePlan(context);
12268
+ const consentContext2 = {
12269
+ ...context,
12270
+ moveLegacyOpenCodeSkills: options?.moveLegacyOpenCodeSkills === true
12271
+ };
12272
+ if (action === "install") return buildOpenCodeInstallPlan(consentContext2);
12273
+ if (action === "update") return buildOpenCodeUpdatePlan(consentContext2);
11988
12274
  if (action === "sync") return buildOpenCodeSyncPlan(context);
11989
12275
  return buildTuiModelPlan(harness, getOpenCodeModelRoles());
11990
12276
  }
@@ -12061,8 +12347,15 @@ export {
12061
12347
  buildClaudeCodeSetupPlan,
12062
12348
  applyClaudeCodeSetup,
12063
12349
  formatClaudeCodeSetupPlan,
12350
+ isOpenCodeInstalled,
12351
+ OPENCODE_VERSION_REQUIREMENT,
12352
+ isSupportedOpenCodeVersion,
12353
+ getOpenCodeVersion,
12354
+ getOpenCodePath,
12064
12355
  recordHarnessInstallCompletion,
12065
12356
  finalizeHarnessInstall,
12357
+ getLegacyOpenCodeSkillBackupRoot,
12358
+ inspectLegacyOpenCodeSkills,
12066
12359
  syncOpenCodeOwnedSkills,
12067
12360
  inspectPiPackageSkills,
12068
12361
  buildPiSetupPlan,