thoth-agents 0.3.8 → 0.3.9

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.
@@ -15,6 +15,7 @@ import {
15
15
  createStepBudgetSection,
16
16
  ensureConfigDir,
17
17
  exa,
18
+ findPackageRoot,
18
19
  generateLiteConfig,
19
20
  getAgentOverride,
20
21
  getAgentPackContract,
@@ -24,14 +25,16 @@ import {
24
25
  getExistingLiteConfigPath,
25
26
  getOpenCodeManagedModelStatePath,
26
27
  getPrimaryModelId,
28
+ isValidPackageVersion,
27
29
  loadAgentPrompt,
28
30
  parseConfig,
29
31
  renderPromptSection,
30
32
  renderRolePrompt,
33
+ resolveExecutingPackageVersion,
31
34
  updateOpenCodeMainConfig,
32
35
  writeConfig,
33
36
  writeLiteConfig
34
- } from "./chunk-F2K4CS5J.js";
37
+ } from "./chunk-2TLT6WWY.js";
35
38
 
36
39
  // src/cli/codex-paths.ts
37
40
  import { homedir } from "os";
@@ -83,7 +86,7 @@ function isExplicitEffort(selection) {
83
86
  }
84
87
 
85
88
  // src/cli/codex-install.ts
86
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
89
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
87
90
  import { basename } from "path";
88
91
  import { fileURLToPath as fileURLToPath2 } from "url";
89
92
 
@@ -1540,27 +1543,6 @@ function readManagedModelState(path, version) {
1540
1543
  return parseManagedModelStateJson(readFileSync3(path, "utf8"), version);
1541
1544
  }
1542
1545
 
1543
- // src/cli/package-root.ts
1544
- import { existsSync as existsSync4 } from "fs";
1545
- import { dirname as dirname4, join as join2, parse } from "path";
1546
- function findPackageRoot(startDir) {
1547
- let currentDir = startDir;
1548
- const filesystemRoot = parse(startDir).root;
1549
- while (true) {
1550
- if (existsSync4(join2(currentDir, "package.json"))) {
1551
- return currentDir;
1552
- }
1553
- if (currentDir === filesystemRoot) {
1554
- return null;
1555
- }
1556
- const parentDir = dirname4(currentDir);
1557
- if (parentDir === currentDir) {
1558
- return null;
1559
- }
1560
- currentDir = parentDir;
1561
- }
1562
- }
1563
-
1564
1546
  // src/cli/codex-install.ts
1565
1547
  var ROOT_START = "<!-- thoth-agents:codex-root:start -->";
1566
1548
  var ROOT_END = "<!-- thoth-agents:codex-root:end -->";
@@ -1672,7 +1654,7 @@ function applyCodexManagedModelOverrides(config, overrides) {
1672
1654
  `Missing Codex role TOML content for ${override.role}.`
1673
1655
  );
1674
1656
  }
1675
- const before = existsSync5(roleItem.targetPath) ? readFileSync4(roleItem.targetPath, "utf8") : roleItem.content;
1657
+ const before = existsSync4(roleItem.targetPath) ? readFileSync4(roleItem.targetPath, "utf8") : roleItem.content;
1676
1658
  const model = normalizeCodexRuntimeModel(override.model);
1677
1659
  let updated = replaceRoleTomlModel(before, model);
1678
1660
  if (override.clearEffort) {
@@ -1725,7 +1707,7 @@ function resolveRoleTomlContent(options) {
1725
1707
  options.nextState.configuredEfforts ??= {};
1726
1708
  options.nextState.configuredEfforts[key] = configuredEffort;
1727
1709
  }
1728
- if (options.reset || !existsSync5(options.targetPath)) {
1710
+ if (options.reset || !existsSync4(options.targetPath)) {
1729
1711
  options.nextState.models[key] = renderedModel;
1730
1712
  if (configuredModel !== void 0) {
1731
1713
  options.nextState.configuredModels ??= {};
@@ -1799,7 +1781,7 @@ function buildCodexSetupPlan(config) {
1799
1781
  action: "write-role-toml",
1800
1782
  targetPath: target.path,
1801
1783
  description: `Materialize Codex role subagent ${target.role}.`,
1802
- requiresBackup: existsSync5(target.path),
1784
+ requiresBackup: existsSync4(target.path),
1803
1785
  role: target.role,
1804
1786
  renderedModel: parseRoleTomlModel(
1805
1787
  roleArtifactContent(target.role, render.artifacts)
@@ -1818,7 +1800,7 @@ function buildCodexSetupPlan(config) {
1818
1800
  action: "write-managed-model-state",
1819
1801
  targetPath: targets.managedModelsPath,
1820
1802
  description: "Record thoth-agents-managed Codex role model ownership state.",
1821
- requiresBackup: existsSync5(targets.managedModelsPath),
1803
+ requiresBackup: existsSync4(targets.managedModelsPath),
1822
1804
  content: stableJson3(nextManagedModelState)
1823
1805
  },
1824
1806
  {
@@ -1885,7 +1867,7 @@ function applyCodexSetup(plan) {
1885
1867
  }
1886
1868
  if (item.content === void 0) continue;
1887
1869
  const content = item.action === "merge-managed-block" ? mergeManagedBlock(
1888
- existsSync5(item.targetPath) ? readFileSync4(item.targetPath, "utf8") : "",
1870
+ existsSync4(item.targetPath) ? readFileSync4(item.targetPath, "utf8") : "",
1889
1871
  item.content
1890
1872
  ) : item.content;
1891
1873
  if (writeTextWithBackup(item.targetPath, content))
@@ -2473,11 +2455,123 @@ function formatClaudeCodeSetupPlan(plan) {
2473
2455
  return ["Claude Code setup plan:", ...lines].join("\n");
2474
2456
  }
2475
2457
 
2476
- // src/cli/skills.ts
2477
- import { spawnSync as spawnSync2 } from "child_process";
2478
- import { existsSync as existsSync6 } from "fs";
2458
+ // src/cli/install-ledger.ts
2459
+ import {
2460
+ copyFileSync as copyFileSync3,
2461
+ existsSync as existsSync5,
2462
+ mkdirSync as mkdirSync3,
2463
+ readFileSync as readFileSync5,
2464
+ renameSync as renameSync2,
2465
+ rmSync,
2466
+ writeFileSync as writeFileSync3
2467
+ } from "fs";
2479
2468
  import { homedir as homedir2 } from "os";
2480
- import { join as join3 } from "path";
2469
+ import { dirname as dirname4, join as join2 } from "path";
2470
+ var INSTALL_LEDGER_SCHEMA_VERSION = 1;
2471
+ var INSTALL_HARNESSES = /* @__PURE__ */ new Set([
2472
+ "opencode",
2473
+ "codex",
2474
+ "claude"
2475
+ ]);
2476
+ function hasOnlyKeys(value, keys) {
2477
+ const actual = Object.keys(value);
2478
+ return actual.length === keys.length && actual.every((key) => keys.includes(key));
2479
+ }
2480
+ function parseInstallLedger(serialized) {
2481
+ let parsed;
2482
+ try {
2483
+ parsed = JSON.parse(serialized);
2484
+ } catch (error) {
2485
+ return { success: false, error: `Malformed install ledger JSON: ${error}` };
2486
+ }
2487
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2488
+ return { success: false, error: "Install ledger must be a JSON object." };
2489
+ }
2490
+ const root = parsed;
2491
+ if (!hasOnlyKeys(root, ["schemaVersion", "harnesses"]) || root.schemaVersion !== INSTALL_LEDGER_SCHEMA_VERSION || !root.harnesses || typeof root.harnesses !== "object" || Array.isArray(root.harnesses)) {
2492
+ return { success: false, error: "Install ledger schema is unsupported." };
2493
+ }
2494
+ const harnesses = root.harnesses;
2495
+ for (const [harness, value] of Object.entries(harnesses)) {
2496
+ if (!INSTALL_HARNESSES.has(harness) || !value || typeof value !== "object" || Array.isArray(value) || !hasOnlyKeys(value, ["version"]) || !isValidPackageVersion(value.version)) {
2497
+ return {
2498
+ success: false,
2499
+ error: `Install ledger record for ${harness} is invalid.`
2500
+ };
2501
+ }
2502
+ }
2503
+ return { success: true, ledger: parsed };
2504
+ }
2505
+ function getInstallLedgerPath(options = {}) {
2506
+ const env = options.env ?? process.env;
2507
+ const configuredRoot = env.XDG_CONFIG_HOME?.trim();
2508
+ const homeDir = options.homeDir ?? env.HOME ?? env.USERPROFILE ?? homedir2();
2509
+ const configRoot = options.configRoot ?? (configuredRoot || join2(homeDir, ".config"));
2510
+ return join2(configRoot, "thoth-agents", "install-state.json");
2511
+ }
2512
+ function readInstallLedger(options = {}) {
2513
+ const path = getInstallLedgerPath(options);
2514
+ if (!existsSync5(path)) return { status: "missing", path };
2515
+ let serialized;
2516
+ try {
2517
+ serialized = readFileSync5(path, "utf8");
2518
+ } catch (error) {
2519
+ return {
2520
+ status: "invalid",
2521
+ path,
2522
+ error: `Could not read install ledger: ${error}`
2523
+ };
2524
+ }
2525
+ const parsed = parseInstallLedger(serialized);
2526
+ return parsed.success ? { status: "valid", path, ledger: parsed.ledger } : { status: "invalid", path, error: parsed.error };
2527
+ }
2528
+ function recordCompletedInstall(options) {
2529
+ const { harness, version } = options;
2530
+ const path = getInstallLedgerPath(options);
2531
+ if (!INSTALL_HARNESSES.has(harness) || !isValidPackageVersion(version)) {
2532
+ return {
2533
+ success: false,
2534
+ path,
2535
+ error: "A supported harness and valid package version are required."
2536
+ };
2537
+ }
2538
+ const current = readInstallLedger(options);
2539
+ const ledger = {
2540
+ schemaVersion: INSTALL_LEDGER_SCHEMA_VERSION,
2541
+ harnesses: current.status === "valid" ? { ...current.ledger.harnesses } : {}
2542
+ };
2543
+ ledger.harnesses[harness] = { version };
2544
+ const temporaryPath = `${path}.tmp`;
2545
+ const repairedInvalidState = current.status === "invalid";
2546
+ const backupPath = repairedInvalidState ? `${path}.bak` : void 0;
2547
+ try {
2548
+ mkdirSync3(dirname4(path), { recursive: true });
2549
+ writeFileSync3(temporaryPath, `${JSON.stringify(ledger, null, 2)}
2550
+ `);
2551
+ if (backupPath) copyFileSync3(path, backupPath);
2552
+ (options.renameFile ?? renameSync2)(temporaryPath, path);
2553
+ return {
2554
+ success: true,
2555
+ path,
2556
+ ledger,
2557
+ repairedInvalidState,
2558
+ ...backupPath ? { backupPath } : {}
2559
+ };
2560
+ } catch (error) {
2561
+ try {
2562
+ rmSync(temporaryPath, { recursive: true, force: true });
2563
+ } catch {
2564
+ }
2565
+ return {
2566
+ success: false,
2567
+ path,
2568
+ error: `Could not record completed install: ${error}`
2569
+ };
2570
+ }
2571
+ }
2572
+
2573
+ // src/cli/thoth-mem-install.ts
2574
+ import { spawnSync as spawnSync2 } from "child_process";
2481
2575
 
2482
2576
  // src/cli/npx-command.ts
2483
2577
  function getNpxCommand(args, options = {}) {
@@ -2490,7 +2584,226 @@ function getNpxCommand(args, options = {}) {
2490
2584
  };
2491
2585
  }
2492
2586
 
2587
+ // src/cli/thoth-mem-install.ts
2588
+ var THOTH_MEM_SETUP_TIMEOUT_MS = 12e4;
2589
+ var STATUS_EXIT_CODE = {
2590
+ complete: 0,
2591
+ failed: 1,
2592
+ partial: 2,
2593
+ requires_user_action: 3
2594
+ };
2595
+ var SETUP_STATUSES = new Set(
2596
+ Object.keys(STATUS_EXIT_CODE)
2597
+ );
2598
+ var STEP_OUTCOMES = /* @__PURE__ */ new Set([
2599
+ "planned",
2600
+ "skipped",
2601
+ "confirmed",
2602
+ "failed",
2603
+ "unavailable"
2604
+ ]);
2605
+ function isRecord4(value) {
2606
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2607
+ }
2608
+ function isStringArray(value) {
2609
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
2610
+ }
2611
+ function isSetupStep(value) {
2612
+ return isRecord4(value) && typeof value.name === "string" && value.name.length > 0 && typeof value.outcome === "string" && STEP_OUTCOMES.has(value.outcome);
2613
+ }
2614
+ function isProviderSetupResult(value, expectedHarness) {
2615
+ return isRecord4(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");
2616
+ }
2617
+ function parseProviderSetupResult(stdout, expectedHarness) {
2618
+ let value;
2619
+ try {
2620
+ value = JSON.parse(stdout.trim());
2621
+ } catch {
2622
+ return void 0;
2623
+ }
2624
+ return isProviderSetupResult(value, expectedHarness) ? value : void 0;
2625
+ }
2626
+ function defaultCommandExecutor2(command, args, options) {
2627
+ const result = spawnSync2(command, [...args], {
2628
+ cwd: options.cwd,
2629
+ encoding: "utf8",
2630
+ stdio: ["ignore", "pipe", "pipe"],
2631
+ timeout: options.timeoutMs
2632
+ });
2633
+ return {
2634
+ exitCode: result.status,
2635
+ stdout: result.stdout ?? "",
2636
+ stderr: result.stderr ?? "",
2637
+ error: result.error
2638
+ };
2639
+ }
2640
+ function describeError(error) {
2641
+ return error instanceof Error ? error.message : String(error);
2642
+ }
2643
+ function isTimeoutError(error) {
2644
+ return isRecord4(error) && error.code === "ETIMEDOUT";
2645
+ }
2646
+ function invalidResult(harness, command, args, exitCode, error) {
2647
+ return {
2648
+ success: false,
2649
+ evidenceValid: false,
2650
+ status: "invalid",
2651
+ changed: false,
2652
+ harness,
2653
+ steps: [],
2654
+ diagnostics: [],
2655
+ manualActions: [],
2656
+ receipt: null,
2657
+ command,
2658
+ args,
2659
+ exitCode,
2660
+ error
2661
+ };
2662
+ }
2663
+ function getThothMemSetupCommand(harness, dryRun, options = {}) {
2664
+ return getNpxCommand(
2665
+ [
2666
+ "-y",
2667
+ "thoth-mem@latest",
2668
+ "setup",
2669
+ harness,
2670
+ "--scope",
2671
+ "global",
2672
+ ...dryRun ? ["--plan"] : [],
2673
+ "--json"
2674
+ ],
2675
+ options
2676
+ );
2677
+ }
2678
+ function runThothMemSetup(options) {
2679
+ const { command, args } = getThothMemSetupCommand(
2680
+ options.harness,
2681
+ options.dryRun ?? false
2682
+ );
2683
+ const executor = options.commandExecutor ?? defaultCommandExecutor2;
2684
+ let commandResult;
2685
+ try {
2686
+ commandResult = executor(command, args, {
2687
+ cwd: options.cwd,
2688
+ timeoutMs: THOTH_MEM_SETUP_TIMEOUT_MS
2689
+ });
2690
+ } catch (error) {
2691
+ return invalidResult(
2692
+ options.harness,
2693
+ command,
2694
+ args,
2695
+ null,
2696
+ `Unable to launch thoth-mem setup: ${describeError(error)}`
2697
+ );
2698
+ }
2699
+ if (commandResult.error) {
2700
+ const stderr = commandResult.stderr.trim().slice(0, 2e3);
2701
+ return invalidResult(
2702
+ options.harness,
2703
+ command,
2704
+ args,
2705
+ commandResult.exitCode,
2706
+ isTimeoutError(commandResult.error) ? `thoth-mem setup timed out after ${THOTH_MEM_SETUP_TIMEOUT_MS} ms${stderr ? `: ${stderr}` : "."}` : `Unable to launch thoth-mem setup: ${describeError(commandResult.error)}`
2707
+ );
2708
+ }
2709
+ const provider = parseProviderSetupResult(
2710
+ commandResult.stdout,
2711
+ options.harness
2712
+ );
2713
+ if (!provider) {
2714
+ const stderr = commandResult.stderr.trim();
2715
+ return invalidResult(
2716
+ options.harness,
2717
+ command,
2718
+ args,
2719
+ commandResult.exitCode,
2720
+ stderr ? `thoth-mem returned invalid JSON evidence: ${stderr.slice(0, 2e3)}` : "thoth-mem returned invalid JSON evidence."
2721
+ );
2722
+ }
2723
+ const expectedExitCode = STATUS_EXIT_CODE[provider.status];
2724
+ const evidenceValid = commandResult.exitCode === expectedExitCode;
2725
+ return {
2726
+ success: evidenceValid && provider.status === "complete",
2727
+ evidenceValid,
2728
+ status: provider.status,
2729
+ changed: provider.changed,
2730
+ harness: provider.harness,
2731
+ target: provider.target,
2732
+ steps: provider.steps,
2733
+ diagnostics: provider.diagnostics,
2734
+ manualActions: provider.manual_actions,
2735
+ receipt: provider.receipt,
2736
+ command,
2737
+ args,
2738
+ exitCode: commandResult.exitCode,
2739
+ ...!evidenceValid ? {
2740
+ error: `thoth-mem exit code ${String(commandResult.exitCode)} contradicts reported status ${provider.status} (expected ${expectedExitCode}).`
2741
+ } : {}
2742
+ };
2743
+ }
2744
+
2745
+ // src/cli/install-completion.ts
2746
+ function isConsistentProviderSuccess(result, harness) {
2747
+ return result.success && result.evidenceValid && result.status === "complete" && result.exitCode === 0 && result.harness === harness;
2748
+ }
2749
+ function finalizeHarnessInstall(options) {
2750
+ const ledgerPath = getInstallLedgerPath(options.ledgerOptions);
2751
+ const setup = options.runThothMemSetup ?? runThothMemSetup;
2752
+ const provider = setup({
2753
+ harness: options.harness,
2754
+ dryRun: options.dryRun,
2755
+ cwd: options.cwd
2756
+ });
2757
+ if (!isConsistentProviderSuccess(provider, options.harness)) {
2758
+ return {
2759
+ success: false,
2760
+ provider,
2761
+ ledger: { status: "not-attempted", path: ledgerPath },
2762
+ error: provider.error ?? "thoth-mem setup did not return consistent complete evidence."
2763
+ };
2764
+ }
2765
+ if (options.dryRun) {
2766
+ return {
2767
+ success: true,
2768
+ provider,
2769
+ ledger: { status: "planned", path: ledgerPath }
2770
+ };
2771
+ }
2772
+ const record = options.recordCompletedInstall ?? recordCompletedInstall;
2773
+ const ledger = record({
2774
+ ...options.ledgerOptions,
2775
+ harness: options.harness,
2776
+ version: options.version
2777
+ });
2778
+ if (!ledger.success) {
2779
+ return {
2780
+ success: false,
2781
+ provider,
2782
+ ledger: {
2783
+ status: "failed",
2784
+ path: ledger.path,
2785
+ error: ledger.error
2786
+ },
2787
+ error: ledger.error
2788
+ };
2789
+ }
2790
+ return {
2791
+ success: true,
2792
+ provider,
2793
+ ledger: {
2794
+ status: "recorded",
2795
+ path: ledger.path,
2796
+ repairedInvalidState: ledger.repairedInvalidState,
2797
+ ...ledger.backupPath ? { backupPath: ledger.backupPath } : {}
2798
+ }
2799
+ };
2800
+ }
2801
+
2493
2802
  // src/cli/skills.ts
2803
+ import { spawnSync as spawnSync3 } from "child_process";
2804
+ import { existsSync as existsSync6 } from "fs";
2805
+ import { homedir as homedir3 } from "os";
2806
+ import { join as join3 } from "path";
2494
2807
  var SKILLS_CLI_AGENT = {
2495
2808
  opencode: "opencode",
2496
2809
  codex: "codex",
@@ -2530,19 +2843,19 @@ var REQUIRED_SKILLS = [
2530
2843
  description: "Conditional product and architecture decision interview"
2531
2844
  }
2532
2845
  ];
2533
- function getRequiredSkillPaths(skill, harness, homeDir = homedir2()) {
2846
+ function getRequiredSkillPaths(skill, harness, homeDir = homedir3()) {
2534
2847
  return GLOBAL_SKILL_ROOTS[harness].map(
2535
2848
  (root) => join3(homeDir, ...root, skill.skillName, "SKILL.md")
2536
2849
  );
2537
2850
  }
2538
- function getRequiredSkillPath(skill, harness, homeDir = homedir2()) {
2851
+ function getRequiredSkillPath(skill, harness, homeDir = homedir3()) {
2539
2852
  const [primaryPath] = getRequiredSkillPaths(skill, harness, homeDir);
2540
2853
  if (!primaryPath) {
2541
2854
  throw new Error(`No global skill root is configured for ${harness}.`);
2542
2855
  }
2543
2856
  return primaryPath;
2544
2857
  }
2545
- function getInstalledRequiredSkillPath(skill, harness, homeDir = homedir2()) {
2858
+ function getInstalledRequiredSkillPath(skill, harness, homeDir = homedir3()) {
2546
2859
  return getRequiredSkillPaths(skill, harness, homeDir).find(
2547
2860
  (path) => existsSync6(path)
2548
2861
  );
@@ -2585,7 +2898,7 @@ function installRequiredSkill(skill, harness, options = {}) {
2585
2898
  options
2586
2899
  );
2587
2900
  try {
2588
- const result = spawnSync2(command, args, { stdio: "inherit" });
2901
+ const result = spawnSync3(command, args, { stdio: "inherit" });
2589
2902
  if (result.status === 0) {
2590
2903
  return {
2591
2904
  skill,
@@ -2614,6 +2927,77 @@ function installRequiredSkill(skill, harness, options = {}) {
2614
2927
  }
2615
2928
 
2616
2929
  // src/cli/operations/types.ts
2930
+ function getInstallCompletionEvidence(completion, options) {
2931
+ const targets = [
2932
+ {
2933
+ kind: "surface",
2934
+ label: "Provider-owned thoth-mem setup",
2935
+ state: completion.provider.success ? "installed" : "drift",
2936
+ observed: completion.provider.status
2937
+ }
2938
+ ];
2939
+ const warnings = [
2940
+ ...completion.provider.diagnostics.map((message) => ({
2941
+ severity: "minor",
2942
+ code: `${options.codePrefix}-provider-diagnostic`,
2943
+ message
2944
+ })),
2945
+ ...completion.provider.manualActions.map((message) => ({
2946
+ severity: "important",
2947
+ code: `${options.codePrefix}-provider-manual-action`,
2948
+ message
2949
+ }))
2950
+ ];
2951
+ if (completion.provider.receipt) {
2952
+ targets.push({
2953
+ kind: "file",
2954
+ path: completion.provider.receipt,
2955
+ label: "thoth-mem setup receipt",
2956
+ observed: "provider-owned receipt"
2957
+ });
2958
+ }
2959
+ if (completion.success) {
2960
+ targets.push({
2961
+ kind: "file",
2962
+ path: completion.ledger.path,
2963
+ label: "CLI-managed install version",
2964
+ state: "installed",
2965
+ observed: `recorded ${options.version}`
2966
+ });
2967
+ } else {
2968
+ warnings.push({
2969
+ severity: "critical",
2970
+ code: `${options.codePrefix}-install-finalization-failed`,
2971
+ message: completion.error ?? options.fallbackError
2972
+ });
2973
+ }
2974
+ return { targets, warnings };
2975
+ }
2976
+ function getCliManagedInstallVersionTarget(harness, context2 = {}) {
2977
+ const executing = resolveExecutingPackageVersion();
2978
+ const ledger = readInstallLedger({
2979
+ env: context2.env,
2980
+ homeDir: context2.homeDir
2981
+ });
2982
+ const recordedVersion = ledger.status === "valid" ? ledger.ledger.harnesses[harness]?.version : void 0;
2983
+ let state;
2984
+ if (!executing.ok || ledger.status === "invalid") {
2985
+ state = "unknown";
2986
+ } else if (!recordedVersion) {
2987
+ state = "missing";
2988
+ } else {
2989
+ state = recordedVersion === executing.version ? "installed" : "outdated";
2990
+ }
2991
+ return {
2992
+ kind: "file",
2993
+ path: ledger.path,
2994
+ label: "CLI-managed install version",
2995
+ state,
2996
+ expected: executing.ok ? `executing ${executing.version}` : "executing unknown",
2997
+ observed: ledger.status === "invalid" ? "recorded unknown (invalid ledger)" : recordedVersion ? `recorded ${recordedVersion}` : "recorded missing",
2998
+ description: "Authoritative last complete CLI-managed install version; native marketplace state is independent."
2999
+ };
3000
+ }
2617
3001
  var UNSUPPORTED_PROVIDER_CAPABILITY = {
2618
3002
  state: "unsupported",
2619
3003
  source: "none",
@@ -2872,10 +3256,16 @@ function statusFromSetupPlan(plan, context2) {
2872
3256
  }
2873
3257
  function getClaudeCodeStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
2874
3258
  const providerCapability = classifyProviderCapabilityEvidence(evidence);
3259
+ const installVersionTarget = getCliManagedInstallVersionTarget("claude", {
3260
+ env: context2.env,
3261
+ homeDir: context2.homeDir
3262
+ });
2875
3263
  try {
2876
3264
  const plan = buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true));
3265
+ const status = statusFromSetupPlan(plan, context2);
2877
3266
  return {
2878
- ...statusFromSetupPlan(plan, context2),
3267
+ ...status,
3268
+ targets: [...status.targets, installVersionTarget],
2879
3269
  providerCapability
2880
3270
  };
2881
3271
  } catch (error) {
@@ -2885,7 +3275,7 @@ function getClaudeCodeStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
2885
3275
  displayName: CLAUDE_CODE_DISPLAY_NAME,
2886
3276
  state: "unknown",
2887
3277
  summary: `Claude Code setup plan could not be built: ${message}`,
2888
- targets: [],
3278
+ targets: [installVersionTarget],
2889
3279
  diagnostics: [
2890
3280
  {
2891
3281
  severity: "critical",
@@ -2907,11 +3297,50 @@ function planItemFromSetup(item) {
2907
3297
  backup: { required: false, strategy: "external" }
2908
3298
  };
2909
3299
  }
2910
- function planFromSetup(id, action, title, summary, setupPlan, context2) {
3300
+ function claudeLedgerOptions(context2) {
3301
+ return context2.installLedgerOptions ?? {
3302
+ env: context2.env,
3303
+ homeDir: context2.homeDir
3304
+ };
3305
+ }
3306
+ function planFromSetup(id, action, title, summary, setupPlan, context2, version) {
2911
3307
  const status = statusFromSetupPlan(setupPlan, context2);
2912
3308
  const missingSkills = status.targets.some(
2913
3309
  (target) => target.kind === "skill" && target.state === "missing"
2914
3310
  );
3311
+ const providerCommand = getThothMemSetupCommand("claude", true);
3312
+ const nativeSetupItems = setupPlan.items.length > 0 ? setupPlan.items.map(planItemFromSetup) : [
3313
+ {
3314
+ title: "Verify Claude Code native marketplace and plugin state",
3315
+ target: {
3316
+ kind: "package",
3317
+ path: setupPlan.pluginRoot,
3318
+ label: "Claude Code native thoth-agents plugin"
3319
+ },
3320
+ preview: "Inspect and verify the canonical marketplace and enabled native plugin.",
3321
+ backup: { required: false, strategy: "external" }
3322
+ }
3323
+ ];
3324
+ const completionItems = version ? [
3325
+ {
3326
+ title: "Plan provider-owned thoth-mem setup for Claude Code",
3327
+ target: {
3328
+ kind: "surface",
3329
+ label: "Provider-owned thoth-mem setup"
3330
+ },
3331
+ preview: `${providerCommand.command} ${providerCommand.args.join(" ")}`
3332
+ },
3333
+ {
3334
+ title: "Record completed Claude Code CLI install",
3335
+ target: {
3336
+ kind: "file",
3337
+ path: getInstallLedgerPath(claudeLedgerOptions(context2)),
3338
+ label: "CLI-managed install version",
3339
+ expected: `recorded ${version}`
3340
+ },
3341
+ preview: JSON.stringify({ harness: "claude", version })
3342
+ }
3343
+ ] : [];
2915
3344
  const plan = {
2916
3345
  id,
2917
3346
  harness: "claude",
@@ -2935,8 +3364,9 @@ function planFromSetup(id, action, title, summary, setupPlan, context2) {
2935
3364
  description: "Claude Code owns its marketplace snapshots and plugin cache."
2936
3365
  },
2937
3366
  items: [
2938
- ...setupPlan.items.map(planItemFromSetup),
2939
- claudeCodeRequiredSkillPlanItem()
3367
+ ...nativeSetupItems,
3368
+ claudeCodeRequiredSkillPlanItem(),
3369
+ ...completionItems
2940
3370
  ],
2941
3371
  warnings: status.diagnostics,
2942
3372
  disclaimers: [
@@ -2944,36 +3374,51 @@ function planFromSetup(id, action, title, summary, setupPlan, context2) {
2944
3374
  ...setupPlan.disclaimers.map((message) => ({ message }))
2945
3375
  ]
2946
3376
  };
2947
- claudeCodePlanSources.set(plan, { setupPlan, context: context2 });
3377
+ claudeCodePlanSources.set(plan, {
3378
+ setupPlan,
3379
+ context: context2,
3380
+ ...version ? { version } : {}
3381
+ });
2948
3382
  return plan;
2949
3383
  }
2950
- function buildClaudeCodeInstallPlan(context2 = { cwd: process.cwd() }) {
2951
- return planFromSetup(
2952
- "claude-code-install-preview",
2953
- "install",
2954
- "Install Claude Code native plugin",
2955
- "Preview native marketplace registration, plugin installation, and required skills.",
2956
- buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true)),
2957
- context2
3384
+ function buildCompleteClaudeCodePlan(action, context2) {
3385
+ const resolveVersion = context2.resolveExecutingPackageVersion ?? resolveExecutingPackageVersion;
3386
+ const packageVersion = resolveVersion();
3387
+ const buildSetup = context2.buildClaudeCodeSetupPlan ?? buildClaudeCodeSetupPlan;
3388
+ const plan = planFromSetup(
3389
+ `claude-code-${action}-preview`,
3390
+ action,
3391
+ `${action === "install" ? "Install" : "Update"} complete Claude Code setup`,
3392
+ "Preview native marketplace/plugin refresh, required skills, provider setup, and CLI ledger commit.",
3393
+ buildSetup(claudeCodeConfig(context2, true, action === "update")),
3394
+ context2,
3395
+ packageVersion.ok ? packageVersion.version : void 0
2958
3396
  );
3397
+ if (!packageVersion.ok) {
3398
+ plan.canApply = false;
3399
+ plan.warnings.push(
3400
+ warning(
3401
+ packageVersion.error.message,
3402
+ "claude-code-package-version-unresolved"
3403
+ )
3404
+ );
3405
+ }
3406
+ return plan;
3407
+ }
3408
+ function buildClaudeCodeInstallPlan(context2 = { cwd: process.cwd() }) {
3409
+ return buildCompleteClaudeCodePlan("install", context2);
2959
3410
  }
2960
3411
  function buildClaudeCodeUpdatePlan(context2 = { cwd: process.cwd() }) {
2961
- return planFromSetup(
2962
- "claude-code-update-preview",
2963
- "update",
2964
- "Reconcile Claude Code native plugin",
2965
- "Preview native marketplace, enabled-plugin, and required-skill reconciliation.",
2966
- buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true, true)),
2967
- context2
2968
- );
3412
+ return buildCompleteClaudeCodePlan("update", context2);
2969
3413
  }
2970
3414
  function buildClaudeCodeSyncPlan(context2 = { cwd: process.cwd() }) {
3415
+ const buildSetup = context2.buildClaudeCodeSetupPlan ?? buildClaudeCodeSetupPlan;
2971
3416
  return planFromSetup(
2972
3417
  "claude-code-sync-preview",
2973
3418
  "sync",
2974
3419
  "Sync Claude Code native plugin",
2975
3420
  "Preview native marketplace, enabled-plugin, and required-skill reconciliation.",
2976
- buildClaudeCodeSetupPlan(claudeCodeConfig(context2, true)),
3421
+ buildSetup(claudeCodeConfig(context2, true)),
2977
3422
  context2
2978
3423
  );
2979
3424
  }
@@ -3054,12 +3499,24 @@ function applyClaudeCodePlan(plan) {
3054
3499
  "Claude Code setup plan was not produced by a Claude Code operation plan builder in this process."
3055
3500
  );
3056
3501
  }
3057
- const result = applyClaudeCodeSetup({ ...source.setupPlan, dryRun: false });
3502
+ if (source.version) {
3503
+ const resolveVersion = source.context.resolveExecutingPackageVersion ?? resolveExecutingPackageVersion;
3504
+ const currentVersion = resolveVersion();
3505
+ if (!currentVersion.ok || currentVersion.version !== source.version) {
3506
+ return rejectPlan(
3507
+ plan,
3508
+ currentVersion.ok ? `Approved package version changed from ${source.version} to ${currentVersion.version} before apply.` : currentVersion.error.message
3509
+ );
3510
+ }
3511
+ }
3512
+ const applySetup = source.context.applyClaudeCodeSetup ?? applyClaudeCodeSetup;
3513
+ const result = applySetup({ ...source.setupPlan, dryRun: false });
3058
3514
  const requiredSkillWarnings = [];
3059
3515
  const requiredSkillTargets = [];
3060
3516
  if (result.success) {
3517
+ const installSkill = source.context.installRequiredSkill ?? installRequiredSkill;
3061
3518
  for (const skill of REQUIRED_SKILLS) {
3062
- const installed = installRequiredSkill(skill, "claude", {
3519
+ const installed = installSkill(skill, "claude", {
3063
3520
  homeDir: source.context.homeDir
3064
3521
  });
3065
3522
  const success2 = installed.status !== "failed";
@@ -3079,37 +3536,63 @@ function applyClaudeCodePlan(plan) {
3079
3536
  }
3080
3537
  }
3081
3538
  }
3082
- const success = result.success && requiredSkillWarnings.length === 0;
3539
+ let success = result.success && requiredSkillWarnings.length === 0;
3083
3540
  let summary = result.error ?? `Failed to apply Claude Code ${plan.action} plan.`;
3084
3541
  if (success) {
3085
3542
  summary = `Applied Claude Code native ${plan.action} plan.`;
3086
3543
  } else if (requiredSkillWarnings.length > 0) {
3087
3544
  summary = "Claude Code plugin was installed, but required skills failed to install.";
3088
3545
  }
3546
+ const changedTargets = [
3547
+ ...result.changed.map((path) => ({
3548
+ kind: "package",
3549
+ path,
3550
+ label: path.includes("/marketplaces/") ? "Claude Code marketplace" : "Claude Code plugin",
3551
+ state: "installed"
3552
+ })),
3553
+ ...requiredSkillTargets
3554
+ ];
3555
+ const warnings = [
3556
+ ...requiredSkillWarnings,
3557
+ ...result.success ? [] : [
3558
+ {
3559
+ severity: "critical",
3560
+ message: result.error ?? "apply failed."
3561
+ }
3562
+ ]
3563
+ ];
3564
+ if (success && source.version && plan.action !== "sync") {
3565
+ const finalize = source.context.finalizeHarnessInstall ?? finalizeHarnessInstall;
3566
+ const completion = finalize({
3567
+ harness: "claude",
3568
+ version: source.version,
3569
+ dryRun: false,
3570
+ cwd: source.context.cwd,
3571
+ runThothMemSetup: source.context.runThothMemSetup,
3572
+ ledgerOptions: claudeLedgerOptions(source.context)
3573
+ });
3574
+ const completionEvidence = getInstallCompletionEvidence(completion, {
3575
+ codePrefix: "claude-code",
3576
+ version: source.version,
3577
+ fallbackError: "Claude Code install finalization failed."
3578
+ });
3579
+ warnings.push(...completionEvidence.warnings);
3580
+ changedTargets.push(...completionEvidence.targets);
3581
+ if (completion.success) {
3582
+ summary = `Applied complete Claude Code ${plan.action} plan.`;
3583
+ } else {
3584
+ success = false;
3585
+ summary = completion.error ?? "Claude Code install finalization failed.";
3586
+ }
3587
+ }
3089
3588
  return {
3090
3589
  harness: "claude",
3091
3590
  action: plan.action,
3092
3591
  applied: success,
3093
3592
  summary,
3094
- changedTargets: [
3095
- ...result.changed.map((path) => ({
3096
- kind: "package",
3097
- path,
3098
- label: path.includes("/marketplaces/") ? "Claude Code marketplace" : "Claude Code plugin",
3099
- state: "installed"
3100
- })),
3101
- ...requiredSkillTargets
3102
- ],
3593
+ changedTargets,
3103
3594
  backups: [],
3104
- warnings: [
3105
- ...requiredSkillWarnings,
3106
- ...result.success ? [] : [
3107
- {
3108
- severity: "critical",
3109
- message: result.error ?? "apply failed."
3110
- }
3111
- ]
3112
- ],
3595
+ warnings,
3113
3596
  disclaimers: claudeCodeDisclaimers()
3114
3597
  };
3115
3598
  }
@@ -3121,8 +3604,245 @@ function defaultClaudeCodeModelRoles() {
3121
3604
  }
3122
3605
 
3123
3606
  // src/cli/operations/codex.ts
3124
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
3125
- import { basename as basename2 } from "path";
3607
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
3608
+ import { basename as basename2 } from "path";
3609
+
3610
+ // src/cli/codex-plugin-install.ts
3611
+ import { spawnSync as spawnSync4 } from "child_process";
3612
+ var MARKETPLACE_NAME2 = "thoth-agents";
3613
+ var MARKETPLACE_SOURCE2 = "EremesNG/thoth-agents";
3614
+ var PLUGIN_ID2 = "thoth-agents@thoth-agents";
3615
+ var MARKETPLACE_TARGET2 = "codex://marketplaces/thoth-agents";
3616
+ var PLUGIN_TARGET2 = "codex://plugins/thoth-agents@thoth-agents";
3617
+ function getCodexCommand(args, options = {}) {
3618
+ if ((options.platform ?? process.platform) !== "win32") {
3619
+ return { command: "codex", args: [...args] };
3620
+ }
3621
+ return {
3622
+ command: options.commandShell ?? process.env.ComSpec ?? "cmd.exe",
3623
+ args: ["/d", "/s", "/c", "codex", ...args]
3624
+ };
3625
+ }
3626
+ function defaultCommandExecutor3(command, args, options) {
3627
+ const invocation = command === "codex" ? getCodexCommand(args) : { command, args: [...args] };
3628
+ const result = spawnSync4(invocation.command, invocation.args, {
3629
+ cwd: options.cwd,
3630
+ encoding: "utf8",
3631
+ shell: false,
3632
+ windowsHide: true
3633
+ });
3634
+ return {
3635
+ exitCode: result.status ?? (result.error ? 1 : 0),
3636
+ stdout: result.stdout ?? "",
3637
+ stderr: result.stderr ?? result.error?.message ?? ""
3638
+ };
3639
+ }
3640
+ function isRecord5(value) {
3641
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3642
+ }
3643
+ function parseRecordArray(value) {
3644
+ return Array.isArray(value) && value.every(isRecord5) ? value : null;
3645
+ }
3646
+ function parseManagerRecord(content) {
3647
+ try {
3648
+ const parsed = JSON.parse(content);
3649
+ return isRecord5(parsed) ? parsed : null;
3650
+ } catch {
3651
+ return null;
3652
+ }
3653
+ }
3654
+ function normalizeMarketplaceSource(value) {
3655
+ return value.trim().replace(/^git@github\.com:/i, "").replace(/^(?:https?|ssh):\/\/(?:git@)?github\.com\//i, "").replace(/^github\.com\//i, "").replace(/\/?\.git\/?$/i, "").replace(/\/$/, "").toLowerCase();
3656
+ }
3657
+ function marketplaceSources(entry) {
3658
+ const sources = [entry.repo, entry.source];
3659
+ if (isRecord5(entry.marketplaceSource)) {
3660
+ sources.push(entry.marketplaceSource.repo, entry.marketplaceSource.source);
3661
+ }
3662
+ return sources.filter(
3663
+ (source) => typeof source === "string"
3664
+ );
3665
+ }
3666
+ function isCanonicalMarketplace2(entry) {
3667
+ if (entry.name !== MARKETPLACE_NAME2) return false;
3668
+ const canonical = MARKETPLACE_SOURCE2.toLowerCase();
3669
+ return marketplaceSources(entry).some(
3670
+ (source) => normalizeMarketplaceSource(source) === canonical
3671
+ );
3672
+ }
3673
+ function inspectCodexManager(executor, cwd) {
3674
+ const marketplaceResult = executor(
3675
+ "codex",
3676
+ ["plugin", "marketplace", "list", "--json"],
3677
+ { cwd }
3678
+ );
3679
+ const pluginResult = executor(
3680
+ "codex",
3681
+ ["plugin", "list", "--available", "--json"],
3682
+ { cwd }
3683
+ );
3684
+ const diagnostics = [];
3685
+ if (marketplaceResult.exitCode !== 0 || pluginResult.exitCode !== 0) {
3686
+ diagnostics.push(
3687
+ "Codex native plugin state could not be inspected; no manager mutation will be attempted."
3688
+ );
3689
+ return {
3690
+ ready: false,
3691
+ marketplace: "unknown",
3692
+ plugin: "unknown",
3693
+ diagnostics
3694
+ };
3695
+ }
3696
+ const marketplaceRecord = parseManagerRecord(marketplaceResult.stdout);
3697
+ const pluginRecord = parseManagerRecord(pluginResult.stdout);
3698
+ const marketplaces = marketplaceRecord ? parseRecordArray(marketplaceRecord.marketplaces) : null;
3699
+ const installedPlugins = pluginRecord ? parseRecordArray(pluginRecord.installed) : null;
3700
+ if (!marketplaces || !installedPlugins) {
3701
+ diagnostics.push(
3702
+ "Codex returned unparseable plugin manager JSON; no manager mutation will be attempted."
3703
+ );
3704
+ return {
3705
+ ready: false,
3706
+ marketplace: "unknown",
3707
+ plugin: "unknown",
3708
+ diagnostics
3709
+ };
3710
+ }
3711
+ const namedMarketplaces = marketplaces.filter(
3712
+ (entry) => entry.name === MARKETPLACE_NAME2
3713
+ );
3714
+ let marketplace = "absent";
3715
+ if (namedMarketplaces.length > 0) {
3716
+ marketplace = namedMarketplaces.every(isCanonicalMarketplace2) ? "installed" : "conflict";
3717
+ }
3718
+ if (marketplace === "conflict") {
3719
+ diagnostics.push(
3720
+ "A Codex marketplace named thoth-agents is registered from a different source; resolve it through Codex before retrying."
3721
+ );
3722
+ }
3723
+ const matchingPlugins = installedPlugins.filter(
3724
+ (entry) => entry.pluginId === PLUGIN_ID2
3725
+ );
3726
+ let plugin = "absent";
3727
+ if (matchingPlugins.some((entry) => entry.enabled === true)) {
3728
+ plugin = "installed";
3729
+ } else if (matchingPlugins.length > 0) {
3730
+ plugin = "disabled";
3731
+ }
3732
+ return {
3733
+ ready: marketplace !== "conflict",
3734
+ marketplace,
3735
+ plugin,
3736
+ diagnostics
3737
+ };
3738
+ }
3739
+ function commandItem2(action, projectRoot) {
3740
+ if (action === "register-marketplace") {
3741
+ return {
3742
+ action,
3743
+ targetPath: MARKETPLACE_TARGET2,
3744
+ description: "Register the package-owned thoth-agents Codex marketplace.",
3745
+ command: {
3746
+ executable: "codex",
3747
+ args: ["plugin", "marketplace", "add", MARKETPLACE_SOURCE2, "--json"],
3748
+ cwd: projectRoot
3749
+ }
3750
+ };
3751
+ }
3752
+ return {
3753
+ action,
3754
+ targetPath: PLUGIN_TARGET2,
3755
+ description: "Install and enable the thoth-agents Codex plugin from its native marketplace.",
3756
+ command: {
3757
+ executable: "codex",
3758
+ args: ["plugin", "add", PLUGIN_ID2, "--json"],
3759
+ cwd: projectRoot
3760
+ }
3761
+ };
3762
+ }
3763
+ function buildCodexPluginSetupPlan(config) {
3764
+ const commandExecutor = config.commandExecutor ?? defaultCommandExecutor3;
3765
+ const inspection = inspectCodexManager(commandExecutor, config.projectRoot);
3766
+ const items = [];
3767
+ if (inspection.ready) {
3768
+ if (inspection.marketplace === "absent") {
3769
+ items.push(commandItem2("register-marketplace", config.projectRoot));
3770
+ }
3771
+ if (inspection.plugin === "absent" || inspection.plugin === "disabled") {
3772
+ items.push(commandItem2("install-plugin", config.projectRoot));
3773
+ }
3774
+ }
3775
+ return {
3776
+ dryRun: config.dryRun === true,
3777
+ items,
3778
+ ready: inspection.ready,
3779
+ projectRoot: config.projectRoot,
3780
+ commandExecutor,
3781
+ diagnostics: [
3782
+ ...inspection.diagnostics,
3783
+ "Codex installs thoth-agents from EremesNG/thoth-agents through its native plugin manager and owns the cached plugin files.",
3784
+ "Restart Codex after installation; use /plugins to inspect marketplace and plugin state."
3785
+ ]
3786
+ };
3787
+ }
3788
+ function uniqueMessages3(messages) {
3789
+ return [...new Set(messages)];
3790
+ }
3791
+ function boundedCommandFailure2(item, result) {
3792
+ const detail = result.stderr.trim().replace(/\s+/g, " ").slice(0, 240);
3793
+ return `${item.description} Codex exited with code ${result.exitCode}${detail ? `: ${detail}` : "."}`;
3794
+ }
3795
+ function applyCodexPluginSetup(plan) {
3796
+ const changed = [];
3797
+ const diagnostics = uniqueMessages3(plan.diagnostics);
3798
+ if (!plan.ready) {
3799
+ return {
3800
+ success: false,
3801
+ changed,
3802
+ diagnostics,
3803
+ error: "Codex native plugin manager state is not safe to mutate."
3804
+ };
3805
+ }
3806
+ if (plan.dryRun) return { success: true, changed, diagnostics };
3807
+ for (const item of plan.items) {
3808
+ const result = plan.commandExecutor(
3809
+ item.command.executable,
3810
+ item.command.args,
3811
+ { cwd: item.command.cwd }
3812
+ );
3813
+ if (result.exitCode !== 0) {
3814
+ return {
3815
+ success: false,
3816
+ changed,
3817
+ diagnostics,
3818
+ error: boundedCommandFailure2(item, result)
3819
+ };
3820
+ }
3821
+ changed.push(item.targetPath);
3822
+ }
3823
+ const inspection = inspectCodexManager(
3824
+ plan.commandExecutor,
3825
+ plan.projectRoot
3826
+ );
3827
+ diagnostics.push(...inspection.diagnostics);
3828
+ if (inspection.marketplace !== "installed" || inspection.plugin !== "installed") {
3829
+ return {
3830
+ success: false,
3831
+ changed,
3832
+ diagnostics: uniqueMessages3(diagnostics),
3833
+ error: "Codex did not verify the expected marketplace and enabled plugin after mutation."
3834
+ };
3835
+ }
3836
+ return { success: true, changed, diagnostics: uniqueMessages3(diagnostics) };
3837
+ }
3838
+ function formatCodexPluginSetupPlan(plan) {
3839
+ const lines = plan.items.map(
3840
+ (item) => `- ${item.action}: ${item.command.executable} ${item.command.args.join(" ")}`
3841
+ );
3842
+ return ["Codex plugin setup plan:", ...lines].join("\n");
3843
+ }
3844
+
3845
+ // src/cli/operations/codex.ts
3126
3846
  var CODEX_DISPLAY_NAME = "Codex";
3127
3847
  var codexPlanSources = /* @__PURE__ */ new WeakMap();
3128
3848
  var codexModelSources = /* @__PURE__ */ new WeakMap();
@@ -3245,7 +3965,7 @@ function backupForItem(item) {
3245
3965
  function rootBlockState(item) {
3246
3966
  if (!existsSync8(item.targetPath))
3247
3967
  return { state: "missing", observed: "absent" };
3248
- const content = readFileSync5(item.targetPath, "utf8");
3968
+ const content = readFileSync6(item.targetPath, "utf8");
3249
3969
  if (item.content && content.includes(item.content)) {
3250
3970
  return { state: "installed", observed: "managed root block present" };
3251
3971
  }
@@ -3258,11 +3978,11 @@ function managedModelState(item) {
3258
3978
  if (!existsSync8(item.targetPath))
3259
3979
  return { state: "missing", observed: "absent" };
3260
3980
  try {
3261
- const parsed = JSON.parse(readFileSync5(item.targetPath, "utf8"));
3981
+ const parsed = JSON.parse(readFileSync6(item.targetPath, "utf8"));
3262
3982
  if (parsed.version !== MANAGED_MODEL_STATE_VERSION || !parsed.models || typeof parsed.models !== "object" || Array.isArray(parsed.models)) {
3263
3983
  return { state: "unknown", observed: "invalid managed model state" };
3264
3984
  }
3265
- return item.content === readFileSync5(item.targetPath, "utf8") ? { state: "installed", observed: "managed model state current" } : { state: "drift", observed: "managed model state differs" };
3985
+ return item.content === readFileSync6(item.targetPath, "utf8") ? { state: "installed", observed: "managed model state current" } : { state: "drift", observed: "managed model state differs" };
3266
3986
  } catch {
3267
3987
  return { state: "unknown", observed: "unparseable managed model state" };
3268
3988
  }
@@ -3270,7 +3990,7 @@ function managedModelState(item) {
3270
3990
  function userConfigState(item) {
3271
3991
  if (!existsSync8(item.targetPath))
3272
3992
  return { state: "missing", observed: "absent" };
3273
- const content = readFileSync5(item.targetPath, "utf8");
3993
+ const content = readFileSync6(item.targetPath, "utf8");
3274
3994
  if (/^\s*default_mode_request_user_input\s*=\s*true\b/m.test(content)) {
3275
3995
  return {
3276
3996
  state: "installed",
@@ -3285,7 +4005,7 @@ function userConfigState(item) {
3285
4005
  function contentState(item) {
3286
4006
  if (!existsSync8(item.targetPath))
3287
4007
  return { state: "missing", observed: "absent" };
3288
- const observed = readFileSync5(item.targetPath, "utf8");
4008
+ const observed = readFileSync6(item.targetPath, "utf8");
3289
4009
  if (item.content === observed)
3290
4010
  return { state: "installed", observed: "current" };
3291
4011
  if (item.role) {
@@ -3376,6 +4096,10 @@ function codexRequiredSkillPlanItem() {
3376
4096
  }
3377
4097
  function getCodexStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
3378
4098
  const providerCapability = classifyProviderCapabilityEvidence(evidence);
4099
+ const installVersionTarget = getCliManagedInstallVersionTarget("codex", {
4100
+ env: context2.env,
4101
+ homeDir: context2.homeDir
4102
+ });
3379
4103
  let plan;
3380
4104
  try {
3381
4105
  plan = buildCodexSetupPlan(codexConfig(context2, true));
@@ -3386,7 +4110,7 @@ function getCodexStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
3386
4110
  displayName: CODEX_DISPLAY_NAME,
3387
4111
  state: "unknown",
3388
4112
  summary: `Codex setup plan could not be built: ${message}`,
3389
- targets: [],
4113
+ targets: [installVersionTarget],
3390
4114
  diagnostics: [
3391
4115
  {
3392
4116
  severity: "critical",
@@ -3420,7 +4144,8 @@ function getCodexStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
3420
4144
  ...classified.map(
3421
4145
  ({ item, state: state2, observed }) => targetForItem2(item, state2, observed)
3422
4146
  ),
3423
- ...requiredSkills.targets
4147
+ ...requiredSkills.targets,
4148
+ installVersionTarget
3424
4149
  ],
3425
4150
  diagnostics,
3426
4151
  actions: codexActions,
@@ -3439,9 +4164,62 @@ function planItemFromSetup2(item) {
3439
4164
  backup: backupForItem(item)
3440
4165
  };
3441
4166
  }
3442
- function planFromSetup2(id, action, title, summary, setupPlan, context2) {
4167
+ function planItemFromPluginSetup(item) {
4168
+ return {
4169
+ title: item.description,
4170
+ target: {
4171
+ kind: "package",
4172
+ path: item.targetPath,
4173
+ label: "Codex native plugin manager target"
4174
+ },
4175
+ preview: `${item.command.executable} ${item.command.args.join(" ")}`,
4176
+ backup: { required: false, strategy: "external" }
4177
+ };
4178
+ }
4179
+ function codexLedgerOptions(context2) {
4180
+ return context2.installLedgerOptions ?? {
4181
+ env: context2.env,
4182
+ homeDir: context2.homeDir
4183
+ };
4184
+ }
4185
+ function planFromSetup2(id, action, title, summary, setupPlan, context2, completion) {
3443
4186
  const status = getCodexStatus(context2);
3444
- const canApply = status.state === "installed" || status.state === "missing" || status.state === "outdated";
4187
+ const canApply = (status.state === "installed" || status.state === "missing" || status.state === "outdated") && (completion?.pluginPlan.ready ?? true);
4188
+ const providerCommand = getThothMemSetupCommand("codex", true);
4189
+ const nativePluginItems = completion ? completion.pluginPlan.items.length > 0 ? completion.pluginPlan.items.map(planItemFromPluginSetup) : [
4190
+ {
4191
+ title: "Verify Codex native plugin manager state",
4192
+ target: {
4193
+ kind: "package",
4194
+ label: "Codex native thoth-agents plugin"
4195
+ },
4196
+ preview: "Inspect and post-verify the canonical thoth-agents marketplace and enabled plugin.",
4197
+ backup: { required: false, strategy: "external" }
4198
+ }
4199
+ ] : [];
4200
+ const completionItems = completion ? [
4201
+ {
4202
+ title: "Plan provider-owned thoth-mem setup for Codex",
4203
+ target: {
4204
+ kind: "surface",
4205
+ label: "Provider-owned thoth-mem setup"
4206
+ },
4207
+ preview: `${providerCommand.command} ${providerCommand.args.join(" ")}`
4208
+ },
4209
+ {
4210
+ title: "Record completed Codex CLI install",
4211
+ target: {
4212
+ kind: "file",
4213
+ path: getInstallLedgerPath(codexLedgerOptions(context2)),
4214
+ label: "CLI-managed install version",
4215
+ expected: `recorded ${completion.version}`
4216
+ },
4217
+ preview: JSON.stringify({
4218
+ harness: "codex",
4219
+ version: completion.version
4220
+ })
4221
+ }
4222
+ ] : [];
3445
4223
  const plan = {
3446
4224
  id,
3447
4225
  harness: "codex",
@@ -3458,8 +4236,10 @@ function planFromSetup2(id, action, title, summary, setupPlan, context2) {
3458
4236
  description: "Codex setup apply uses the existing installer backup behavior for files that already exist."
3459
4237
  },
3460
4238
  items: [
4239
+ ...nativePluginItems,
3461
4240
  ...setupPlan.items.map(planItemFromSetup2),
3462
- codexRequiredSkillPlanItem()
4241
+ codexRequiredSkillPlanItem(),
4242
+ ...completionItems
3463
4243
  ],
3464
4244
  warnings: [
3465
4245
  ...status.diagnostics,
@@ -3475,22 +4255,59 @@ function planFromSetup2(id, action, title, summary, setupPlan, context2) {
3475
4255
  ...setupPlan.disclaimers.map((message) => ({ message }))
3476
4256
  ]
3477
4257
  };
3478
- codexPlanSources.set(plan, { setupPlan, context: context2 });
4258
+ codexPlanSources.set(plan, {
4259
+ setupPlan,
4260
+ context: context2,
4261
+ ...completion ? {
4262
+ pluginPlan: completion.pluginPlan,
4263
+ version: completion.version
4264
+ } : {}
4265
+ });
3479
4266
  return plan;
3480
4267
  }
3481
- function buildCodexUpdatePlan(context2 = { cwd: process.cwd() }) {
3482
- const setupPlan = buildCodexSetupPlan(codexConfig(context2, true));
3483
- return planFromSetup2(
3484
- "codex-update-preview",
3485
- "update",
3486
- "Update Codex managed setup",
3487
- "Preview Codex managed setup refresh using buildCodexSetupPlan().",
4268
+ function buildCompleteCodexPlan(action, context2) {
4269
+ const resolveVersion = context2.resolveExecutingPackageVersion ?? resolveExecutingPackageVersion;
4270
+ const packageVersion = resolveVersion();
4271
+ const buildPlugin = context2.buildCodexPluginSetupPlan ?? buildCodexPluginSetupPlan;
4272
+ const pluginPlan = buildPlugin({
4273
+ dryRun: true,
4274
+ projectRoot: context2.cwd,
4275
+ commandExecutor: context2.codexPluginCommandExecutor
4276
+ });
4277
+ const buildSetup = context2.buildCodexSetupPlan ?? buildCodexSetupPlan;
4278
+ const setupPlan = buildSetup(codexConfig(context2, true));
4279
+ const plan = planFromSetup2(
4280
+ `codex-${action}-preview`,
4281
+ action,
4282
+ `${action === "install" ? "Install" : "Update"} complete Codex setup`,
4283
+ `Preview Codex native plugin, global agent pack, required skills, provider setup, and CLI ledger ${action}.`,
3488
4284
  setupPlan,
3489
- context2
4285
+ context2,
4286
+ packageVersion.ok ? { pluginPlan, version: packageVersion.version } : void 0
3490
4287
  );
4288
+ if (!packageVersion.ok) {
4289
+ plan.canApply = false;
4290
+ plan.warnings.push(
4291
+ warning2(packageVersion.error.message, "codex-package-version-unresolved")
4292
+ );
4293
+ }
4294
+ if (!pluginPlan.ready) {
4295
+ plan.canApply = false;
4296
+ plan.warnings.push(
4297
+ warning2(
4298
+ "Codex native plugin manager state is not safe to mutate.",
4299
+ "codex-native-plugin-not-ready"
4300
+ )
4301
+ );
4302
+ }
4303
+ return plan;
4304
+ }
4305
+ function buildCodexUpdatePlan(context2 = { cwd: process.cwd() }) {
4306
+ return buildCompleteCodexPlan("update", context2);
3491
4307
  }
3492
4308
  function buildCodexSyncPlan(context2 = { cwd: process.cwd() }) {
3493
- const setupPlan = buildCodexSetupPlan(codexConfig(context2, true));
4309
+ const buildSetup = context2.buildCodexSetupPlan ?? buildCodexSetupPlan;
4310
+ const setupPlan = buildSetup(codexConfig(context2, true));
3494
4311
  return planFromSetup2(
3495
4312
  "codex-sync-preview",
3496
4313
  "sync",
@@ -3501,15 +4318,7 @@ function buildCodexSyncPlan(context2 = { cwd: process.cwd() }) {
3501
4318
  );
3502
4319
  }
3503
4320
  function buildCodexInstallPlan(context2 = { cwd: process.cwd() }) {
3504
- const setupPlan = buildCodexSetupPlan(codexConfig(context2, true));
3505
- return planFromSetup2(
3506
- "codex-install-preview",
3507
- "install",
3508
- "Install Codex managed setup",
3509
- "Preview Codex managed agent-pack setup using buildCodexSetupPlan().",
3510
- setupPlan,
3511
- context2
3512
- );
4321
+ return buildCompleteCodexPlan("install", context2);
3513
4322
  }
3514
4323
  function normalizeCodexModel(input) {
3515
4324
  return normalizeCodexRuntimeModel(input.model);
@@ -3721,12 +4530,66 @@ function applyCodexPlan(plan) {
3721
4530
  "Codex setup plan was not produced by a Codex operation plan builder in this process."
3722
4531
  );
3723
4532
  }
3724
- const result = applyCodexSetup({ ...source.setupPlan, dryRun: false });
4533
+ if (source.version) {
4534
+ const resolveVersion = source.context.resolveExecutingPackageVersion ?? resolveExecutingPackageVersion;
4535
+ const currentVersion = resolveVersion();
4536
+ if (!currentVersion.ok || currentVersion.version !== source.version) {
4537
+ return rejectPlan2(
4538
+ plan,
4539
+ currentVersion.ok ? `Approved package version changed from ${source.version} to ${currentVersion.version} before apply.` : currentVersion.error.message
4540
+ );
4541
+ }
4542
+ }
4543
+ const changedTargets = [];
4544
+ const warnings = [];
4545
+ if (source.pluginPlan) {
4546
+ const applyPlugin = source.context.applyCodexPluginSetup ?? applyCodexPluginSetup;
4547
+ const nativeResult = applyPlugin({
4548
+ ...source.pluginPlan,
4549
+ dryRun: false
4550
+ });
4551
+ warnings.push(
4552
+ ...nativeResult.diagnostics.map((message) => ({
4553
+ severity: "minor",
4554
+ code: "codex-native-plugin-diagnostic",
4555
+ message
4556
+ }))
4557
+ );
4558
+ changedTargets.push(
4559
+ ...nativeResult.changed.map((path) => ({
4560
+ kind: "package",
4561
+ path,
4562
+ label: "Codex native plugin manager target",
4563
+ state: "installed"
4564
+ }))
4565
+ );
4566
+ if (!nativeResult.success) {
4567
+ const message = nativeResult.error ?? "Codex native plugin setup failed.";
4568
+ warnings.push({
4569
+ severity: "critical",
4570
+ code: "codex-native-plugin-failed",
4571
+ message
4572
+ });
4573
+ return {
4574
+ harness: "codex",
4575
+ action: plan.action,
4576
+ applied: false,
4577
+ summary: message,
4578
+ changedTargets,
4579
+ backups: [],
4580
+ warnings,
4581
+ disclaimers: codexDisclaimers()
4582
+ };
4583
+ }
4584
+ }
4585
+ const applySetup = source.context.applyCodexSetup ?? applyCodexSetup;
4586
+ const result = applySetup({ ...source.setupPlan, dryRun: false });
3725
4587
  const requiredSkillWarnings = [];
3726
4588
  const requiredSkillTargets = [];
3727
4589
  if (result.success) {
4590
+ const installSkill = source.context.installRequiredSkill ?? installRequiredSkill;
3728
4591
  for (const skill of REQUIRED_SKILLS) {
3729
- const installed = installRequiredSkill(skill, "codex", {
4592
+ const installed = installSkill(skill, "codex", {
3730
4593
  homeDir: source.context.homeDir
3731
4594
  });
3732
4595
  const success2 = installed.status !== "failed";
@@ -3746,31 +4609,56 @@ function applyCodexPlan(plan) {
3746
4609
  }
3747
4610
  }
3748
4611
  }
3749
- const success = result.success && requiredSkillWarnings.length === 0;
4612
+ let success = result.success && requiredSkillWarnings.length === 0;
4613
+ let summary = success ? `Applied Codex managed ${plan.action} plan.` : requiredSkillWarnings.length > 0 ? `Codex setup was written, but required skills failed to install.` : result.error ?? `Failed to apply Codex ${plan.action} plan.`;
4614
+ warnings.push(...requiredSkillWarnings);
4615
+ if (!result.success) {
4616
+ warnings.push({
4617
+ severity: "critical",
4618
+ message: result.error ?? "Codex setup apply failed."
4619
+ });
4620
+ }
4621
+ changedTargets.push(
4622
+ ...result.changed.map((path) => ({
4623
+ kind: path.endsWith(".json") ? "memory-state" : "generated-artifact",
4624
+ path,
4625
+ label: basename2(path),
4626
+ state: "installed"
4627
+ })),
4628
+ ...requiredSkillTargets
4629
+ );
4630
+ if (success && source.version && plan.action !== "sync") {
4631
+ const finalize = source.context.finalizeHarnessInstall ?? finalizeHarnessInstall;
4632
+ const completion = finalize({
4633
+ harness: "codex",
4634
+ version: source.version,
4635
+ dryRun: false,
4636
+ cwd: source.context.cwd,
4637
+ runThothMemSetup: source.context.runThothMemSetup,
4638
+ ledgerOptions: codexLedgerOptions(source.context)
4639
+ });
4640
+ const completionEvidence = getInstallCompletionEvidence(completion, {
4641
+ codePrefix: "codex",
4642
+ version: source.version,
4643
+ fallbackError: "Codex install finalization failed."
4644
+ });
4645
+ warnings.push(...completionEvidence.warnings);
4646
+ changedTargets.push(...completionEvidence.targets);
4647
+ if (completion.success) {
4648
+ summary = `Applied complete Codex ${plan.action} plan.`;
4649
+ } else {
4650
+ success = false;
4651
+ summary = completion.error ?? "Codex install finalization failed.";
4652
+ }
4653
+ }
3750
4654
  return {
3751
4655
  harness: "codex",
3752
4656
  action: plan.action,
3753
4657
  applied: success,
3754
- summary: success ? `Applied Codex managed ${plan.action} plan.` : requiredSkillWarnings.length > 0 ? `Codex setup was written, but required skills failed to install.` : result.error ?? `Failed to apply Codex ${plan.action} plan.`,
3755
- changedTargets: [
3756
- ...result.changed.map((path) => ({
3757
- kind: path.endsWith(".json") ? "memory-state" : "generated-artifact",
3758
- path,
3759
- label: basename2(path),
3760
- state: "installed"
3761
- })),
3762
- ...requiredSkillTargets
3763
- ],
4658
+ summary,
4659
+ changedTargets,
3764
4660
  backups: result.changed.filter((path) => existsSync8(`${path}.bak`)).map((path) => ({ path: `${path}.bak`, label: "managed backup" })),
3765
- warnings: [
3766
- ...requiredSkillWarnings,
3767
- ...result.success ? [] : [
3768
- {
3769
- severity: "critical",
3770
- message: result.error ?? "Codex setup apply failed."
3771
- }
3772
- ]
3773
- ],
4661
+ warnings,
3774
4662
  disclaimers: codexDisclaimers()
3775
4663
  };
3776
4664
  }
@@ -3814,13 +4702,13 @@ function resolveOpenCodeEffort(input) {
3814
4702
  import {
3815
4703
  cpSync,
3816
4704
  existsSync as existsSync9,
3817
- mkdirSync as mkdirSync3,
4705
+ mkdirSync as mkdirSync4,
3818
4706
  mkdtempSync,
3819
- renameSync as renameSync2,
3820
- rmSync,
4707
+ renameSync as renameSync3,
4708
+ rmSync as rmSync2,
3821
4709
  statSync
3822
4710
  } from "fs";
3823
- import { homedir as homedir3 } from "os";
4711
+ import { homedir as homedir4 } from "os";
3824
4712
  import { dirname as dirname5, join as join4 } from "path";
3825
4713
  import { fileURLToPath as fileURLToPath4 } from "url";
3826
4714
 
@@ -3848,7 +4736,7 @@ function defaultPackageRoot() {
3848
4736
  }
3849
4737
  function getOpenCodeOwnedSkillEntries(options = {}) {
3850
4738
  const packageRoot = options.packageRoot ?? defaultPackageRoot();
3851
- const homeDir = options.homeDir ?? homedir3();
4739
+ const homeDir = options.homeDir ?? homedir4();
3852
4740
  return THOTH_OWNED_SKILL_NAMES.map((name) => ({
3853
4741
  name,
3854
4742
  sourcePath: join4(packageRoot, "skills", name),
@@ -3870,12 +4758,12 @@ function installOwnedSkills(skills) {
3870
4758
  const firstSkill = skills[0];
3871
4759
  if (!firstSkill) return;
3872
4760
  const globalRoot = dirname5(firstSkill.destinationPath);
3873
- mkdirSync3(globalRoot, { recursive: true });
4761
+ mkdirSync4(globalRoot, { recursive: true });
3874
4762
  const transactionRoot = mkdtempSync(join4(globalRoot, ".thoth-agents-owned-"));
3875
4763
  const stagedRoot = join4(transactionRoot, "staged");
3876
4764
  const backupRoot = join4(transactionRoot, "backup");
3877
- mkdirSync3(stagedRoot, { recursive: true });
3878
- mkdirSync3(backupRoot, { recursive: true });
4765
+ mkdirSync4(stagedRoot, { recursive: true });
4766
+ mkdirSync4(backupRoot, { recursive: true });
3879
4767
  try {
3880
4768
  for (const skill of skills) {
3881
4769
  cpSync(skill.sourcePath, join4(stagedRoot, skill.name), {
@@ -3888,25 +4776,25 @@ function installOwnedSkills(skills) {
3888
4776
  const backupPath = join4(backupRoot, skill.name);
3889
4777
  const hadExistingDestination = existsSync9(skill.destinationPath);
3890
4778
  if (hadExistingDestination) {
3891
- renameSync2(skill.destinationPath, backupPath);
4779
+ renameSync3(skill.destinationPath, backupPath);
3892
4780
  }
3893
4781
  try {
3894
- renameSync2(stagedPath, skill.destinationPath);
4782
+ renameSync3(stagedPath, skill.destinationPath);
3895
4783
  } catch (error) {
3896
4784
  if (existsSync9(skill.destinationPath)) {
3897
- rmSync(skill.destinationPath, { recursive: true, force: true });
4785
+ rmSync2(skill.destinationPath, { recursive: true, force: true });
3898
4786
  }
3899
4787
  if (hadExistingDestination && existsSync9(backupPath)) {
3900
- renameSync2(backupPath, skill.destinationPath);
4788
+ renameSync3(backupPath, skill.destinationPath);
3901
4789
  }
3902
4790
  throw error;
3903
4791
  }
3904
4792
  if (existsSync9(backupPath)) {
3905
- rmSync(backupPath, { recursive: true, force: true });
4793
+ rmSync2(backupPath, { recursive: true, force: true });
3906
4794
  }
3907
4795
  }
3908
4796
  } finally {
3909
- rmSync(transactionRoot, { recursive: true, force: true });
4797
+ rmSync2(transactionRoot, { recursive: true, force: true });
3910
4798
  }
3911
4799
  }
3912
4800
  function syncOpenCodeOwnedSkills(options = {}) {
@@ -3928,7 +4816,8 @@ function syncOpenCodeOwnedSkills(options = {}) {
3928
4816
 
3929
4817
  // src/cli/operations/opencode.ts
3930
4818
  var PACKAGE_NAME = "thoth-agents";
3931
- var EXPECTED_PLUGIN = `${PACKAGE_NAME}@latest`;
4819
+ var EXECUTING_PACKAGE_VERSION = resolveExecutingPackageVersion();
4820
+ var EXPECTED_PLUGIN = EXECUTING_PACKAGE_VERSION.ok ? `${PACKAGE_NAME}@${EXECUTING_PACKAGE_VERSION.version}` : null;
3932
4821
  var OPENAI_PRESET = "openai";
3933
4822
  var APPLIED_MODELS_PRESET = "agents";
3934
4823
  var ROLE_NAMES = [...ALL_AGENT_NAMES];
@@ -4053,7 +4942,11 @@ function issueOpenCodePlan(plan, status, context2, payload) {
4053
4942
  if (!planDigest.ok || !planDigest.digest || !statusDigest.ok || !statusDigest.digest) {
4054
4943
  return plan;
4055
4944
  }
4056
- const immutablePayload = payload.kind === "fixed" ? Object.freeze({ kind: "fixed", action: payload.action }) : Object.freeze({
4945
+ const immutablePayload = payload.kind === "fixed" ? Object.freeze({
4946
+ kind: "fixed",
4947
+ action: payload.action,
4948
+ version: payload.version
4949
+ }) : Object.freeze({
4057
4950
  kind: "model",
4058
4951
  roles: Object.freeze(
4059
4952
  payload.roles.map((role) => Object.freeze({ ...role }))
@@ -4065,6 +4958,7 @@ function issueOpenCodePlan(plan, status, context2, payload) {
4065
4958
  harness: "opencode",
4066
4959
  action: payload.kind === "model" ? "model-config" : payload.action,
4067
4960
  context: immutableContext(context2),
4961
+ runtimeContext: context2,
4068
4962
  payload: immutablePayload
4069
4963
  });
4070
4964
  return plan;
@@ -4138,7 +5032,7 @@ function targetForMainConfig(state) {
4138
5032
  path: getExistingConfigPath(),
4139
5033
  label: "OpenCode config",
4140
5034
  ...state ? { state } : {},
4141
- expected: `plugin includes ${EXPECTED_PLUGIN}`
5035
+ expected: EXPECTED_PLUGIN ? `plugin includes ${EXPECTED_PLUGIN}` : "plugin version unavailable"
4142
5036
  };
4143
5037
  }
4144
5038
  function targetForLiteConfig(state) {
@@ -4216,7 +5110,7 @@ function configPluginMarker(config) {
4216
5110
  return plugins.length > 0 ? `plugin: ${JSON.stringify(plugins)}` : "plugin: []";
4217
5111
  }
4218
5112
  function hasExpectedPlugin(config) {
4219
- return Array.isArray(config?.plugin) && config.plugin.includes(EXPECTED_PLUGIN);
5113
+ return EXPECTED_PLUGIN !== null && Array.isArray(config?.plugin) && config.plugin.includes(EXPECTED_PLUGIN);
4220
5114
  }
4221
5115
  function hasManagedPluginDrift(config) {
4222
5116
  return Array.isArray(config?.plugin) && config.plugin.some(
@@ -4356,6 +5250,13 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
4356
5250
  const lite = parseConfig(litePath);
4357
5251
  const diagnostics = [];
4358
5252
  const skillStatus = openCodeSkillTargets(context2);
5253
+ if (!EXECUTING_PACKAGE_VERSION.ok) {
5254
+ diagnostics.push({
5255
+ severity: "critical",
5256
+ message: EXECUTING_PACKAGE_VERSION.error.message,
5257
+ code: "opencode-package-version-unresolved"
5258
+ });
5259
+ }
4359
5260
  if (main.error) {
4360
5261
  diagnostics.push({
4361
5262
  severity: "critical",
@@ -4419,7 +5320,7 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
4419
5320
  harness: "opencode",
4420
5321
  displayName: "OpenCode",
4421
5322
  state: "drift",
4422
- summary: "OpenCode config has a managed thoth-agents plugin entry that is not latest.",
5323
+ summary: "OpenCode config has a managed thoth-agents plugin entry that does not match the executing CLI release.",
4423
5324
  targets: [
4424
5325
  { ...mainTarget, state: "drift" },
4425
5326
  {
@@ -4653,8 +5554,13 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
4653
5554
  };
4654
5555
  }
4655
5556
  function getOpenCodeStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
5557
+ const managedStatus = getOpenCodeManagedStatus(context2);
4656
5558
  return {
4657
- ...getOpenCodeManagedStatus(context2),
5559
+ ...managedStatus,
5560
+ targets: [
5561
+ ...managedStatus.targets,
5562
+ getCliManagedInstallVersionTarget("opencode", context2)
5563
+ ],
4658
5564
  providerCapability: classifyProviderCapabilityEvidence(evidence)
4659
5565
  };
4660
5566
  }
@@ -4737,52 +5643,77 @@ function planFromItems(id, action, title, summary, items, context2) {
4737
5643
  }
4738
5644
  };
4739
5645
  }
4740
- function buildOpenCodeUpdatePlan(_context = { cwd: process.cwd() }) {
4741
- const path = getExistingConfigPath();
4742
- const { plan, status } = planFromItems(
4743
- "opencode-update-preview",
4744
- "update",
4745
- "Update OpenCode managed plugin entry",
4746
- `Preview ensuring plugin: ["${EXPECTED_PLUGIN}"].`,
5646
+ function packageVersionForContext(context2) {
5647
+ return (context2.resolveExecutingPackageVersion ?? resolveExecutingPackageVersion)();
5648
+ }
5649
+ function ledgerOptionsForContext(context2) {
5650
+ return context2.installLedgerOptions ?? {
5651
+ env: context2.env,
5652
+ homeDir: homeDirFromContext(context2)
5653
+ };
5654
+ }
5655
+ function failedPackageVersionPlan(action, context2, error) {
5656
+ const { plan } = planFromItems(
5657
+ `opencode-${action}-version-unavailable`,
5658
+ action,
5659
+ `${action === "install" ? "Install" : action === "update" ? "Update" : "Sync"} OpenCode`,
5660
+ "The executing package version could not be approved.",
4747
5661
  [
4748
5662
  {
4749
- title: "Ensure OpenCode plugin points at thoth-agents@latest",
4750
- target: targetForMainConfig(),
4751
- state: getOpenCodeStatus(_context).state,
4752
- preview: `plugin: ["${EXPECTED_PLUGIN}"]`,
4753
- backup: defaultBackup(path)
5663
+ title: "Resolve exact thoth-agents package version",
5664
+ target: { kind: "package", label: "Executing thoth-agents package" },
5665
+ state: "unknown",
5666
+ preview: error
4754
5667
  }
4755
5668
  ],
4756
- _context
5669
+ context2
4757
5670
  );
4758
- return issueOpenCodePlan(plan, status, _context, {
4759
- kind: "fixed",
4760
- action: "update"
5671
+ plan.canApply = false;
5672
+ plan.warnings.push({
5673
+ severity: "critical",
5674
+ code: "opencode-package-version-unresolved",
5675
+ message: error
4761
5676
  });
5677
+ return plan;
4762
5678
  }
4763
- function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
5679
+ function buildOpenCodeCompletePlan(action, context2) {
5680
+ const packageVersion = packageVersionForContext(context2);
5681
+ if (!packageVersion.ok) {
5682
+ return failedPackageVersionPlan(
5683
+ action,
5684
+ context2,
5685
+ packageVersion.error.message
5686
+ );
5687
+ }
5688
+ const version = packageVersion.version;
5689
+ const expectedPlugin = `${PACKAGE_NAME}@${version}`;
4764
5690
  const generatedConfig = generateLiteConfig({
4765
5691
  agent: "opencode",
4766
5692
  hasTmux: false,
4767
5693
  dryRun: true,
4768
5694
  reset: false
4769
5695
  });
4770
- const litePath = getExistingLiteConfigPath();
4771
- const ownedSkills = syncOpenCodeOwnedSkills({
5696
+ const syncOwned = context2.syncOpenCodeOwnedSkills ?? syncOpenCodeOwnedSkills;
5697
+ const ownedSkills = syncOwned({
4772
5698
  dryRun: true,
4773
5699
  homeDir: homeDirFromContext(context2)
4774
5700
  });
5701
+ const providerCommand = getThothMemSetupCommand("opencode", true);
5702
+ const ledgerPath = getInstallLedgerPath(ledgerOptionsForContext(context2));
4775
5703
  const { plan, status } = planFromItems(
4776
- "opencode-sync-preview",
4777
- "sync",
4778
- "Sync OpenCode managed configuration",
4779
- "Preview OpenCode plugin entry, default-agent disablement, and thoth-agents config sync.",
5704
+ `opencode-${action}-preview`,
5705
+ action,
5706
+ `${action === "install" ? "Install" : "Update"} complete OpenCode setup`,
5707
+ `Preview complete OpenCode refresh pinned to ${expectedPlugin}.`,
4780
5708
  [
4781
5709
  {
4782
- title: "Ensure OpenCode plugin points at thoth-agents@latest",
4783
- target: targetForMainConfig(),
5710
+ title: `Ensure OpenCode plugin points at ${expectedPlugin}`,
5711
+ target: {
5712
+ ...targetForMainConfig(),
5713
+ expected: `plugin includes ${expectedPlugin}`
5714
+ },
4784
5715
  state: getOpenCodeStatus(context2).state,
4785
- preview: `plugin: ["${EXPECTED_PLUGIN}"]`,
5716
+ preview: `plugin: ["${expectedPlugin}"]`,
4786
5717
  backup: defaultBackup(getExistingConfigPath())
4787
5718
  },
4788
5719
  {
@@ -4795,7 +5726,7 @@ function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
4795
5726
  title: "Write thoth-agents seven-role config",
4796
5727
  target: targetForLiteConfig(),
4797
5728
  preview: JSON.stringify(generatedConfig, null, 2),
4798
- backup: defaultBackup(litePath)
5729
+ backup: defaultBackup(getExistingLiteConfigPath())
4799
5730
  },
4800
5731
  {
4801
5732
  title: "Synchronize global thoth-owned OpenCode skills",
@@ -4821,59 +5752,69 @@ function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
4821
5752
  null,
4822
5753
  2
4823
5754
  )
5755
+ },
5756
+ {
5757
+ title: "Plan provider-owned thoth-mem setup",
5758
+ target: {
5759
+ kind: "surface",
5760
+ label: "Provider-owned thoth-mem setup"
5761
+ },
5762
+ preview: `${providerCommand.command} ${providerCommand.args.join(" ")}`
5763
+ },
5764
+ {
5765
+ title: "Record completed OpenCode CLI install",
5766
+ target: {
5767
+ kind: "file",
5768
+ path: ledgerPath,
5769
+ label: "CLI-managed install version",
5770
+ expected: `recorded ${version}`
5771
+ },
5772
+ preview: JSON.stringify({ harness: "opencode", version })
4824
5773
  }
4825
5774
  ],
4826
5775
  context2
4827
5776
  );
5777
+ plan.canApply = canApplyToManagedHealth("install", status);
4828
5778
  return issueOpenCodePlan(plan, status, context2, {
4829
5779
  kind: "fixed",
4830
- action: "sync"
5780
+ action,
5781
+ version
4831
5782
  });
4832
5783
  }
4833
- function tuiInstallConfig() {
4834
- return {
5784
+ function buildOpenCodeUpdatePlan(context2 = { cwd: process.cwd() }) {
5785
+ return buildOpenCodeCompletePlan("update", context2);
5786
+ }
5787
+ function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
5788
+ const packageVersion = packageVersionForContext(context2);
5789
+ if (!packageVersion.ok) {
5790
+ return failedPackageVersionPlan(
5791
+ "sync",
5792
+ context2,
5793
+ packageVersion.error.message
5794
+ );
5795
+ }
5796
+ const generatedConfig = generateLiteConfig({
4835
5797
  agent: "opencode",
4836
5798
  hasTmux: false,
4837
5799
  dryRun: true,
4838
5800
  reset: false
4839
- };
4840
- }
4841
- function buildOpenCodeInstallPlan(context2 = { cwd: process.cwd() }) {
5801
+ });
5802
+ const litePath = getExistingLiteConfigPath();
4842
5803
  const ownedSkills = syncOpenCodeOwnedSkills({
4843
5804
  dryRun: true,
4844
5805
  homeDir: homeDirFromContext(context2)
4845
5806
  });
4846
- const generatedConfig = generateLiteConfig(tuiInstallConfig());
4847
- const installPreview = {
4848
- noTui: true,
4849
- hasTmux: false,
4850
- ownedSkills: ownedSkills.skills.map((skill) => skill.name),
4851
- requiredSkills: REQUIRED_SKILLS.map((skill) => skill.name),
4852
- equivalentCommand: "install --agent=opencode --no-tui --tmux=no"
4853
- };
4854
- const litePath = getExistingLiteConfigPath();
4855
5807
  const { plan, status } = planFromItems(
4856
- "opencode-install-preview",
4857
- "install",
4858
- "Preview install",
4859
- "Preview OpenCode install with the required global skills.",
5808
+ "opencode-sync-preview",
5809
+ "sync",
5810
+ "Sync OpenCode managed configuration",
5811
+ "Preview OpenCode plugin entry, default-agent disablement, and thoth-agents config sync.",
4860
5812
  [
4861
5813
  {
4862
- title: "Apply OpenCode TUI install options",
4863
- target: {
4864
- kind: "config",
4865
- path: getExistingConfigPath(),
4866
- label: "OpenCode install options",
4867
- state: getOpenCodeStatus(context2).state,
4868
- expected: "--no-tui --tmux=no plus required global skills"
4869
- },
4870
- preview: JSON.stringify(installPreview, null, 2)
4871
- },
4872
- {
4873
- title: "Ensure OpenCode plugin points at thoth-agents@latest",
5814
+ title: `Ensure OpenCode plugin points at ${PACKAGE_NAME}@${packageVersion.version}`,
4874
5815
  target: targetForMainConfig(),
4875
5816
  state: getOpenCodeStatus(context2).state,
4876
- preview: `plugin: ["${EXPECTED_PLUGIN}"]`,
5817
+ preview: `plugin: ["${PACKAGE_NAME}@${packageVersion.version}"]`,
4877
5818
  backup: defaultBackup(getExistingConfigPath())
4878
5819
  },
4879
5820
  {
@@ -4918,9 +5859,13 @@ function buildOpenCodeInstallPlan(context2 = { cwd: process.cwd() }) {
4918
5859
  );
4919
5860
  return issueOpenCodePlan(plan, status, context2, {
4920
5861
  kind: "fixed",
4921
- action: "install"
5862
+ action: "sync",
5863
+ version: packageVersion.version
4922
5864
  });
4923
5865
  }
5866
+ function buildOpenCodeInstallPlan(context2 = { cwd: process.cwd() }) {
5867
+ return buildOpenCodeCompletePlan("install", context2);
5868
+ }
4924
5869
  function normalizeRoleModel(input) {
4925
5870
  if (input.provider && !input.model.includes("/")) {
4926
5871
  return `${input.provider}/${input.model}`;
@@ -4974,7 +5919,7 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
4974
5919
  plan.warnings.push(...effortWarnings);
4975
5920
  }
4976
5921
  if (input.harness !== "opencode") {
4977
- const rejectedPlan = {
5922
+ return {
4978
5923
  ...plan,
4979
5924
  canApply: false,
4980
5925
  warnings: [
@@ -4986,13 +5931,12 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
4986
5931
  }
4987
5932
  ]
4988
5933
  };
4989
- return rejectedPlan;
4990
5934
  }
4991
5935
  const unsupportedRoles = input.roles.filter(
4992
5936
  (role) => !ROLE_NAMES.includes(role.role)
4993
5937
  );
4994
5938
  if (input.roles.length === 0 || unsupportedRoles.length > 0) {
4995
- const rejectedPlan = {
5939
+ return {
4996
5940
  ...plan,
4997
5941
  canApply: false,
4998
5942
  warnings: [
@@ -5004,7 +5948,6 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
5004
5948
  }
5005
5949
  ]
5006
5950
  };
5007
- return rejectedPlan;
5008
5951
  }
5009
5952
  if (effortWarnings.length > 0) {
5010
5953
  return plan;
@@ -5261,9 +6204,10 @@ function applyModelPlan(roles) {
5261
6204
  function applyRequiredSkills(context2) {
5262
6205
  const warnings = [];
5263
6206
  const homeDir = homeDirFromContext(context2);
6207
+ const installSkill = context2.installRequiredSkill ?? installRequiredSkill;
5264
6208
  for (const skill of REQUIRED_SKILLS) {
5265
6209
  try {
5266
- const result = installRequiredSkill(skill, "opencode", { homeDir });
6210
+ const result = installSkill(skill, "opencode", { homeDir });
5267
6211
  if (result.status !== "failed") continue;
5268
6212
  warnings.push({
5269
6213
  severity: "critical",
@@ -5282,7 +6226,8 @@ function applyRequiredSkills(context2) {
5282
6226
  return { success: warnings.length === 0, warnings };
5283
6227
  }
5284
6228
  function applyOwnedSkills(context2) {
5285
- const result = syncOpenCodeOwnedSkills({
6229
+ const syncOwned = context2.syncOpenCodeOwnedSkills ?? syncOpenCodeOwnedSkills;
6230
+ const result = syncOwned({
5286
6231
  homeDir: homeDirFromContext(context2)
5287
6232
  });
5288
6233
  if (result.success) return { success: true, warnings: [] };
@@ -5309,8 +6254,32 @@ function applyOpenCodePlan(plan) {
5309
6254
  "opencode-plan-unissued"
5310
6255
  );
5311
6256
  }
6257
+ if (issued.payload.kind === "fixed") {
6258
+ const currentPackageVersion = packageVersionForContext(
6259
+ issued.runtimeContext
6260
+ );
6261
+ if (!currentPackageVersion.ok) {
6262
+ return rejectPlan3(
6263
+ plan,
6264
+ currentPackageVersion.error.message,
6265
+ "critical",
6266
+ "opencode-package-version-unresolved",
6267
+ issued.context
6268
+ );
6269
+ }
6270
+ if (currentPackageVersion.version !== issued.payload.version) {
6271
+ return rejectPlan3(
6272
+ plan,
6273
+ `Approved package version changed from ${issued.payload.version} to ${currentPackageVersion.version} before apply.`,
6274
+ "critical",
6275
+ "opencode-package-version-changed",
6276
+ issued.context
6277
+ );
6278
+ }
6279
+ }
5312
6280
  const status = getOpenCodeStatus(issued.context);
5313
- if (!canApplyToManagedHealth(plan.action, status)) {
6281
+ const healthAction = issued.payload.kind === "fixed" && (issued.payload.action === "install" || issued.payload.action === "update") ? "install" : plan.action;
6282
+ if (!canApplyToManagedHealth(healthAction, status)) {
5314
6283
  return rejectPlan3(
5315
6284
  plan,
5316
6285
  `OpenCode state is ${status.state}; refusing to apply plan without a safe status.`,
@@ -5335,9 +6304,11 @@ function applyOpenCodePlan(plan) {
5335
6304
  const changedTargets = [];
5336
6305
  const backups = [];
5337
6306
  const warnings = [];
5338
- const mainConfigResult = updateOpenCodeMainConfig({
6307
+ const updateMainConfig = issued.runtimeContext.updateOpenCodeMainConfig ?? updateOpenCodeMainConfig;
6308
+ const mainConfigResult = updateMainConfig({
5339
6309
  ensurePlugin: true,
5340
- disableDefaults: issued.payload.action === "sync" || issued.payload.action === "install"
6310
+ pluginVersion: issued.payload.version,
6311
+ disableDefaults: true
5341
6312
  });
5342
6313
  if (!mainConfigResult.success) {
5343
6314
  return rejectPlan3(
@@ -5350,7 +6321,7 @@ function applyOpenCodePlan(plan) {
5350
6321
  }
5351
6322
  changedTargets.push({
5352
6323
  ...targetForMainConfig("installed"),
5353
- observed: `plugin includes ${EXPECTED_PLUGIN}`
6324
+ observed: `plugin includes ${PACKAGE_NAME}@${issued.payload.version}`
5354
6325
  });
5355
6326
  if (existsSync10(`${mainConfigResult.configPath}.bak`)) {
5356
6327
  backups.push({
@@ -5358,68 +6329,95 @@ function applyOpenCodePlan(plan) {
5358
6329
  label: "OpenCode config backup"
5359
6330
  });
5360
6331
  }
5361
- if (issued.payload.action === "sync" || issued.payload.action === "install") {
5362
- const liteResult = writeLiteConfig(
5363
- {
5364
- agent: "opencode",
5365
- hasTmux: false,
5366
- dryRun: false,
5367
- reset: false
5368
- },
5369
- getExistingLiteConfigPath()
6332
+ const writeLite = issued.runtimeContext.writeLiteConfig ?? writeLiteConfig;
6333
+ const liteResult = writeLite(
6334
+ {
6335
+ agent: "opencode",
6336
+ hasTmux: false,
6337
+ dryRun: false,
6338
+ reset: false
6339
+ },
6340
+ getExistingLiteConfigPath()
6341
+ );
6342
+ if (!liteResult.success) {
6343
+ return rejectPlan3(
6344
+ plan,
6345
+ liteResult.error ?? "Failed to write thoth-agents config."
5370
6346
  );
5371
- if (!liteResult.success) {
5372
- return rejectPlan3(
5373
- plan,
5374
- liteResult.error ?? "Failed to write thoth-agents config."
5375
- );
5376
- }
5377
- changedTargets.push({
5378
- ...targetForLiteConfig("installed"),
5379
- observed: "seven-role roster written"
6347
+ }
6348
+ changedTargets.push({
6349
+ ...targetForLiteConfig("installed"),
6350
+ observed: "seven-role roster written"
6351
+ });
6352
+ if (existsSync10(`${liteResult.configPath}.bak`)) {
6353
+ backups.push({
6354
+ path: `${liteResult.configPath}.bak`,
6355
+ label: "thoth-agents config backup"
5380
6356
  });
5381
- if (existsSync10(`${liteResult.configPath}.bak`)) {
5382
- backups.push({
5383
- path: `${liteResult.configPath}.bak`,
5384
- label: "thoth-agents config backup"
5385
- });
5386
- }
5387
6357
  }
5388
- if (issued.payload.action === "install" || issued.payload.action === "sync") {
5389
- const ownedSkills = applyOwnedSkills(issued.context);
5390
- warnings.push(...ownedSkills.warnings);
5391
- changedTargets.push({
5392
- kind: "skill",
5393
- label: "Thoth-owned OpenCode skills",
5394
- state: ownedSkills.success ? "installed" : "drift",
5395
- observed: ownedSkills.success ? "thoth-owned global skills synchronized" : "thoth-owned global skill synchronization failed"
6358
+ const ownedSkills = applyOwnedSkills(issued.runtimeContext);
6359
+ warnings.push(...ownedSkills.warnings);
6360
+ changedTargets.push({
6361
+ kind: "skill",
6362
+ label: "Thoth-owned OpenCode skills",
6363
+ state: ownedSkills.success ? "installed" : "drift",
6364
+ observed: ownedSkills.success ? "thoth-owned global skills synchronized" : "thoth-owned global skill synchronization failed"
6365
+ });
6366
+ if (!ownedSkills.success) {
6367
+ return {
6368
+ harness: "opencode",
6369
+ action: plan.action,
6370
+ applied: false,
6371
+ summary: "OpenCode configuration was written, but thoth-owned skills failed to synchronize.",
6372
+ changedTargets,
6373
+ backups,
6374
+ warnings,
6375
+ disclaimers: defaultDisclaimers()
6376
+ };
6377
+ }
6378
+ const requiredSkills = applyRequiredSkills(issued.runtimeContext);
6379
+ warnings.push(...requiredSkills.warnings);
6380
+ changedTargets.push({
6381
+ kind: "skill",
6382
+ label: "Required OpenCode skills",
6383
+ state: requiredSkills.success ? "installed" : "drift",
6384
+ observed: requiredSkills.success ? "required external skills installed" : "required external skill installation failed"
6385
+ });
6386
+ if (!requiredSkills.success) {
6387
+ return {
6388
+ harness: "opencode",
6389
+ action: plan.action,
6390
+ applied: false,
6391
+ summary: "OpenCode configuration was written, but required skills failed to install.",
6392
+ changedTargets,
6393
+ backups,
6394
+ warnings,
6395
+ disclaimers: defaultDisclaimers()
6396
+ };
6397
+ }
6398
+ if (issued.payload.action === "install" || issued.payload.action === "update") {
6399
+ const finalize = issued.runtimeContext.finalizeHarnessInstall ?? finalizeHarnessInstall;
6400
+ const completion = finalize({
6401
+ harness: "opencode",
6402
+ version: issued.payload.version,
6403
+ dryRun: false,
6404
+ cwd: issued.runtimeContext.cwd,
6405
+ runThothMemSetup: issued.runtimeContext.runThothMemSetup,
6406
+ ledgerOptions: ledgerOptionsForContext(issued.runtimeContext)
5396
6407
  });
5397
- if (!ownedSkills.success) {
5398
- return {
5399
- harness: "opencode",
5400
- action: plan.action,
5401
- applied: false,
5402
- summary: "OpenCode configuration was written, but thoth-owned skills failed to synchronize.",
5403
- changedTargets,
5404
- backups,
5405
- warnings,
5406
- disclaimers: defaultDisclaimers()
5407
- };
5408
- }
5409
- const requiredSkills = applyRequiredSkills(issued.context);
5410
- warnings.push(...requiredSkills.warnings);
5411
- changedTargets.push({
5412
- kind: "skill",
5413
- label: "Required OpenCode skills",
5414
- state: requiredSkills.success ? "installed" : "drift",
5415
- observed: requiredSkills.success ? "required external skills installed" : "required external skill installation failed"
6408
+ const completionEvidence = getInstallCompletionEvidence(completion, {
6409
+ codePrefix: "opencode",
6410
+ version: issued.payload.version,
6411
+ fallbackError: "OpenCode install finalization failed."
5416
6412
  });
5417
- if (!requiredSkills.success) {
6413
+ warnings.push(...completionEvidence.warnings);
6414
+ changedTargets.push(...completionEvidence.targets);
6415
+ if (!completion.success) {
5418
6416
  return {
5419
6417
  harness: "opencode",
5420
6418
  action: plan.action,
5421
6419
  applied: false,
5422
- summary: "OpenCode configuration was written, but required skills failed to install.",
6420
+ summary: completion.error ?? "OpenCode install finalization failed.",
5423
6421
  changedTargets,
5424
6422
  backups,
5425
6423
  warnings,
@@ -5431,7 +6429,7 @@ function applyOpenCodePlan(plan) {
5431
6429
  harness: "opencode",
5432
6430
  action: plan.action,
5433
6431
  applied: true,
5434
- summary: plan.action === "install" ? "Applied OpenCode install plan." : plan.action === "sync" ? "Applied OpenCode managed configuration sync." : "Applied OpenCode plugin update.",
6432
+ summary: plan.action === "install" ? "Applied OpenCode install plan." : plan.action === "sync" ? "Applied OpenCode managed configuration sync." : "Applied complete OpenCode update plan.",
5435
6433
  changedTargets,
5436
6434
  backups,
5437
6435
  warnings,
@@ -5463,13 +6461,13 @@ import { execFileSync } from "child_process";
5463
6461
  // src/cli/model-catalog/cache.ts
5464
6462
  import {
5465
6463
  existsSync as existsSync11,
5466
- mkdirSync as mkdirSync4,
5467
- readFileSync as readFileSync6,
5468
- renameSync as renameSync3,
6464
+ mkdirSync as mkdirSync5,
6465
+ readFileSync as readFileSync7,
6466
+ renameSync as renameSync4,
5469
6467
  unlinkSync,
5470
- writeFileSync as writeFileSync3
6468
+ writeFileSync as writeFileSync4
5471
6469
  } from "fs";
5472
- import { homedir as homedir4 } from "os";
6470
+ import { homedir as homedir5 } from "os";
5473
6471
  import { dirname as dirname6, posix, win32 } from "path";
5474
6472
  import { z as z2 } from "zod";
5475
6473
  var modelCatalogSourceSchema = z2.enum(["remote", "lkg", "manual"]);
@@ -5490,7 +6488,7 @@ var modelsDevCacheSchema = z2.object({
5490
6488
  function resolveModelsDevCachePath(options = {}) {
5491
6489
  const platform = options.platform ?? process.platform;
5492
6490
  const env = options.env ?? process.env;
5493
- const home = options.homeDir ?? homedir4();
6491
+ const home = options.homeDir ?? homedir5();
5494
6492
  if (platform === "win32") {
5495
6493
  const path = win32;
5496
6494
  const base2 = env.LOCALAPPDATA ?? env.APPDATA ?? path.join(home, "AppData", "Local");
@@ -5502,7 +6500,7 @@ function resolveModelsDevCachePath(options = {}) {
5502
6500
  function readModelsDevCache(cachePath) {
5503
6501
  if (!existsSync11(cachePath)) return void 0;
5504
6502
  try {
5505
- const input = JSON.parse(readFileSync6(cachePath, "utf8"));
6503
+ const input = JSON.parse(readFileSync7(cachePath, "utf8"));
5506
6504
  const parsed = modelsDevCacheSchema.safeParse(input);
5507
6505
  return parsed.success ? parsed.data : void 0;
5508
6506
  } catch {
@@ -5514,15 +6512,15 @@ function writeModelsDevCacheAtomic(cachePath, cache) {
5514
6512
  if (!validated.success) {
5515
6513
  throw new Error(`Invalid models.dev cache: ${validated.error.message}`);
5516
6514
  }
5517
- mkdirSync4(dirname6(cachePath), { recursive: true });
6515
+ mkdirSync5(dirname6(cachePath), { recursive: true });
5518
6516
  const temporaryPath = `${cachePath}.${process.pid}.${Date.now()}.tmp`;
5519
6517
  try {
5520
- writeFileSync3(
6518
+ writeFileSync4(
5521
6519
  temporaryPath,
5522
6520
  `${JSON.stringify(validated.data, null, 2)}
5523
6521
  `
5524
6522
  );
5525
- renameSync3(temporaryPath, cachePath);
6523
+ renameSync4(temporaryPath, cachePath);
5526
6524
  } catch (error) {
5527
6525
  if (existsSync11(temporaryPath)) unlinkSync(temporaryPath);
5528
6526
  throw error;
@@ -5837,7 +6835,7 @@ async function getModelOptions(harness) {
5837
6835
  }
5838
6836
 
5839
6837
  // src/cli/tui/operations.ts
5840
- import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
6838
+ import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
5841
6839
  var context = { cwd: process.cwd() };
5842
6840
  var codexContext = { cwd: process.cwd() };
5843
6841
  var claudeCodeContext = {
@@ -5879,7 +6877,7 @@ function getCodexModelRoles(source = codexContext) {
5879
6877
  const item = plan.items.find(
5880
6878
  (candidate) => candidate.action === "write-role-toml" && candidate.role === role
5881
6879
  );
5882
- const content = item && existsSync12(item.targetPath) ? readFileSync7(item.targetPath, "utf8") : item?.content;
6880
+ const content = item && existsSync12(item.targetPath) ? readFileSync8(item.targetPath, "utf8") : item?.content;
5883
6881
  const effort = content ? parseRoleTomlEffort(content) : void 0;
5884
6882
  return {
5885
6883
  role,
@@ -6002,8 +7000,11 @@ export {
6002
7000
  buildClaudeCodeSetupPlan,
6003
7001
  applyClaudeCodeSetup,
6004
7002
  formatClaudeCodeSetupPlan,
7003
+ buildCodexPluginSetupPlan,
7004
+ applyCodexPluginSetup,
7005
+ formatCodexPluginSetupPlan,
7006
+ finalizeHarnessInstall,
6005
7007
  syncOpenCodeOwnedSkills,
6006
- getNpxCommand,
6007
7008
  REQUIRED_SKILLS,
6008
7009
  getRequiredSkillInstallCommand,
6009
7010
  installRequiredSkill,