thoth-agents 0.4.2 → 0.5.1

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 (115) hide show
  1. package/LICENSE +30 -0
  2. package/README.md +86 -58
  3. package/dist/agents/prompt-sections.d.ts +5 -3
  4. package/dist/agents/prompt-utils.d.ts +1 -1
  5. package/dist/agents/worker.d.ts +2 -0
  6. package/dist/{chunk-STCURXAQ.js → chunk-MRFCA3U2.js} +33 -67
  7. package/dist/{chunk-EFGSGPYM.js → chunk-OW6I7ZUU.js} +391 -416
  8. package/dist/chunk-V7HSGRMB.js +1016 -0
  9. package/dist/{chunk-WCLUZZHI.js → chunk-VUNYH263.js} +1580 -351
  10. package/dist/cli/claude-code-paths.d.ts +2 -2
  11. package/dist/cli/codex-install.d.ts +5 -2
  12. package/dist/cli/codex-paths.d.ts +2 -2
  13. package/dist/cli/index.js +47 -11
  14. package/dist/cli/operations/pi.d.ts +1 -0
  15. package/dist/cli/pi-effort.d.ts +1 -1
  16. package/dist/cli/pi-external-package.d.ts +24 -0
  17. package/dist/cli/pi-git-source.d.ts +1 -0
  18. package/dist/cli/pi-install.d.ts +92 -15
  19. package/dist/cli/pi-managed-write.d.ts +3 -0
  20. package/dist/cli/pi-model-config.d.ts +17 -0
  21. package/dist/cli/pi-native-probe.d.ts +2 -0
  22. package/dist/cli/pi-paths.d.ts +2 -0
  23. package/dist/cli/pi-resources.d.ts +3 -1
  24. package/dist/cli/pi-tool-config.d.ts +32 -0
  25. package/dist/cli/tui/index.js +3 -3
  26. package/dist/cli/types.d.ts +2 -0
  27. package/dist/config/constants.d.ts +4 -8
  28. package/dist/config/schema.d.ts +7 -36
  29. package/dist/harness/adapters/claude-code.d.ts +2 -4
  30. package/dist/harness/core/agent-pack.d.ts +52 -42
  31. package/dist/harness/core/memory-governance.d.ts +2 -2
  32. package/dist/harness/core/sdd.d.ts +159 -319
  33. package/dist/harness/writers/pi-agent.d.ts +3 -1
  34. package/dist/index.js +12 -6
  35. package/dist/pi/tools-panel.d.ts +51 -0
  36. package/dist/pi.d.ts +44 -2
  37. package/dist/pi.js +471 -9
  38. package/dist/utils/subprocess.d.ts +1 -0
  39. package/package.json +23 -6
  40. package/pi/.thoth-agents-assets.json +5 -6
  41. package/pi/agents/thoth-designer.md +23 -8
  42. package/pi/agents/thoth-explorer.md +29 -9
  43. package/pi/agents/thoth-librarian.md +30 -8
  44. package/pi/agents/thoth-oracle.md +18 -10
  45. package/pi/agents/thoth-worker.md +96 -0
  46. package/plugin/.claude-plugin/.thoth-agents-plugin-assets.json +7 -12
  47. package/plugin/.claude-plugin/plugin.json +2 -2
  48. package/plugin/.codex-plugin/plugin.json +2 -2
  49. package/plugin/agents/designer.md +13 -5
  50. package/plugin/agents/explorer.md +16 -5
  51. package/plugin/agents/librarian.md +15 -4
  52. package/plugin/agents/oracle.md +13 -7
  53. package/plugin/agents/orchestrator.md +82 -91
  54. package/plugin/agents/worker.md +76 -0
  55. package/plugin/skills/plan-reviewer/SKILL.md +39 -141
  56. package/plugin/skills/thoth-archive/SKILL.md +50 -47
  57. package/plugin/skills/thoth-archive/scripts/archive.mjs +287 -654
  58. package/plugin/skills/thoth-constitution/SKILL.md +23 -42
  59. package/plugin/skills/thoth-constitution/templates/constitution.md +54 -44
  60. package/plugin/skills/thoth-init/SKILL.md +19 -29
  61. package/plugin/skills/thoth-init/scripts/init.mjs +73 -66
  62. package/plugin/skills/thoth-sdd/SKILL.md +164 -117
  63. package/plugin/skills/thoth-sdd/references/phases/checklist.md +2 -22
  64. package/plugin/skills/thoth-sdd/references/phases/clarify.md +14 -12
  65. package/plugin/skills/thoth-sdd/references/phases/converge.md +2 -13
  66. package/plugin/skills/thoth-sdd/references/phases/explore.md +3 -8
  67. package/plugin/skills/thoth-sdd/references/phases/implement.md +58 -57
  68. package/plugin/skills/thoth-sdd/references/phases/plan.md +2 -18
  69. package/plugin/skills/thoth-sdd/references/phases/specify.md +2 -33
  70. package/plugin/skills/thoth-sdd/references/phases/tasks.md +4 -45
  71. package/plugin/skills/thoth-sdd/references/phases/verify.md +4 -22
  72. package/plugin/skills/thoth-sdd/scripts/validate.mjs +610 -1232
  73. package/plugin/skills/thoth-sdd/templates/change.md +94 -0
  74. package/skills/README.md +11 -13
  75. package/skills/plan-reviewer/SKILL.md +39 -141
  76. package/skills/thoth-archive/SKILL.md +50 -47
  77. package/skills/thoth-archive/scripts/archive.mjs +287 -654
  78. package/skills/thoth-constitution/SKILL.md +23 -42
  79. package/skills/thoth-constitution/templates/constitution.md +54 -44
  80. package/skills/thoth-init/SKILL.md +19 -29
  81. package/skills/thoth-init/scripts/init.mjs +73 -66
  82. package/skills/thoth-sdd/SKILL.md +164 -117
  83. package/skills/thoth-sdd/references/phases/checklist.md +2 -22
  84. package/skills/thoth-sdd/references/phases/clarify.md +14 -12
  85. package/skills/thoth-sdd/references/phases/converge.md +2 -13
  86. package/skills/thoth-sdd/references/phases/explore.md +3 -8
  87. package/skills/thoth-sdd/references/phases/implement.md +58 -57
  88. package/skills/thoth-sdd/references/phases/plan.md +2 -18
  89. package/skills/thoth-sdd/references/phases/specify.md +2 -33
  90. package/skills/thoth-sdd/references/phases/tasks.md +4 -45
  91. package/skills/thoth-sdd/references/phases/verify.md +4 -22
  92. package/skills/thoth-sdd/scripts/validate.mjs +610 -1232
  93. package/skills/thoth-sdd/templates/change.md +94 -0
  94. package/thoth-agents.schema.json +9 -77
  95. package/dist/agents/deep.d.ts +0 -2
  96. package/dist/agents/quick.d.ts +0 -2
  97. package/dist/chunk-2U5MBLLV.js +0 -312
  98. package/pi/agents/thoth-deep.md +0 -81
  99. package/pi/agents/thoth-quick.md +0 -81
  100. package/plugin/agents/deep.md +0 -68
  101. package/plugin/agents/quick.md +0 -68
  102. package/plugin/skills/plan-reviewer/templates/plan-review.md +0 -65
  103. package/plugin/skills/thoth-sdd/templates/archive-report.md +0 -25
  104. package/plugin/skills/thoth-sdd/templates/checklist.md +0 -27
  105. package/plugin/skills/thoth-sdd/templates/plan.md +0 -46
  106. package/plugin/skills/thoth-sdd/templates/spec.md +0 -62
  107. package/plugin/skills/thoth-sdd/templates/tasks.md +0 -58
  108. package/plugin/skills/thoth-sdd/templates/verify-report.md +0 -28
  109. package/skills/plan-reviewer/templates/plan-review.md +0 -65
  110. package/skills/thoth-sdd/templates/archive-report.md +0 -25
  111. package/skills/thoth-sdd/templates/checklist.md +0 -27
  112. package/skills/thoth-sdd/templates/plan.md +0 -46
  113. package/skills/thoth-sdd/templates/spec.md +0 -62
  114. package/skills/thoth-sdd/templates/tasks.md +0 -58
  115. package/skills/thoth-sdd/templates/verify-report.md +0 -28
@@ -4,13 +4,19 @@ import {
4
4
  exa
5
5
  } from "./chunk-62L3ORXN.js";
6
6
  import {
7
+ PI_EFFORT_VALUES,
7
8
  PI_MANAGED_OWNER,
8
9
  PI_ROOT_END,
9
10
  PI_ROOT_START,
11
+ assertSafePiManagedPath,
10
12
  piAdapter,
13
+ readPiModelConfig,
11
14
  renderConfiguredRolePrompt,
12
- syncPiSpecialists
13
- } from "./chunk-2U5MBLLV.js";
15
+ resolvePiEffort,
16
+ savePiModelConfig,
17
+ syncPiSpecialists,
18
+ writePiManagedText
19
+ } from "./chunk-V7HSGRMB.js";
14
20
  import {
15
21
  ALL_AGENT_NAMES,
16
22
  CLAUDE_CODE_PROMPT_DIALECT,
@@ -41,7 +47,7 @@ import {
41
47
  updateOpenCodeMainConfig,
42
48
  writeConfig,
43
49
  writeLiteConfig
44
- } from "./chunk-EFGSGPYM.js";
50
+ } from "./chunk-OW6I7ZUU.js";
45
51
 
46
52
  // src/cli/codex-paths.ts
47
53
  import { homedir } from "os";
@@ -51,8 +57,7 @@ var CODEX_ROLE_NAMES = [
51
57
  "librarian",
52
58
  "oracle",
53
59
  "designer",
54
- "quick",
55
- "deep"
60
+ "worker"
56
61
  ];
57
62
  function getCodexHome(options = {}) {
58
63
  const explicit = options.codexHome ?? process.env.CODEX_HOME?.trim();
@@ -93,8 +98,9 @@ function isExplicitEffort(selection) {
93
98
  }
94
99
 
95
100
  // src/cli/codex-install.ts
96
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
97
- import { basename } from "path";
101
+ import { createHash as createHash2 } from "crypto";
102
+ import { existsSync as existsSync4, lstatSync, readFileSync as readFileSync4, rmSync } from "fs";
103
+ import { basename, dirname as dirname4, parse, resolve as resolve2 } from "path";
98
104
  import { fileURLToPath as fileURLToPath2 } from "url";
99
105
 
100
106
  // src/harness/adapters/codex.ts
@@ -1020,7 +1026,7 @@ function createCodexPluginPackageManifest(context2) {
1020
1026
  return {
1021
1027
  name: "thoth-agents",
1022
1028
  version: readRootPackageVersion(context2),
1023
- description: "Bundled Spec Kit-compatible SDD skills and MCP configuration; the seven-role global Codex layer requires thoth-agents CLI setup."
1029
+ description: "Bundled AI-first work skills and MCP configuration; the six-role global Codex layer requires thoth-agents CLI setup."
1024
1030
  };
1025
1031
  }
1026
1032
  function stableJson2(value) {
@@ -1065,7 +1071,7 @@ function codexRuntimeGuidance() {
1065
1071
  return [
1066
1072
  "<codex-runtime>",
1067
1073
  "- The ambient Codex session is the adaptive root; no orchestrator child TOML is generated.",
1068
- "- Delegate with `collaboration.spawn_agent` only when the root determines that specialization, context isolation, review, or independent parallel work creates a net gain.",
1074
+ "- When delegation is selected under the shared ownership policy, use `collaboration.spawn_agent`; honor explicit direct-work instructions.",
1069
1075
  "- Collaboration tools are direct tools and must not be called from inside `functions.exec`.",
1070
1076
  "- When the active `collaboration.spawn_agent` schema exposes `agent_type`, set it to the selected canonical role. When it does not, use a role-prefixed `task_name` plus a self-contained bounded envelope; that fallback is instruction-only.",
1071
1077
  "- Keep maximum depth 1: children do not delegate. Use one writer per mutable surface and parallelize only independent work.",
@@ -1497,6 +1503,90 @@ function readManagedModelState(path, version) {
1497
1503
  var ROOT_START = "<!-- thoth-agents:codex-root:start -->";
1498
1504
  var ROOT_END = "<!-- thoth-agents:codex-root:end -->";
1499
1505
  var MANAGED_MODEL_STATE_VERSION = 1;
1506
+ var OBSOLETE_CODEX_ROLES = ["quick", "deep"];
1507
+ var RECOVERY_FILE = ".thoth-agents-install-recovery.json";
1508
+ function lstatIfPresent(path) {
1509
+ try {
1510
+ return lstatSync(path);
1511
+ } catch (error) {
1512
+ if (error.code === "ENOENT") return void 0;
1513
+ throw error;
1514
+ }
1515
+ }
1516
+ function fingerprint(content) {
1517
+ return createHash2("sha256").update(content).digest("hex");
1518
+ }
1519
+ function assertSafeCodexRolePath(path, agentsDir) {
1520
+ const absolute = resolve2(path);
1521
+ const expectedParent = resolve2(agentsDir);
1522
+ if (dirname4(absolute) !== expectedParent)
1523
+ throw new Error(
1524
+ `Unsafe Codex role path outside managed agents directory: ${path}`
1525
+ );
1526
+ const root = parse(absolute).root;
1527
+ const ancestors = [];
1528
+ let cursor = dirname4(absolute);
1529
+ while (cursor !== root) {
1530
+ ancestors.push(cursor);
1531
+ cursor = dirname4(cursor);
1532
+ }
1533
+ ancestors.push(root);
1534
+ for (const ancestor of ancestors.reverse()) {
1535
+ const stat2 = lstatIfPresent(ancestor);
1536
+ if (!stat2) continue;
1537
+ if (stat2.isSymbolicLink())
1538
+ throw new Error(
1539
+ `Managed Codex role path has a symlinked parent: ${ancestor}`
1540
+ );
1541
+ if (!stat2.isDirectory())
1542
+ throw new Error(
1543
+ `Managed Codex role path parent is not a directory: ${ancestor}`
1544
+ );
1545
+ }
1546
+ const stat = lstatIfPresent(absolute);
1547
+ if (!stat) return;
1548
+ if (stat.isSymbolicLink())
1549
+ throw new Error(`Managed Codex role target is a symlink: ${absolute}`);
1550
+ if (!stat.isFile())
1551
+ throw new Error(
1552
+ `Managed Codex role target is not a regular file: ${absolute}`
1553
+ );
1554
+ }
1555
+ function addApplyGuard(item) {
1556
+ if (!lstatIfPresent(item.targetPath))
1557
+ return { ...item, expectedAbsent: true };
1558
+ return { ...item, expectedContent: readFileSync4(item.targetPath, "utf8") };
1559
+ }
1560
+ function assertApplyGuard(item) {
1561
+ if (item.expectedAbsent) {
1562
+ if (lstatIfPresent(item.targetPath))
1563
+ throw new Error(
1564
+ `Managed Codex target appeared after planning: ${item.targetPath}`
1565
+ );
1566
+ return;
1567
+ }
1568
+ if (item.expectedContent !== void 0) {
1569
+ if (!lstatIfPresent(item.targetPath) || readFileSync4(item.targetPath, "utf8") !== item.expectedContent)
1570
+ throw new Error(
1571
+ `Managed Codex target changed after planning: ${item.targetPath}`
1572
+ );
1573
+ }
1574
+ }
1575
+ function readInstallRecovery(path) {
1576
+ if (!lstatIfPresent(path)) return void 0;
1577
+ const raw = JSON.parse(readFileSync4(path, "utf8"));
1578
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
1579
+ throw new Error(`Corrupt Codex install recovery state: ${path}`);
1580
+ const value = raw;
1581
+ if (value.version !== 1 || !value.roles || typeof value.roles !== "object" || Array.isArray(value.roles))
1582
+ throw new Error(`Corrupt Codex install recovery state: ${path}`);
1583
+ const roles = Object.fromEntries(Object.entries(value.roles));
1584
+ if (Object.values(roles).some(
1585
+ (hash) => typeof hash !== "string" || !/^[a-f0-9]{64}$/.test(hash)
1586
+ ))
1587
+ throw new Error(`Corrupt Codex install recovery state: ${path}`);
1588
+ return { version: 1, roles };
1589
+ }
1500
1590
  function mergeManagedBlock(existing, managedBlock) {
1501
1591
  const start = existing.indexOf(ROOT_START);
1502
1592
  const end = existing.indexOf(ROOT_END);
@@ -1714,8 +1804,82 @@ function buildCodexSetupPlan(config) {
1714
1804
  ...packageRoot ? { packageRoot } : {}
1715
1805
  });
1716
1806
  const rootBlock = renderCodexRootInstructions();
1807
+ const agentsDir = dirname4(
1808
+ targets.roleAgentPaths[0]?.path ?? targets.managedModelsPath
1809
+ );
1810
+ const recoveryPath = resolve2(agentsDir, RECOVERY_FILE);
1811
+ assertSafeCodexRolePath(targets.managedModelsPath, agentsDir);
1812
+ assertSafeCodexRolePath(recoveryPath, agentsDir);
1813
+ for (const target of targets.roleAgentPaths)
1814
+ assertSafeCodexRolePath(target.path, agentsDir);
1717
1815
  const managedModelState2 = readManagedModelState2(targets.managedModelsPath);
1816
+ const recovery = readInstallRecovery(recoveryPath);
1718
1817
  const nextManagedModelState = emptyManagedModelState2();
1818
+ const roleItems = targets.roleAgentPaths.map(
1819
+ (target) => addApplyGuard({
1820
+ kind: "role-subagent-toml",
1821
+ action: "write-role-toml",
1822
+ targetPath: target.path,
1823
+ description: `Materialize Codex role subagent ${target.role}.`,
1824
+ requiresBackup: Boolean(lstatIfPresent(target.path)),
1825
+ role: target.role,
1826
+ renderedModel: parseRoleTomlModel(
1827
+ roleArtifactContent(target.role, render.artifacts)
1828
+ ),
1829
+ content: resolveRoleTomlContent({
1830
+ renderedContent: roleArtifactContent(target.role, render.artifacts),
1831
+ targetPath: target.path,
1832
+ state: managedModelState2,
1833
+ nextState: nextManagedModelState,
1834
+ reset: config.reset
1835
+ })
1836
+ })
1837
+ );
1838
+ const recoveryRoles = Object.fromEntries(
1839
+ roleItems.map((item) => [
1840
+ roleManagedModelStateKey(item.targetPath),
1841
+ fingerprint(item.content ?? "")
1842
+ ])
1843
+ );
1844
+ for (const item of roleItems) {
1845
+ if (item.role !== "worker" || !lstatIfPresent(item.targetPath)) continue;
1846
+ const key = roleManagedModelStateKey(item.targetPath);
1847
+ const ownedByLedger = key in managedModelState2.models;
1848
+ const contentHash = fingerprint(readFileSync4(item.targetPath, "utf8"));
1849
+ const recoverable = recovery?.roles[key] === contentHash && recoveryRoles[key] === contentHash;
1850
+ if (!ownedByLedger && !recoverable)
1851
+ throw new Error(
1852
+ `Existing Worker Codex role lacks thoth-agents ownership state or a trustworthy recovery fingerprint: ${item.targetPath}. Move or remove it explicitly before retrying.`
1853
+ );
1854
+ }
1855
+ if (recovery) {
1856
+ const expectedKeys = Object.keys(recoveryRoles).sort();
1857
+ if (JSON.stringify(Object.keys(recovery.roles).sort()) !== JSON.stringify(expectedKeys) || expectedKeys.some((key) => recovery.roles[key] !== recoveryRoles[key]))
1858
+ throw new Error(
1859
+ `Codex install recovery state does not match this package: ${recoveryPath}`
1860
+ );
1861
+ }
1862
+ const obsoleteItems = OBSOLETE_CODEX_ROLES.flatMap(
1863
+ (role) => {
1864
+ const targetPath = resolve2(agentsDir, `thoth-agents-${role}.toml`);
1865
+ assertSafeCodexRolePath(targetPath, agentsDir);
1866
+ if (!lstatIfPresent(targetPath)) return [];
1867
+ const key = roleManagedModelStateKey(targetPath);
1868
+ if (!(key in managedModelState2.models))
1869
+ throw new Error(
1870
+ `Obsolete Codex role lacks thoth-agents ownership state and was preserved: ${targetPath}. Remove it explicitly before retrying.`
1871
+ );
1872
+ return [
1873
+ addApplyGuard({
1874
+ kind: "role-subagent-toml",
1875
+ action: "retire-obsolete-role-toml",
1876
+ targetPath,
1877
+ description: `Retire attributable obsolete Codex role ${role}.`,
1878
+ requiresBackup: false
1879
+ })
1880
+ ];
1881
+ }
1882
+ );
1719
1883
  const items = [
1720
1884
  {
1721
1885
  kind: "root-instructions",
@@ -1725,34 +1889,24 @@ function buildCodexSetupPlan(config) {
1725
1889
  requiresBackup: true,
1726
1890
  content: rootBlock
1727
1891
  },
1728
- ...targets.roleAgentPaths.map(
1729
- (target) => ({
1730
- kind: "role-subagent-toml",
1731
- action: "write-role-toml",
1732
- targetPath: target.path,
1733
- description: `Materialize Codex role subagent ${target.role}.`,
1734
- requiresBackup: existsSync4(target.path),
1735
- role: target.role,
1736
- renderedModel: parseRoleTomlModel(
1737
- roleArtifactContent(target.role, render.artifacts)
1738
- ),
1739
- content: resolveRoleTomlContent({
1740
- renderedContent: roleArtifactContent(target.role, render.artifacts),
1741
- targetPath: target.path,
1742
- state: managedModelState2,
1743
- nextState: nextManagedModelState,
1744
- reset: config.reset
1745
- })
1746
- })
1747
- ),
1748
- {
1892
+ addApplyGuard({
1893
+ kind: "install-recovery",
1894
+ action: "write-install-recovery",
1895
+ targetPath: recoveryPath,
1896
+ description: "Persist exact role fingerprints for interrupted-install recovery.",
1897
+ requiresBackup: Boolean(lstatIfPresent(recoveryPath)),
1898
+ content: stableJson3({ version: 1, roles: recoveryRoles })
1899
+ }),
1900
+ ...roleItems,
1901
+ ...obsoleteItems,
1902
+ addApplyGuard({
1749
1903
  kind: "managed-model-state",
1750
1904
  action: "write-managed-model-state",
1751
1905
  targetPath: targets.managedModelsPath,
1752
1906
  description: "Record thoth-agents-managed Codex role model ownership state.",
1753
1907
  requiresBackup: existsSync4(targets.managedModelsPath),
1754
1908
  content: stableJson3(nextManagedModelState)
1755
- },
1909
+ }),
1756
1910
  {
1757
1911
  kind: "user-config",
1758
1912
  action: "merge-toml",
@@ -1802,8 +1956,32 @@ function applyCodexSetup(plan) {
1802
1956
  ]);
1803
1957
  if (plan.dryRun) return { success: true, changed, diagnostics };
1804
1958
  try {
1959
+ const stateItem = plan.items.find(
1960
+ (item) => item.action === "write-managed-model-state"
1961
+ );
1962
+ if (!stateItem)
1963
+ throw new Error("Codex managed model state item is missing from plan.");
1964
+ const agentsDir = dirname4(stateItem.targetPath);
1965
+ for (const item of plan.items) {
1966
+ if (item.action === "write-role-toml" || item.action === "retire-obsolete-role-toml" || item.action === "write-install-recovery" || item.action === "write-managed-model-state") {
1967
+ assertSafeCodexRolePath(item.targetPath, agentsDir);
1968
+ assertApplyGuard(item);
1969
+ }
1970
+ }
1805
1971
  for (const item of plan.items) {
1806
1972
  if (item.action === "diagnose-only") continue;
1973
+ if (item.action === "retire-obsolete-role-toml") {
1974
+ const agentsDir2 = dirname4(item.targetPath);
1975
+ assertSafeCodexRolePath(item.targetPath, agentsDir2);
1976
+ assertApplyGuard(item);
1977
+ assertSafeCodexRolePath(stateItem.targetPath, agentsDir2);
1978
+ assertApplyGuard(stateItem);
1979
+ if (existsSync4(item.targetPath)) {
1980
+ rmSync(item.targetPath);
1981
+ changed.push(item.targetPath);
1982
+ }
1983
+ continue;
1984
+ }
1807
1985
  if (item.action === "merge-toml") {
1808
1986
  const result = writeCodexConfigMerge({
1809
1987
  configPath: item.targetPath,
@@ -1816,12 +1994,29 @@ function applyCodexSetup(plan) {
1816
1994
  continue;
1817
1995
  }
1818
1996
  if (item.content === void 0) continue;
1997
+ if (item.action === "write-role-toml" || item.action === "write-install-recovery" || item.action === "write-managed-model-state") {
1998
+ assertSafeCodexRolePath(item.targetPath, agentsDir);
1999
+ assertApplyGuard(item);
2000
+ }
1819
2001
  const content = item.action === "merge-managed-block" ? mergeManagedBlock(
1820
2002
  existsSync4(item.targetPath) ? readFileSync4(item.targetPath, "utf8") : "",
1821
2003
  item.content
1822
2004
  ) : item.content;
1823
2005
  if (writeTextWithBackup(item.targetPath, content))
1824
2006
  changed.push(item.targetPath);
2007
+ if (item.action === "write-managed-model-state") {
2008
+ const recoveryItem = plan.items.find(
2009
+ (candidate) => candidate.action === "write-install-recovery"
2010
+ );
2011
+ if (!recoveryItem?.content)
2012
+ throw new Error("Codex install recovery item is missing from plan.");
2013
+ assertSafeCodexRolePath(recoveryItem.targetPath, agentsDir);
2014
+ if (!lstatIfPresent(recoveryItem.targetPath) || readFileSync4(recoveryItem.targetPath, "utf8") !== recoveryItem.content)
2015
+ throw new Error(
2016
+ `Codex install recovery state changed before completion: ${recoveryItem.targetPath}`
2017
+ );
2018
+ rmSync(recoveryItem.targetPath);
2019
+ }
1825
2020
  }
1826
2021
  return { success: true, changed, diagnostics: uniqueMessages(diagnostics) };
1827
2022
  } catch (error) {
@@ -1841,7 +2036,7 @@ import { existsSync as existsSync7 } from "fs";
1841
2036
  import { fileURLToPath as fileURLToPath3 } from "url";
1842
2037
 
1843
2038
  // src/harness/writers/claude-code-plugin-package.ts
1844
- import { createHash as createHash2 } from "crypto";
2039
+ import { createHash as createHash3 } from "crypto";
1845
2040
  var CLAUDE_PLUGIN_MANIFEST_FIELDS = [
1846
2041
  "name",
1847
2042
  "version",
@@ -1858,7 +2053,7 @@ function stableJson4(value) {
1858
2053
  `;
1859
2054
  }
1860
2055
  function sha256Hash(content) {
1861
- return `sha256:${createHash2("sha256").update(content).digest("hex")}`;
2056
+ return `sha256:${createHash3("sha256").update(content).digest("hex")}`;
1862
2057
  }
1863
2058
  function withoutProviderAsset(artifact) {
1864
2059
  if (!normalizePath2(artifact.path).endsWith(".mcp.json")) return artifact;
@@ -1952,14 +2147,12 @@ var CLAUDE_CODE_SUBAGENT_DEFAULT_MODELS = {
1952
2147
  librarian: "sonnet",
1953
2148
  oracle: "opus",
1954
2149
  designer: "sonnet",
1955
- quick: "haiku",
1956
- deep: "sonnet"
2150
+ worker: "sonnet"
1957
2151
  };
1958
2152
  var CLAUDE_CODE_SUBAGENT_DEFAULT_EFFORTS = {
1959
2153
  explorer: "low",
1960
- quick: "low",
1961
2154
  designer: "medium",
1962
- deep: "medium",
2155
+ worker: "medium",
1963
2156
  librarian: "high",
1964
2157
  oracle: "high"
1965
2158
  };
@@ -2013,7 +2206,7 @@ function renderClaudeCodeRootInstructions(config) {
2013
2206
  rootPrompt,
2014
2207
  "<claude-code-runtime>",
2015
2208
  "- You are the Claude Code adaptive root activated by plugin settings.json.",
2016
- `- Delegate only for net gain through Agent with \`subagent_type\` set to one of these plugin-namespaced specialists: ${specialists}. Always keep the ${CLAUDE_CODE_SUBAGENT_NAMESPACE}: prefix.`,
2209
+ `- When delegation is selected, use Agent with \`subagent_type\`: ${specialists}. Honor shared ownership and explicit direct-work instructions. Keep the ${CLAUDE_CODE_SUBAGENT_NAMESPACE}: prefix.`,
2017
2210
  "- Subagents cannot delegate further. Parallelize only independent work and maintain one writer per mutable surface.",
2018
2211
  "- Read-only roles deny Write and Edit while retaining other inherited tools, including MCP tools. Coordination-agent path scope remains instruction-level.",
2019
2212
  "- Use AskUserQuestion only for blocking material choices and TodoWrite only for genuine multi-step progress.",
@@ -2056,7 +2249,7 @@ function createPluginManifest(context2) {
2056
2249
  return {
2057
2250
  name: "thoth-agents",
2058
2251
  version: readRootPackageVersion2(context2),
2059
- description: "Adaptive multi-harness agent pack with seven roles and a runtime-autonomous Spec Kit-compatible SDD bundle for Claude Code.",
2252
+ description: "Adaptive multi-harness agent pack with six roles and proportional SDD governance for Claude Code.",
2060
2253
  author: { name: "thoth-agents" }
2061
2254
  };
2062
2255
  }
@@ -2089,7 +2282,7 @@ function renderOrchestratorArtifact(config) {
2089
2282
  );
2090
2283
  const content = renderClaudeCodeSubagent({
2091
2284
  name: "orchestrator",
2092
- description: orchestrator?.responsibility ?? "Adaptive root coordinator for direct work, SDD routing, and specialist dispatch.",
2285
+ description: orchestrator?.responsibility ?? "Adaptive root coordinator for human agreement, SDD classification, and specialist dispatch.",
2093
2286
  model: "inherit",
2094
2287
  instructions: renderClaudeCodeRootInstructions(config)
2095
2288
  });
@@ -2146,8 +2339,7 @@ var CLAUDE_CODE_ROLE_NAMES = [
2146
2339
  "librarian",
2147
2340
  "oracle",
2148
2341
  "designer",
2149
- "quick",
2150
- "deep"
2342
+ "worker"
2151
2343
  ];
2152
2344
 
2153
2345
  // src/cli/claude-code-install.ts
@@ -2453,11 +2645,11 @@ import {
2453
2645
  mkdirSync as mkdirSync3,
2454
2646
  readFileSync as readFileSync5,
2455
2647
  renameSync as renameSync2,
2456
- rmSync,
2648
+ rmSync as rmSync2,
2457
2649
  writeFileSync as writeFileSync3
2458
2650
  } from "fs";
2459
2651
  import { homedir as homedir2 } from "os";
2460
- import { dirname as dirname4, join as join2 } from "path";
2652
+ import { dirname as dirname5, join as join2 } from "path";
2461
2653
  var INSTALL_LEDGER_SCHEMA_VERSION = 1;
2462
2654
  var INSTALL_HARNESSES = /* @__PURE__ */ new Set([
2463
2655
  "opencode",
@@ -2537,7 +2729,7 @@ function recordCompletedInstall(options) {
2537
2729
  const repairedInvalidState = current.status === "invalid";
2538
2730
  const backupPath = repairedInvalidState ? `${path}.bak` : void 0;
2539
2731
  try {
2540
- mkdirSync3(dirname4(path), { recursive: true });
2732
+ mkdirSync3(dirname5(path), { recursive: true });
2541
2733
  writeFileSync3(temporaryPath, `${JSON.stringify(ledger, null, 2)}
2542
2734
  `);
2543
2735
  if (backupPath) copyFileSync3(path, backupPath);
@@ -2551,7 +2743,7 @@ function recordCompletedInstall(options) {
2551
2743
  };
2552
2744
  } catch (error) {
2553
2745
  try {
2554
- rmSync(temporaryPath, { recursive: true, force: true });
2746
+ rmSync2(temporaryPath, { recursive: true, force: true });
2555
2747
  } catch {
2556
2748
  }
2557
2749
  return {
@@ -2617,6 +2809,7 @@ function parseProviderSetupResult(stdout, expectedHarness) {
2617
2809
  }
2618
2810
  function defaultCommandExecutor2(command, args, options) {
2619
2811
  const result = spawnSync2(command, [...args], {
2812
+ windowsHide: true,
2620
2813
  cwd: options.cwd,
2621
2814
  encoding: "utf8",
2622
2815
  stdio: ["ignore", "pipe", "pipe"],
@@ -2902,7 +3095,10 @@ function installRequiredSkill(skill, harness, options = {}) {
2902
3095
  options
2903
3096
  );
2904
3097
  try {
2905
- const result = spawnSync3(command, args, { stdio: "inherit" });
3098
+ const result = spawnSync3(command, args, {
3099
+ windowsHide: true,
3100
+ stdio: "inherit"
3101
+ });
2906
3102
  if (result.status === 0) {
2907
3103
  return {
2908
3104
  skill,
@@ -3614,13 +3810,13 @@ import { basename as basename2 } from "path";
3614
3810
  // src/cli/codex-plugin-install.ts
3615
3811
  import { spawnSync as spawnSync4 } from "child_process";
3616
3812
  import {
3617
- lstatSync,
3813
+ lstatSync as lstatSync2,
3618
3814
  readdirSync,
3619
3815
  readFileSync as readFileSync6,
3620
3816
  realpathSync,
3621
- rmSync as rmSync2
3817
+ rmSync as rmSync3
3622
3818
  } from "fs";
3623
- import { isAbsolute, join as join4, relative, resolve as resolve2, sep } from "path";
3819
+ import { isAbsolute, join as join4, relative, resolve as resolve3, sep } from "path";
3624
3820
  var PLUGIN_NAME2 = "thoth-agents";
3625
3821
  var MARKETPLACE_NAME2 = "thoth-plugins";
3626
3822
  var MARKETPLACE_SOURCE2 = "https://github.com/EremesNG/thoth-plugins.git";
@@ -3694,9 +3890,9 @@ function parseManagerRecord(content) {
3694
3890
  function normalizeMarketplaceSource2(value) {
3695
3891
  return value.trim().replace(/#.*$/u, "").replace(/^git@github\.com:/i, "").replace(/^(?:https?|ssh):\/\/(?:git@)?github\.com\//i, "").replace(/^github\.com\//i, "").replace(/\/?\.git\/?$/i, "").replace(/\/$/, "").toLowerCase();
3696
3892
  }
3697
- function lstatIfPresent(path) {
3893
+ function lstatIfPresent2(path) {
3698
3894
  try {
3699
- return lstatSync(path);
3895
+ return lstatSync2(path);
3700
3896
  } catch (error) {
3701
3897
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
3702
3898
  return null;
@@ -3709,7 +3905,7 @@ function strictDescendant(base, candidate) {
3709
3905
  return suffix !== "" && suffix !== ".." && !suffix.startsWith(`..${sep}`) && !isAbsolute(suffix);
3710
3906
  }
3711
3907
  function parseObjectFile(path, label) {
3712
- const file = lstatIfPresent(path);
3908
+ const file = lstatIfPresent2(path);
3713
3909
  if (!file || file.isSymbolicLink() || !file.isFile()) {
3714
3910
  throw new Error(`${label} must be a regular non-link file`);
3715
3911
  }
@@ -3732,7 +3928,7 @@ function validateCacheManifest(root) {
3732
3928
  );
3733
3929
  }
3734
3930
  const productRoot = join4(root, PLUGIN_NAME2);
3735
- const product = lstatIfPresent(productRoot);
3931
+ const product = lstatIfPresent2(productRoot);
3736
3932
  if (!product || product.isSymbolicLink() || !product.isDirectory()) {
3737
3933
  throw new Error(`Legacy cache product root ${productRoot} is unsafe`);
3738
3934
  }
@@ -3742,7 +3938,7 @@ function validateCacheManifest(root) {
3742
3938
  }
3743
3939
  for (const entry of versions) {
3744
3940
  const versionRoot = join4(productRoot, entry.name);
3745
- const version = lstatIfPresent(versionRoot);
3941
+ const version = lstatIfPresent2(versionRoot);
3746
3942
  if (!entry.isDirectory() || !version || version.isSymbolicLink() || !version.isDirectory()) {
3747
3943
  throw new Error(`Legacy cache version root ${versionRoot} is unsafe`);
3748
3944
  }
@@ -3783,22 +3979,22 @@ function validateSnapshotManifest(root, marketplaceName) {
3783
3979
  }
3784
3980
  }
3785
3981
  function validateLegacyRoot(codexHome, definition, expectedRealPath) {
3786
- const path = resolve2(codexHome, ...definition.relativePath.split("/"));
3982
+ const path = resolve3(codexHome, ...definition.relativePath.split("/"));
3787
3983
  if (!strictDescendant(codexHome, path)) {
3788
3984
  throw new Error(
3789
3985
  `Legacy cleanup target escapes CODEX_HOME: ${definition.relativePath}`
3790
3986
  );
3791
3987
  }
3792
- const target = lstatIfPresent(path);
3988
+ const target = lstatIfPresent2(path);
3793
3989
  if (!target) return null;
3794
- const home = lstatIfPresent(codexHome);
3990
+ const home = lstatIfPresent2(codexHome);
3795
3991
  if (!home || !home.isDirectory()) {
3796
3992
  throw new Error(`Resolved CODEX_HOME is not a directory: ${codexHome}`);
3797
3993
  }
3798
3994
  let boundary = codexHome;
3799
3995
  for (const segment of definition.relativePath.split("/")) {
3800
3996
  boundary = join4(boundary, segment);
3801
- const value = lstatIfPresent(boundary);
3997
+ const value = lstatIfPresent2(boundary);
3802
3998
  if (!value || value.isSymbolicLink() || !value.isDirectory()) {
3803
3999
  throw new Error(
3804
4000
  `Legacy cleanup boundary is not a real directory: ${boundary}`
@@ -3831,7 +4027,7 @@ function preflightLegacyRoots(codexHomeInput) {
3831
4027
  if (typeof codexHomeInput !== "string" || codexHomeInput.trim() === "") {
3832
4028
  throw new Error("CODEX_HOME could not be resolved");
3833
4029
  }
3834
- const codexHome = resolve2(codexHomeInput);
4030
+ const codexHome = resolve3(codexHomeInput);
3835
4031
  const roots = LEGACY_ROOTS.map(
3836
4032
  (definition) => validateLegacyRoot(codexHome, definition)
3837
4033
  ).filter((root) => root !== null);
@@ -3966,7 +4162,7 @@ function legacyManagerClean(inspection) {
3966
4162
  }
3967
4163
  function legacyRootsAbsent(codexHome) {
3968
4164
  return LEGACY_ROOTS.every(
3969
- ({ relativePath }) => !lstatIfPresent(resolve2(codexHome, ...relativePath.split("/")))
4165
+ ({ relativePath }) => !lstatIfPresent2(resolve3(codexHome, ...relativePath.split("/")))
3970
4166
  );
3971
4167
  }
3972
4168
  function legacyPluginCommandItem(pluginId, projectRoot) {
@@ -4194,11 +4390,11 @@ function applyCodexPluginSetup(plan) {
4194
4390
  error: `Central ${PLUGIN_ID2} remains installed, but the fixed legacy cleanup plan is invalid; close Codex and retry.`
4195
4391
  };
4196
4392
  }
4197
- const exactPath = resolve2(
4393
+ const exactPath = resolve3(
4198
4394
  plan.codexHome,
4199
4395
  ...definition.relativePath.split("/")
4200
4396
  );
4201
- if (resolve2(root.path) !== exactPath || resolve2(item.targetPath) !== exactPath || root.kind !== definition.kind || root.marketplaceName !== definition.marketplaceName) {
4397
+ if (resolve3(root.path) !== exactPath || resolve3(item.targetPath) !== exactPath || root.kind !== definition.kind || root.marketplaceName !== definition.marketplaceName) {
4202
4398
  return {
4203
4399
  success: false,
4204
4400
  changed,
@@ -4206,7 +4402,7 @@ function applyCodexPluginSetup(plan) {
4206
4402
  error: `Central ${PLUGIN_ID2} remains installed, but the fixed legacy cleanup target changed; close Codex and retry.`
4207
4403
  };
4208
4404
  }
4209
- if (!lstatIfPresent(exactPath)) continue;
4405
+ if (!lstatIfPresent2(exactPath)) continue;
4210
4406
  try {
4211
4407
  const validated = validateLegacyRoot(
4212
4408
  plan.codexHome,
@@ -4214,8 +4410,8 @@ function applyCodexPluginSetup(plan) {
4214
4410
  root.realPath
4215
4411
  );
4216
4412
  if (!validated) continue;
4217
- rmSync2(validated.path, { recursive: true, force: false });
4218
- if (lstatIfPresent(validated.path)) {
4413
+ rmSync3(validated.path, { recursive: true, force: false });
4414
+ if (lstatIfPresent2(validated.path)) {
4219
4415
  throw new Error(
4220
4416
  `Legacy cleanup target still exists: ${root.relativePath}`
4221
4417
  );
@@ -4440,6 +4636,14 @@ function classifyItem(item) {
4440
4636
  if (item.action === "write-managed-model-state") {
4441
4637
  return managedModelState(item);
4442
4638
  }
4639
+ if (item.action === "write-install-recovery") {
4640
+ if (!existsSync8(item.targetPath))
4641
+ return { state: "installed", observed: "no interrupted install" };
4642
+ return {
4643
+ state: "drift",
4644
+ observed: "recoverable interrupted install journal present"
4645
+ };
4646
+ }
4443
4647
  if (item.action === "merge-toml") return userConfigState(item);
4444
4648
  return contentState(item);
4445
4649
  }
@@ -4547,6 +4751,14 @@ function getCodexStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
4547
4751
  code: "codex-diagnostic"
4548
4752
  }));
4549
4753
  diagnostics.push(...requiredSkills.diagnostics);
4754
+ if (classified.some(
4755
+ ({ item, state: state2 }) => item.action === "write-install-recovery" && state2 === "drift"
4756
+ ))
4757
+ diagnostics.push({
4758
+ severity: "important",
4759
+ code: "codex-interrupted-install-recoverable",
4760
+ message: "An exact thoth-agents recovery journal indicates an interrupted Codex install; run sync or update to resume safely."
4761
+ });
4550
4762
  return {
4551
4763
  harness: "codex",
4552
4764
  displayName: CODEX_DISPLAY_NAME,
@@ -5079,7 +5291,7 @@ function applyCodexPlan(plan) {
5079
5291
  }
5080
5292
 
5081
5293
  // src/cli/operations/opencode.ts
5082
- import { createHash as createHash3 } from "crypto";
5294
+ import { createHash as createHash4 } from "crypto";
5083
5295
  import { existsSync as existsSync10 } from "fs";
5084
5296
  import { join as join6 } from "path";
5085
5297
 
@@ -5089,7 +5301,8 @@ var OPENAI_GPT_RUNTIME_VARIANTS = /* @__PURE__ */ new Set([
5089
5301
  "low",
5090
5302
  "medium",
5091
5303
  "high",
5092
- "xhigh"
5304
+ "xhigh",
5305
+ "max"
5093
5306
  ]);
5094
5307
  function resolveOpenCodeEffort(input) {
5095
5308
  if (!input.effort || input.effort.kind === "inherit") {
@@ -5117,16 +5330,16 @@ function resolveOpenCodeEffort(input) {
5117
5330
  import {
5118
5331
  cpSync,
5119
5332
  existsSync as existsSync9,
5120
- lstatSync as lstatSync2,
5333
+ lstatSync as lstatSync3,
5121
5334
  mkdirSync as mkdirSync4,
5122
5335
  mkdtempSync,
5123
5336
  readFileSync as readFileSync8,
5124
5337
  renameSync as renameSync3,
5125
- rmSync as rmSync3,
5338
+ rmSync as rmSync4,
5126
5339
  statSync
5127
5340
  } from "fs";
5128
5341
  import { homedir as homedir4 } from "os";
5129
- import { dirname as dirname5, join as join5 } from "path";
5342
+ import { dirname as dirname6, join as join5 } from "path";
5130
5343
  import { fileURLToPath as fileURLToPath4 } from "url";
5131
5344
 
5132
5345
  // src/harness/core/owned-skills.ts
@@ -5170,7 +5383,7 @@ function inspectPiPackageSkill(entry) {
5170
5383
  state: "missing",
5171
5384
  message: `Package-declared Pi skill directory is missing: ${entry.name}`
5172
5385
  };
5173
- const root = lstatSync2(entry.sourcePath);
5386
+ const root = lstatSync3(entry.sourcePath);
5174
5387
  if (!root.isDirectory() || root.isSymbolicLink())
5175
5388
  return {
5176
5389
  name: entry.name,
@@ -5184,7 +5397,7 @@ function inspectPiPackageSkill(entry) {
5184
5397
  state: "missing",
5185
5398
  message: `Package-declared Pi skill contract is missing: ${entry.name}`
5186
5399
  };
5187
- const contract = lstatSync2(contractPath);
5400
+ const contract = lstatSync3(contractPath);
5188
5401
  if (!contract.isFile() || contract.isSymbolicLink())
5189
5402
  return {
5190
5403
  name: entry.name,
@@ -5202,7 +5415,7 @@ function inspectPiPackageSkill(entry) {
5202
5415
  return void 0;
5203
5416
  }
5204
5417
  function resolveOwnedSkillPackageRoot(moduleUrl) {
5205
- const packageRoot = findPackageRoot(dirname5(fileURLToPath4(moduleUrl)));
5418
+ const packageRoot = findPackageRoot(dirname6(fileURLToPath4(moduleUrl)));
5206
5419
  if (!packageRoot) {
5207
5420
  throw new Error(
5208
5421
  "Unable to locate the thoth-agents package root for bundled owned skills."
@@ -5244,7 +5457,7 @@ function validateCanonicalSkill(entry) {
5244
5457
  function installOwnedSkills(skills) {
5245
5458
  const firstSkill = skills[0];
5246
5459
  if (!firstSkill) return;
5247
- const globalRoot = dirname5(firstSkill.destinationPath);
5460
+ const globalRoot = dirname6(firstSkill.destinationPath);
5248
5461
  mkdirSync4(globalRoot, { recursive: true });
5249
5462
  const transactionRoot = mkdtempSync(join5(globalRoot, ".thoth-agents-owned-"));
5250
5463
  const stagedRoot = join5(transactionRoot, "staged");
@@ -5269,7 +5482,7 @@ function installOwnedSkills(skills) {
5269
5482
  renameSync3(stagedPath, skill.destinationPath);
5270
5483
  } catch (error) {
5271
5484
  if (existsSync9(skill.destinationPath)) {
5272
- rmSync3(skill.destinationPath, { recursive: true, force: true });
5485
+ rmSync4(skill.destinationPath, { recursive: true, force: true });
5273
5486
  }
5274
5487
  if (hadExistingDestination && existsSync9(backupPath)) {
5275
5488
  renameSync3(backupPath, skill.destinationPath);
@@ -5277,11 +5490,11 @@ function installOwnedSkills(skills) {
5277
5490
  throw error;
5278
5491
  }
5279
5492
  if (existsSync9(backupPath)) {
5280
- rmSync3(backupPath, { recursive: true, force: true });
5493
+ rmSync4(backupPath, { recursive: true, force: true });
5281
5494
  }
5282
5495
  }
5283
5496
  } finally {
5284
- rmSync3(transactionRoot, { recursive: true, force: true });
5497
+ rmSync4(transactionRoot, { recursive: true, force: true });
5285
5498
  }
5286
5499
  }
5287
5500
  function syncOpenCodeOwnedSkills(options = {}) {
@@ -5435,7 +5648,7 @@ function canonicalDigest(value) {
5435
5648
  const serialized = canonicalData(value, /* @__PURE__ */ new Set());
5436
5649
  return {
5437
5650
  ok: true,
5438
- digest: createHash3("sha256").update(serialized).digest("hex")
5651
+ digest: createHash4("sha256").update(serialized).digest("hex")
5439
5652
  };
5440
5653
  } catch {
5441
5654
  return { ok: false };
@@ -5559,7 +5772,7 @@ function targetForLiteConfig(state) {
5559
5772
  path: getExistingLiteConfigPath(),
5560
5773
  label: "thoth-agents config",
5561
5774
  ...state ? { state } : {},
5562
- expected: `seven-role ${OPENAI_PRESET} roster`
5775
+ expected: `six-role ${OPENAI_PRESET} roster`
5563
5776
  };
5564
5777
  }
5565
5778
  function titleCaseSkillName(value) {
@@ -6037,7 +6250,7 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
6037
6250
  harness: "opencode",
6038
6251
  displayName: "OpenCode",
6039
6252
  state: "drift",
6040
- summary: selectedNamedPreset ? "A valid named OpenCode preset is active outside the managed seven-role roster." : "thoth-agents config does not match the expected seven-role roster.",
6253
+ summary: selectedNamedPreset ? "A valid named OpenCode preset is active outside the managed six-role roster." : "thoth-agents config does not match the expected six-role roster.",
6041
6254
  targets: [
6042
6255
  { ...mainTarget, state: "installed" },
6043
6256
  { ...liteTarget, state: "drift" },
@@ -6241,7 +6454,7 @@ function buildOpenCodeCompletePlan(action, context2) {
6241
6454
  backup: defaultBackup(getExistingConfigPath())
6242
6455
  },
6243
6456
  {
6244
- title: "Write thoth-agents seven-role config",
6457
+ title: "Write thoth-agents six-role config",
6245
6458
  target: targetForLiteConfig(),
6246
6459
  preview: JSON.stringify(generatedConfig, null, 2),
6247
6460
  backup: defaultBackup(getExistingLiteConfigPath())
@@ -6342,7 +6555,7 @@ function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
6342
6555
  backup: defaultBackup(getExistingConfigPath())
6343
6556
  },
6344
6557
  {
6345
- title: "Write thoth-agents seven-role config",
6558
+ title: "Write thoth-agents six-role config",
6346
6559
  target: targetForLiteConfig(),
6347
6560
  preview: JSON.stringify(generatedConfig, null, 2),
6348
6561
  backup: defaultBackup(litePath)
@@ -6865,7 +7078,7 @@ function applyOpenCodePlan(plan) {
6865
7078
  }
6866
7079
  changedTargets.push({
6867
7080
  ...targetForLiteConfig("installed"),
6868
- observed: "seven-role roster written"
7081
+ observed: "six-role roster written"
6869
7082
  });
6870
7083
  if (existsSync10(`${liteResult.configPath}.bak`)) {
6871
7084
  backups.push({
@@ -6957,56 +7170,289 @@ function applyOpenCodePlan(plan) {
6957
7170
 
6958
7171
  // src/cli/operations/pi.ts
6959
7172
  import { spawnSync as spawnSync7 } from "child_process";
6960
- import { createHash as createHash5 } from "crypto";
6961
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
6962
- import { basename as basename3, isAbsolute as isAbsolute4, join as join12 } from "path";
7173
+ import { createHash as createHash6 } from "crypto";
7174
+ import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
7175
+ import { basename as basename4, dirname as dirname11, isAbsolute as isAbsolute5, join as join13 } from "path";
6963
7176
 
6964
- // src/cli/pi-effort.ts
6965
- var PI_EFFORT_VALUES = [
6966
- "off",
6967
- "minimal",
6968
- "low",
6969
- "medium",
6970
- "high",
6971
- "xhigh"
6972
- ];
6973
- var PI_EFFORTS = new Set(PI_EFFORT_VALUES);
6974
- function resolvePiEffort(input) {
6975
- if (!input.effort || input.effort.kind === "inherit") {
6976
- return { ok: true, effort: void 0 };
7177
+ // src/cli/pi-external-package.ts
7178
+ import { readFileSync as readFileSync9, realpathSync as realpathSync2 } from "fs";
7179
+ import { isAbsolute as isAbsolute2, join as join7 } from "path";
7180
+ import { satisfies, valid } from "semver";
7181
+ function npmPackageName(source) {
7182
+ if (!source.startsWith("npm:")) return void 0;
7183
+ const spec = source.slice("npm:".length);
7184
+ const separator = spec.lastIndexOf("@");
7185
+ if (separator <= 0) return spec || void 0;
7186
+ return spec.slice(0, separator);
7187
+ }
7188
+ function installedPackageName(path) {
7189
+ if (!path) return void 0;
7190
+ try {
7191
+ const manifest = JSON.parse(
7192
+ readFileSync9(join7(path, "package.json"), "utf8")
7193
+ );
7194
+ if (typeof manifest === "object" && manifest !== null && !Array.isArray(manifest)) {
7195
+ const { name } = manifest;
7196
+ if (typeof name === "string") return name;
7197
+ }
7198
+ } catch {
7199
+ return void 0;
6977
7200
  }
6978
- const effort = input.effort.value;
6979
- if (!PI_EFFORTS.has(effort)) {
7201
+ return void 0;
7202
+ }
7203
+ function piExternalSourceMatches(candidate, expectedSource) {
7204
+ if (candidate.source === expectedSource) return true;
7205
+ if (!isAbsolute2(expectedSource) || !candidate.installedPath) return false;
7206
+ try {
7207
+ return realpathSync2(candidate.installedPath) === realpathSync2(expectedSource);
7208
+ } catch {
7209
+ return false;
7210
+ }
7211
+ }
7212
+ function inspectPiExternalPackage(packages, spec, requireManagedSource = true) {
7213
+ const matching = packages.filter(
7214
+ ({ source, installedPath }) => source === spec.source || npmPackageName(source) === spec.packageName || installedPackageName(installedPath) === spec.packageName
7215
+ );
7216
+ if (matching.some(({ scope }) => scope === "project"))
6980
7217
  return {
6981
- ok: false,
6982
- message: `Pi does not support effort ${effort}; expected ${PI_EFFORT_VALUES.join(", ")}, or inherit.`
7218
+ state: "drift",
7219
+ reason: "project package shadows the required global package"
6983
7220
  };
6984
- }
6985
- if (input.availableEfforts !== void 0 && !input.availableEfforts.includes(effort)) {
7221
+ const candidates = matching.filter(({ scope }) => scope === "user");
7222
+ if (candidates.length === 0)
7223
+ return { state: "missing", reason: "package is not configured globally" };
7224
+ if (candidates.length !== 1)
7225
+ return { state: "drift", reason: "package configuration is ambiguous" };
7226
+ const candidate = candidates[0];
7227
+ if (!candidate?.installedPath)
6986
7228
  return {
6987
- ok: false,
6988
- message: `Effort ${effort} is not available for ${input.model} in the selected model catalog entry.`
7229
+ state: "drift",
7230
+ source: candidate?.source,
7231
+ reason: "Pi did not report an installed package directory"
7232
+ };
7233
+ let manifest;
7234
+ try {
7235
+ manifest = JSON.parse(
7236
+ readFileSync9(join7(candidate.installedPath, "package.json"), "utf8")
7237
+ );
7238
+ } catch {
7239
+ return {
7240
+ state: "drift",
7241
+ source: candidate.source,
7242
+ installedPath: candidate.installedPath,
7243
+ reason: "installed package manifest is missing or malformed"
6989
7244
  };
6990
7245
  }
6991
- return { ok: true, effort };
7246
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
7247
+ return {
7248
+ state: "drift",
7249
+ source: candidate.source,
7250
+ installedPath: candidate.installedPath,
7251
+ reason: "installed package manifest is not an object"
7252
+ };
7253
+ const { name, version } = manifest;
7254
+ if (name !== spec.packageName)
7255
+ return {
7256
+ state: "drift",
7257
+ source: candidate.source,
7258
+ installedPath: candidate.installedPath,
7259
+ reason: `installed package name does not match ${spec.packageName}`
7260
+ };
7261
+ if (typeof version !== "string" || valid(version) === null)
7262
+ return {
7263
+ state: "drift",
7264
+ source: candidate.source,
7265
+ installedPath: candidate.installedPath,
7266
+ reason: "installed package version is not valid SemVer"
7267
+ };
7268
+ if (!satisfies(version, `>=${spec.version}`))
7269
+ return {
7270
+ state: "drift",
7271
+ source: candidate.source,
7272
+ installedPath: candidate.installedPath,
7273
+ version,
7274
+ reason: `installed package version does not satisfy >=${spec.version}`
7275
+ };
7276
+ if (requireManagedSource && !piExternalSourceMatches(candidate, spec.source))
7277
+ return {
7278
+ state: "drift",
7279
+ source: candidate.source,
7280
+ installedPath: candidate.installedPath,
7281
+ version,
7282
+ reason: `configured source must resolve to ${spec.source}`
7283
+ };
7284
+ return {
7285
+ state: "installed",
7286
+ source: candidate.source,
7287
+ installedPath: candidate.installedPath,
7288
+ version
7289
+ };
6992
7290
  }
6993
7291
 
6994
7292
  // src/cli/pi-install.ts
6995
7293
  import { spawnSync as spawnSync6 } from "child_process";
6996
- import { createHash as createHash4 } from "crypto";
7294
+ import { createHash as createHash5 } from "crypto";
6997
7295
  import {
6998
- copyFileSync as copyFileSync5,
6999
7296
  existsSync as existsSync14,
7000
- lstatSync as lstatSync3,
7001
- mkdirSync as mkdirSync7,
7297
+ lstatSync as lstatSync4,
7002
7298
  readdirSync as readdirSync3,
7003
- readFileSync as readFileSync11,
7004
- renameSync as renameSync6,
7005
- rmSync as rmSync6,
7006
- writeFileSync as writeFileSync7
7299
+ readFileSync as readFileSync13,
7300
+ realpathSync as realpathSync3,
7301
+ statSync as statSync3
7007
7302
  } from "fs";
7008
- import { dirname as dirname9, isAbsolute as isAbsolute3, join as join11 } from "path";
7009
- import { fileURLToPath as fileURLToPath5, pathToFileURL } from "url";
7303
+ import { basename as basename3, dirname as dirname10, isAbsolute as isAbsolute4, join as join12, resolve as resolve8, sep as sep3 } from "path";
7304
+ import { fileURLToPath as fileURLToPath5 } from "url";
7305
+ import { lt } from "semver";
7306
+
7307
+ // src/cli/pi-git-source.ts
7308
+ import { resolve as resolve4, sep as sep2 } from "path";
7309
+ import hostedGitInfo from "hosted-git-info";
7310
+ function splitRef(url) {
7311
+ const scpLikeMatch = url.match(/^git@([^:]+):(.+)$/);
7312
+ if (scpLikeMatch) {
7313
+ const pathWithMaybeRef2 = scpLikeMatch[2] ?? "";
7314
+ const refSeparator2 = pathWithMaybeRef2.indexOf("@");
7315
+ if (refSeparator2 < 0) return { repo: url };
7316
+ const repoPath2 = pathWithMaybeRef2.slice(0, refSeparator2);
7317
+ const ref2 = pathWithMaybeRef2.slice(refSeparator2 + 1);
7318
+ if (!repoPath2 || !ref2) return { repo: url };
7319
+ return { repo: `git@${scpLikeMatch[1] ?? ""}:${repoPath2}`, ref: ref2 };
7320
+ }
7321
+ if (url.includes("://")) {
7322
+ try {
7323
+ const parsed = new URL(url);
7324
+ const pathWithMaybeRef2 = parsed.pathname.replace(/^\/+/, "");
7325
+ const refSeparator2 = pathWithMaybeRef2.indexOf("@");
7326
+ if (refSeparator2 < 0) return { repo: url };
7327
+ const repoPath2 = pathWithMaybeRef2.slice(0, refSeparator2);
7328
+ const ref2 = pathWithMaybeRef2.slice(refSeparator2 + 1);
7329
+ if (!repoPath2 || !ref2) return { repo: url };
7330
+ parsed.pathname = `/${repoPath2}`;
7331
+ return { repo: parsed.toString().replace(/\/$/, ""), ref: ref2 };
7332
+ } catch {
7333
+ return { repo: url };
7334
+ }
7335
+ }
7336
+ const slashIndex = url.indexOf("/");
7337
+ if (slashIndex < 0) return { repo: url };
7338
+ const host = url.slice(0, slashIndex);
7339
+ const pathWithMaybeRef = url.slice(slashIndex + 1);
7340
+ const refSeparator = pathWithMaybeRef.indexOf("@");
7341
+ if (refSeparator < 0) return { repo: url };
7342
+ const repoPath = pathWithMaybeRef.slice(0, refSeparator);
7343
+ const ref = pathWithMaybeRef.slice(refSeparator + 1);
7344
+ if (!repoPath || !ref) return { repo: url };
7345
+ return { repo: `${host}/${repoPath}`, ref };
7346
+ }
7347
+ function hasUnsafeGitInstallPart(value, allowSlash) {
7348
+ let decoded;
7349
+ try {
7350
+ decoded = decodeURIComponent(value);
7351
+ } catch {
7352
+ return true;
7353
+ }
7354
+ for (const candidate of [value, decoded]) {
7355
+ if (candidate.includes("\0") || candidate.includes("\\") || candidate.startsWith("/") || !allowSlash && candidate.includes("/") || candidate.split("/").includes(".."))
7356
+ return true;
7357
+ }
7358
+ return false;
7359
+ }
7360
+ function buildGitSource(args) {
7361
+ if (args.path.startsWith("/")) return null;
7362
+ const normalizedPath = args.path.replace(/\.git$/, "").replace(/^\/+/, "");
7363
+ if (!args.host || !normalizedPath || normalizedPath.split("/").length < 2 || hasUnsafeGitInstallPart(args.host, false) || hasUnsafeGitInstallPart(normalizedPath, true))
7364
+ return null;
7365
+ return {
7366
+ type: "git",
7367
+ repo: args.repo,
7368
+ host: args.host,
7369
+ path: normalizedPath,
7370
+ ref: args.ref,
7371
+ pinned: Boolean(args.ref)
7372
+ };
7373
+ }
7374
+ function parseGenericGitUrl(url) {
7375
+ const { repo: repoWithoutRef, ref } = splitRef(url);
7376
+ let repo = repoWithoutRef;
7377
+ let host = "";
7378
+ let path = "";
7379
+ const scpLikeMatch = repoWithoutRef.match(/^git@([^:]+):(.+)$/);
7380
+ if (scpLikeMatch) {
7381
+ host = scpLikeMatch[1] ?? "";
7382
+ path = scpLikeMatch[2] ?? "";
7383
+ } else if (repoWithoutRef.startsWith("https://") || repoWithoutRef.startsWith("http://") || repoWithoutRef.startsWith("ssh://") || repoWithoutRef.startsWith("git://")) {
7384
+ try {
7385
+ const parsed = new URL(repoWithoutRef);
7386
+ host = parsed.hostname;
7387
+ path = parsed.pathname.replace(/^\/+/, "");
7388
+ } catch {
7389
+ return null;
7390
+ }
7391
+ } else {
7392
+ const slashIndex = repoWithoutRef.indexOf("/");
7393
+ if (slashIndex < 0) return null;
7394
+ host = repoWithoutRef.slice(0, slashIndex);
7395
+ path = repoWithoutRef.slice(slashIndex + 1);
7396
+ if (!host.includes(".") && host !== "localhost") return null;
7397
+ repo = `https://${repoWithoutRef}`;
7398
+ }
7399
+ return buildGitSource({ repo, host, path, ref });
7400
+ }
7401
+ function parseGitUrl(source) {
7402
+ const trimmed = source.trim();
7403
+ const hasGitPrefix = trimmed.startsWith("git:");
7404
+ const url = hasGitPrefix ? trimmed.slice(4).trim() : trimmed;
7405
+ if (!hasGitPrefix && !/^(https?|ssh|git):\/\//i.test(url)) return null;
7406
+ const split = splitRef(url);
7407
+ const hostedCandidates = [
7408
+ split.ref ? `${split.repo}#${split.ref}` : void 0,
7409
+ url
7410
+ ].filter((value) => Boolean(value));
7411
+ for (const candidate of hostedCandidates) {
7412
+ const info = hostedGitInfo.fromUrl(candidate);
7413
+ if (info) {
7414
+ if (split.ref && info.project?.includes("@")) continue;
7415
+ const useHttpsPrefix = !split.repo.startsWith("http://") && !split.repo.startsWith("https://") && !split.repo.startsWith("ssh://") && !split.repo.startsWith("git://") && !split.repo.startsWith("git@");
7416
+ return buildGitSource({
7417
+ repo: useHttpsPrefix ? `https://${split.repo}` : split.repo,
7418
+ host: info.domain || "",
7419
+ path: `${info.user}/${info.project}`,
7420
+ ref: info.committish || split.ref || void 0
7421
+ });
7422
+ }
7423
+ }
7424
+ const httpsCandidates = [
7425
+ split.ref ? `https://${split.repo}#${split.ref}` : void 0,
7426
+ `https://${url}`
7427
+ ].filter((value) => Boolean(value));
7428
+ for (const candidate of httpsCandidates) {
7429
+ const info = hostedGitInfo.fromUrl(candidate);
7430
+ if (info) {
7431
+ if (split.ref && info.project?.includes("@")) continue;
7432
+ return buildGitSource({
7433
+ repo: `https://${split.repo}`,
7434
+ host: info.domain || "",
7435
+ path: `${info.user}/${info.project}`,
7436
+ ref: info.committish || split.ref || void 0
7437
+ });
7438
+ }
7439
+ }
7440
+ return parseGenericGitUrl(url);
7441
+ }
7442
+ function projectGitPackagePath(source, projectRoot) {
7443
+ const trimmed = source.trim();
7444
+ if (!["npm:", "git:", "github:", "http:", "https:", "ssh:", "builtin:"].some(
7445
+ (prefix) => trimmed.startsWith(prefix)
7446
+ ))
7447
+ return void 0;
7448
+ const parsed = parseGitUrl(source);
7449
+ if (!parsed) return void 0;
7450
+ const installRoot = resolve4(projectRoot, "git");
7451
+ const installedPath = resolve4(installRoot, parsed.host, parsed.path);
7452
+ if (installedPath !== installRoot && !installedPath.startsWith(`${installRoot}${sep2}`))
7453
+ return void 0;
7454
+ return installedPath;
7455
+ }
7010
7456
 
7011
7457
  // src/cli/pi-migration.ts
7012
7458
  import {
@@ -7014,16 +7460,16 @@ import {
7014
7460
  existsSync as existsSync11,
7015
7461
  mkdirSync as mkdirSync5,
7016
7462
  readdirSync as readdirSync2,
7017
- readFileSync as readFileSync9,
7463
+ readFileSync as readFileSync10,
7018
7464
  renameSync as renameSync4,
7019
7465
  statSync as statSync2,
7020
7466
  writeFileSync as writeFileSync4
7021
7467
  } from "fs";
7022
- import { dirname as dirname6, join as join7 } from "path";
7468
+ import { dirname as dirname7, join as join8 } from "path";
7023
7469
  function files(root, current = root) {
7024
7470
  if (!existsSync11(root)) return [];
7025
7471
  return readdirSync2(current, { withFileTypes: true }).flatMap((entry) => {
7026
- const path = join7(current, entry.name);
7472
+ const path = join8(current, entry.name);
7027
7473
  return entry.isDirectory() ? files(root, path) : [path.slice(root.length + 1).replaceAll("\\", "/")];
7028
7474
  }).sort();
7029
7475
  }
@@ -7031,7 +7477,7 @@ function identicalTrees(left, right) {
7031
7477
  const leftFiles = files(left);
7032
7478
  const rightFiles = files(right);
7033
7479
  return leftFiles.length === rightFiles.length && leftFiles.every(
7034
- (name, index) => name === rightFiles[index] && readFileSync9(join7(left, name)).equals(readFileSync9(join7(right, name)))
7480
+ (name, index) => name === rightFiles[index] && readFileSync10(join8(left, name)).equals(readFileSync10(join8(right, name)))
7035
7481
  );
7036
7482
  }
7037
7483
  function removeRootBlock(content) {
@@ -7046,19 +7492,19 @@ function removeRootBlock(content) {
7046
7492
  function migrateLegacyPiResources(options) {
7047
7493
  const changed = [];
7048
7494
  const manualActions = [];
7049
- const appendPath = join7(options.piRoot, "APPEND_SYSTEM.md");
7495
+ const appendPath = join8(options.piRoot, "APPEND_SYSTEM.md");
7050
7496
  let nextRoot;
7051
7497
  try {
7052
7498
  if (existsSync11(appendPath))
7053
- nextRoot = removeRootBlock(readFileSync9(appendPath, "utf8"));
7499
+ nextRoot = removeRootBlock(readFileSync10(appendPath, "utf8"));
7054
7500
  } catch (error) {
7055
7501
  manualActions.push(error instanceof Error ? error.message : String(error));
7056
7502
  }
7057
7503
  const retire = [];
7058
7504
  for (const name of THOTH_OWNED_SKILL_NAMES) {
7059
- const source = join7(options.piRoot, "skills", name);
7505
+ const source = join8(options.piRoot, "skills", name);
7060
7506
  if (!existsSync11(source)) continue;
7061
- const canonical = join7(options.packageRoot, "skills", name);
7507
+ const canonical = join8(options.packageRoot, "skills", name);
7062
7508
  const backup = `${source}.thoth-agents-legacy.bak`;
7063
7509
  if (!statSync2(source).isDirectory() || !identicalTrees(source, canonical) || existsSync11(backup)) {
7064
7510
  manualActions.push(
@@ -7079,7 +7525,7 @@ function migrateLegacyPiResources(options) {
7079
7525
  };
7080
7526
  try {
7081
7527
  if (nextRoot !== void 0) {
7082
- mkdirSync5(dirname6(appendPath), { recursive: true });
7528
+ mkdirSync5(dirname7(appendPath), { recursive: true });
7083
7529
  copyFileSync4(appendPath, `${appendPath}.thoth-agents-legacy.bak`);
7084
7530
  const temporary = `${appendPath}.tmp-${process.pid}`;
7085
7531
  writeFileSync4(temporary, nextRoot);
@@ -7111,29 +7557,63 @@ function migrateLegacyPiResources(options) {
7111
7557
 
7112
7558
  // src/cli/pi-native-probe.ts
7113
7559
  import { spawnSync as spawnSync5 } from "child_process";
7114
- import { existsSync as existsSync12, mkdtempSync as mkdtempSync2, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
7560
+ import {
7561
+ existsSync as existsSync12,
7562
+ mkdtempSync as mkdtempSync2,
7563
+ readFileSync as readFileSync11,
7564
+ rmSync as rmSync5,
7565
+ writeFileSync as writeFileSync5
7566
+ } from "fs";
7115
7567
  import { tmpdir } from "os";
7116
- import { dirname as dirname7, join as join8, resolve as resolve3 } from "path";
7568
+ import { dirname as dirname8, join as join9, resolve as resolve5, win32 } from "path";
7569
+ function resolvePiWindowsCliFromShim(shimPath, shimContents) {
7570
+ const target = shimContents.match(
7571
+ /"([^"\r\n]*@earendil-works[\\/]+pi-coding-agent[\\/]+dist[\\/]+bundle[\\/]+cli\.js)"\s+%\*/i
7572
+ )?.[1];
7573
+ if (!target) return void 0;
7574
+ const expanded = target.replace(
7575
+ /%~dp0|%dp0%/gi,
7576
+ `${win32.dirname(shimPath)}\\`
7577
+ );
7578
+ return win32.isAbsolute(expanded) ? win32.normalize(expanded) : void 0;
7579
+ }
7580
+ function findPiWindowsCli() {
7581
+ const located = spawnSync5("where.exe", ["pi.cmd"], {
7582
+ windowsHide: true,
7583
+ encoding: "utf8"
7584
+ }).stdout?.split(/\r?\n/).find(Boolean);
7585
+ if (!located) return void 0;
7586
+ try {
7587
+ const cli = resolvePiWindowsCliFromShim(
7588
+ located,
7589
+ readFileSync11(located, "utf8")
7590
+ );
7591
+ if (cli && existsSync12(cli)) return cli;
7592
+ } catch {
7593
+ }
7594
+ const adjacentCli = join9(
7595
+ dirname8(located),
7596
+ "node_modules",
7597
+ "@earendil-works",
7598
+ "pi-coding-agent",
7599
+ "dist",
7600
+ "bundle",
7601
+ "cli.js"
7602
+ );
7603
+ return existsSync12(adjacentCli) ? adjacentCli : void 0;
7604
+ }
7117
7605
  function defaultExecutor(command, args, env) {
7118
7606
  let executable = command;
7119
7607
  let executableArgs = [...args];
7120
7608
  if (process.platform === "win32" && command === "pi") {
7121
- const located = spawnSync5("where.exe", ["pi.cmd"], { encoding: "utf8" }).stdout?.split(/\r?\n/).find(Boolean);
7122
- const cli = located ? join8(
7123
- dirname7(located),
7124
- "node_modules",
7125
- "@earendil-works",
7126
- "pi-coding-agent",
7127
- "dist",
7128
- "bundle",
7129
- "cli.js"
7130
- ) : void 0;
7131
- if (cli && existsSync12(cli)) {
7609
+ const cli = findPiWindowsCli();
7610
+ if (cli) {
7132
7611
  executable = process.execPath;
7133
7612
  executableArgs = [cli, ...args];
7134
7613
  }
7135
7614
  }
7136
7615
  const result = spawnSync5(executable, executableArgs, {
7616
+ windowsHide: true,
7137
7617
  encoding: "utf8",
7138
7618
  timeout: 3e4,
7139
7619
  env: { ...process.env, ...env }
@@ -7150,8 +7630,8 @@ function occurrences(text, marker) {
7150
7630
  }
7151
7631
  function observePiNativeRoot(options) {
7152
7632
  const ownsIsolatedRoot = options.piHome === void 0;
7153
- const isolatedRoot = options.piHome ?? mkdtempSync2(join8(tmpdir(), "thoth-pi-observer-"));
7154
- const observerPath = join8(
7633
+ const isolatedRoot = options.piHome ?? mkdtempSync2(join9(tmpdir(), "thoth-pi-observer-"));
7634
+ const observerPath = join9(
7155
7635
  isolatedRoot,
7156
7636
  `observer-${process.pid}-${Date.now()}.mjs`
7157
7637
  );
@@ -7160,7 +7640,7 @@ function observePiNativeRoot(options) {
7160
7640
  observerPath,
7161
7641
  `import { existsSync, readdirSync, readFileSync } from 'node:fs';
7162
7642
  import { isAbsolute, join, relative, resolve } from 'node:path';
7163
- import { createAssistantMessageEventStream } from '@earendil-works/pi-ai';
7643
+ import { createAssistantMessageEventStream, getCurrentSystemPrompt } from '@earendil-works/pi-ai';
7164
7644
  const manifestSha256=${JSON.stringify(options.manifestSha256)};
7165
7645
  const extensionSha256=${JSON.stringify(options.extensionSha256)};
7166
7646
  const packageRoot=${JSON.stringify(options.packageRoot)};
@@ -7205,12 +7685,16 @@ export default function observer(pi) {
7205
7685
  const stream=createAssistantMessageEventStream();
7206
7686
  queueMicrotask(async () => {
7207
7687
  try {
7208
- skills=discoveredSkills(context.systemPrompt);
7209
- const raw={systemPrompt:context.systemPrompt,messages:context.messages};
7688
+ const systemPrompt=getCurrentSystemPrompt(context.messages);
7689
+ skills=discoveredSkills(systemPrompt);
7690
+ const raw={systemPrompt};
7210
7691
  if (streamOptions?.onPayload) await streamOptions.onPayload(raw);
7211
7692
  const message={role:'assistant',content:[{type:'text',text:'probe'}],api:model.api,provider:model.provider,model:model.id,usage:{input:0,output:1,cacheRead:0,cacheWrite:0,totalTokens:1,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:'stop',timestamp:Date.now()};
7212
- stream.push({type:'done',message});
7213
- } catch (error) { stream.push({type:'error',error}); }
7693
+ stream.push({type:'start',partial:message});
7694
+ stream.push({type:'done',reason:'stop',message});
7695
+ } catch (error) {
7696
+ stream.push({type:'error',reason:'error',error:{role:'assistant',content:[],api:model.api,provider:model.provider,model:model.id,usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:'error',errorMessage:String(error),timestamp:Date.now()}});
7697
+ } finally { stream.end(); }
7214
7698
  });
7215
7699
  return stream;
7216
7700
  }
@@ -7226,7 +7710,7 @@ export default function observer(pi) {
7226
7710
  "--offline",
7227
7711
  "--no-extensions",
7228
7712
  "--extension",
7229
- resolve3(options.extensionPath),
7713
+ resolve5(options.extensionPath),
7230
7714
  "--extension",
7231
7715
  observerPath,
7232
7716
  "--provider",
@@ -7279,8 +7763,8 @@ ${result.stderr}`.split(/\r?\n/).flatMap((line) => {
7279
7763
  ) : void 0;
7280
7764
  const orchestratorChild = typeof record?.orchestratorChild === "boolean" ? record.orchestratorChild : void 0;
7281
7765
  const sessionStartCount = typeof record?.sessionStartCount === "number" ? record.sessionStartCount : void 0;
7282
- const valid = record?.manifestSha256 === options.manifestSha256 && record?.extensionSha256 === options.extensionSha256 && record?.hookCount === 1 && occurrences(prompt, PI_ROOT_START) === 1 && occurrences(prompt, PI_ROOT_END) === 1 && prompt.indexOf(PI_ROOT_START) < prompt.indexOf(PI_ROOT_END);
7283
- return valid ? {
7766
+ const valid2 = record?.manifestSha256 === options.manifestSha256 && record?.extensionSha256 === options.extensionSha256 && record?.hookCount === 1 && occurrences(prompt, PI_ROOT_START) === 1 && occurrences(prompt, PI_ROOT_END) === 1 && prompt.indexOf(PI_ROOT_START) < prompt.indexOf(PI_ROOT_END);
7767
+ return valid2 ? {
7284
7768
  state: "observed-at-install",
7285
7769
  basis: [
7286
7770
  "Real Pi final provider request contained exactly one receipt-bound root marker."
@@ -7299,8 +7783,8 @@ ${result.stderr}`.split(/\r?\n/).flatMap((line) => {
7299
7783
  };
7300
7784
  } finally {
7301
7785
  if (ownsIsolatedRoot)
7302
- rmSync4(isolatedRoot, { recursive: true, force: true });
7303
- else rmSync4(observerPath, { force: true });
7786
+ rmSync5(isolatedRoot, { recursive: true, force: true });
7787
+ else rmSync5(observerPath, { force: true });
7304
7788
  }
7305
7789
  }
7306
7790
 
@@ -7308,13 +7792,13 @@ ${result.stderr}`.split(/\r?\n/).flatMap((line) => {
7308
7792
  import {
7309
7793
  existsSync as existsSync13,
7310
7794
  mkdirSync as mkdirSync6,
7311
- readFileSync as readFileSync10,
7795
+ readFileSync as readFileSync12,
7312
7796
  renameSync as renameSync5,
7313
- rmSync as rmSync5,
7797
+ rmSync as rmSync6,
7314
7798
  writeFileSync as writeFileSync6
7315
7799
  } from "fs";
7316
7800
  import { homedir as homedir5 } from "os";
7317
- import { dirname as dirname8, isAbsolute as isAbsolute2, join as join9, normalize, resolve as resolve4 } from "path";
7801
+ import { dirname as dirname9, isAbsolute as isAbsolute3, join as join10, normalize, resolve as resolve6 } from "path";
7318
7802
  var keys = [
7319
7803
  "schemaVersion",
7320
7804
  "owner",
@@ -7329,16 +7813,16 @@ var keys = [
7329
7813
  var semver = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
7330
7814
  var digest = /^[0-9a-f]{64}$/;
7331
7815
  function piPackagePathsEqual(left, right) {
7332
- const normalizedLeft = normalize(resolve4(left));
7333
- const normalizedRight = normalize(resolve4(right));
7816
+ const normalizedLeft = normalize(resolve6(left));
7817
+ const normalizedRight = normalize(resolve6(right));
7334
7818
  return process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight;
7335
7819
  }
7336
7820
  function getPiPackageReceiptPath(options = {}) {
7337
7821
  const env = options.env ?? process.env;
7338
- const root = options.configRoot ?? env.XDG_CONFIG_HOME?.trim() ?? join9(options.homeDir ?? homedir5(), ".config");
7339
- return join9(root, "thoth-agents", "pi-package.json");
7822
+ const root = options.configRoot ?? env.XDG_CONFIG_HOME?.trim() ?? join10(options.homeDir ?? homedir5(), ".config");
7823
+ return join10(root, "thoth-agents", "pi-package.json");
7340
7824
  }
7341
- function parse(value) {
7825
+ function parse2(value) {
7342
7826
  if (!value || typeof value !== "object" || Array.isArray(value))
7343
7827
  throw new Error("Receipt must be a JSON object.");
7344
7828
  const record = value;
@@ -7350,7 +7834,7 @@ function parse(value) {
7350
7834
  throw new Error("Receipt version is invalid.");
7351
7835
  const npmSource = `npm:thoth-agents@${record.version}`;
7352
7836
  const npmIdentity = record.source === npmSource && record.installSource === npmSource;
7353
- const localIdentity = typeof record.source === "string" && record.source.length > 0 && !isAbsolute2(record.source) && !/^[a-z][a-z+.-]*:/i.test(record.source) && typeof record.installSource === "string" && isAbsolute2(record.installSource);
7837
+ const localIdentity = typeof record.source === "string" && record.source.length > 0 && !isAbsolute3(record.source) && !/^[a-z][a-z+.-]*:/i.test(record.source) && typeof record.installSource === "string" && isAbsolute3(record.installSource);
7354
7838
  if (!npmIdentity && !localIdentity)
7355
7839
  throw new Error("Receipt source is not canonical.");
7356
7840
  if (typeof record.manifestSha256 !== "string" || !digest.test(record.manifestSha256) || typeof record.extensionSha256 !== "string" || !digest.test(record.extensionSha256))
@@ -7364,7 +7848,7 @@ function readPiPackageReceipt(options = {}) {
7364
7848
  return {
7365
7849
  status: "valid",
7366
7850
  path,
7367
- receipt: parse(JSON.parse(readFileSync10(path, "utf8")))
7851
+ receipt: parse2(JSON.parse(readFileSync12(path, "utf8")))
7368
7852
  };
7369
7853
  } catch (error) {
7370
7854
  return {
@@ -7377,8 +7861,8 @@ function readPiPackageReceipt(options = {}) {
7377
7861
  function writePiPackageReceipt(receipt, options = {}) {
7378
7862
  const path = getPiPackageReceiptPath(options);
7379
7863
  try {
7380
- const validated = parse(receipt);
7381
- mkdirSync6(dirname8(path), { recursive: true });
7864
+ const validated = parse2(receipt);
7865
+ mkdirSync6(dirname9(path), { recursive: true });
7382
7866
  const temporary = `${path}.tmp-${process.pid}`;
7383
7867
  writeFileSync6(temporary, `${JSON.stringify(validated, null, 2)}
7384
7868
  `, {
@@ -7387,7 +7871,7 @@ function writePiPackageReceipt(receipt, options = {}) {
7387
7871
  try {
7388
7872
  renameSync5(temporary, path);
7389
7873
  } catch (error) {
7390
- rmSync5(temporary, { force: true });
7874
+ rmSync6(temporary, { force: true });
7391
7875
  throw error;
7392
7876
  }
7393
7877
  return { success: true, path };
@@ -7422,76 +7906,78 @@ function classifyPiPackageOwnership(input) {
7422
7906
 
7423
7907
  // src/cli/pi-paths.ts
7424
7908
  import { homedir as homedir6 } from "os";
7425
- import { join as join10, resolve as resolve5 } from "path";
7909
+ import { join as join11, resolve as resolve7 } from "path";
7426
7910
  function resolvePiPaths(options = {}) {
7427
7911
  const env = options.env ?? process.env;
7428
- const homeDir = resolve5(
7912
+ const homeDir = resolve7(
7429
7913
  options.homeDir ?? env.HOME ?? env.USERPROFILE ?? homedir6()
7430
7914
  );
7431
- const defaultPiRoot = join10(homeDir, ".pi", "agent");
7915
+ const defaultPiRoot = join11(homeDir, ".pi", "agent");
7432
7916
  const configured = env.PI_CODING_AGENT_DIR?.trim();
7433
- const piRoot = configured ? resolve5(configured) : defaultPiRoot;
7434
- const cwd = resolve5(options.cwd ?? process.cwd());
7435
- const configRoot = env.XDG_CONFIG_HOME?.trim() ? resolve5(env.XDG_CONFIG_HOME) : join10(homeDir, ".config");
7917
+ const piRoot = configured ? resolve7(configured) : defaultPiRoot;
7918
+ const cwd = resolve7(options.cwd ?? process.cwd());
7919
+ const configRoot = env.XDG_CONFIG_HOME?.trim() ? resolve7(env.XDG_CONFIG_HOME) : join11(homeDir, ".config");
7436
7920
  return {
7437
7921
  homeDir,
7438
7922
  piRoot,
7439
7923
  defaultPiRoot,
7440
7924
  customPiRoot: piRoot !== defaultPiRoot,
7441
7925
  skillsDestinationCompatible: piRoot === defaultPiRoot,
7442
- appendSystemPath: join10(piRoot, "APPEND_SYSTEM.md"),
7443
- agentsRoot: join10(piRoot, "agents"),
7444
- alternateAgentsRoot: join10(piRoot, "subagents"),
7445
- ownedSkillsRoot: join10(piRoot, "skills"),
7446
- mcpConfigPath: join10(configRoot, "mcp", "mcp.json"),
7926
+ appendSystemPath: join11(piRoot, "APPEND_SYSTEM.md"),
7927
+ agentsRoot: join11(piRoot, "agents"),
7928
+ alternateAgentsRoot: join11(piRoot, "subagents"),
7929
+ ownedSkillsRoot: join11(piRoot, "skills"),
7930
+ settingsPath: join11(piRoot, "settings.json"),
7931
+ subagentsConfigPath: join11(piRoot, "subagents.json"),
7932
+ mcpConfigPath: join11(configRoot, "mcp", "mcp.json"),
7447
7933
  projectAgentRoots: [
7448
- join10(cwd, ".pi", "agents"),
7449
- join10(cwd, ".pi", "subagents")
7934
+ join11(cwd, ".pi", "agents"),
7935
+ join11(cwd, ".pi", "subagents")
7450
7936
  ],
7451
- projectMcpPaths: [join10(cwd, ".mcp.json"), join10(cwd, ".pi", "mcp.json")]
7937
+ projectMcpPaths: [join11(cwd, ".mcp.json"), join11(cwd, ".pi", "mcp.json")]
7452
7938
  };
7453
7939
  }
7454
7940
 
7455
7941
  // src/cli/pi-install.ts
7456
- var PI_MINIMUM_VERSION = "0.84.4";
7942
+ var PI_MINIMUM_VERSION = "0.99.0";
7457
7943
  var PI_NODE_MINIMUM = "22.19.0";
7458
7944
  var PI_COMMAND_TIMEOUT_MS = 12e4;
7459
7945
  var PI_PACKAGE_SPECS = [
7460
7946
  {
7461
7947
  id: "delegation",
7462
- source: "npm:pi-subagents-j0k3r@1.5.9",
7463
- packageName: "pi-subagents-j0k3r",
7464
- version: "1.5.9"
7948
+ source: "npm:@thoth-agents/pi-subagents@>=0.1.0",
7949
+ packageName: "@thoth-agents/pi-subagents",
7950
+ version: "0.1.0"
7465
7951
  },
7466
7952
  {
7467
7953
  id: "context7",
7468
- source: "npm:@upstash/context7-pi@0.1.2",
7954
+ source: "npm:@upstash/context7-pi@>=0.1.2",
7469
7955
  packageName: "@upstash/context7-pi",
7470
7956
  version: "0.1.2"
7471
7957
  },
7472
7958
  {
7473
7959
  id: "web-access",
7474
- source: "npm:pi-web-access@0.27.0",
7960
+ source: "npm:pi-web-access@>=0.27.0",
7475
7961
  packageName: "pi-web-access",
7476
7962
  version: "0.27.0"
7477
7963
  },
7478
7964
  {
7479
7965
  id: "grep-adapter",
7480
- source: "npm:pi-mcp-adapter@2.32.1",
7966
+ source: "npm:pi-mcp-adapter@>=2.32.1",
7481
7967
  packageName: "pi-mcp-adapter",
7482
7968
  version: "2.32.1"
7483
7969
  },
7484
7970
  {
7485
7971
  id: "ask-user-question",
7486
- source: "npm:@juicesharp/rpiv-ask-user-question@2.9.0",
7487
- packageName: "@juicesharp/rpiv-ask-user-question",
7488
- version: "2.9.0"
7972
+ source: "npm:@thoth-agents/pi-questions-user@>=0.1.0",
7973
+ packageName: "@thoth-agents/pi-questions-user",
7974
+ version: "0.1.0"
7489
7975
  },
7490
7976
  {
7491
7977
  id: "todo",
7492
- source: "npm:@juicesharp/rpiv-todo@2.9.0",
7493
- packageName: "@juicesharp/rpiv-todo",
7494
- version: "2.9.0"
7978
+ source: "npm:@thoth-agents/pi-todo@>=0.1.0",
7979
+ packageName: "@thoth-agents/pi-todo",
7980
+ version: "0.1.0"
7495
7981
  }
7496
7982
  ];
7497
7983
  var PI_GREP_MCP_ENTRY = {
@@ -7503,7 +7989,7 @@ function getPiFirstPartySource(version) {
7503
7989
  return `npm:thoth-agents@${version}`;
7504
7990
  }
7505
7991
  function packageRootFor(options) {
7506
- const root = options.packageRoot ?? findPackageRoot(dirname9(fileURLToPath5(import.meta.url)));
7992
+ const root = options.packageRoot ?? findPackageRoot(dirname10(fileURLToPath5(import.meta.url)));
7507
7993
  if (!root)
7508
7994
  throw new Error(
7509
7995
  "Unable to locate the executing thoth-agents package root."
@@ -7512,63 +7998,56 @@ function packageRootFor(options) {
7512
7998
  }
7513
7999
  function packageVersionFor(root) {
7514
8000
  const value = JSON.parse(
7515
- readFileSync11(join11(root, "package.json"), "utf8")
8001
+ readFileSync13(join12(root, "package.json"), "utf8")
7516
8002
  );
7517
8003
  if (typeof value.version !== "string")
7518
8004
  throw new Error("Executing thoth-agents package version is invalid.");
7519
8005
  return value.version;
7520
8006
  }
7521
8007
  function sha2562(path) {
7522
- return createHash4("sha256").update(readFileSync11(path)).digest("hex");
8008
+ return createHash5("sha256").update(readFileSync13(path)).digest("hex");
7523
8009
  }
7524
8010
  function verifyPiFirstPartyPackage(input) {
7525
8011
  try {
7526
- const manifestPath = join11(input.packageRoot, "package.json");
7527
- const extensionPath = join11(input.packageRoot, "dist", "pi.js");
7528
- const assetsPath = join11(
8012
+ const manifestPath = join12(input.packageRoot, "package.json");
8013
+ const extensionPath = join12(input.packageRoot, "dist", "pi.js");
8014
+ const assetsPath = join12(
7529
8015
  input.packageRoot,
7530
8016
  "pi",
7531
8017
  ".thoth-agents-assets.json"
7532
8018
  );
7533
8019
  for (const path of [manifestPath, extensionPath, assetsPath])
7534
- if (!existsSync14(path) || !lstatSync3(path).isFile() || lstatSync3(path).isSymbolicLink())
8020
+ if (!existsSync14(path) || !lstatSync4(path).isFile() || lstatSync4(path).isSymbolicLink())
7535
8021
  throw new Error(
7536
8022
  `Required regular package asset is missing or symlinked: ${path}`
7537
8023
  );
7538
- const manifest = JSON.parse(readFileSync11(manifestPath, "utf8"));
8024
+ const manifest = JSON.parse(readFileSync13(manifestPath, "utf8"));
7539
8025
  if (manifest.name !== "thoth-agents" || manifest.version !== input.version || JSON.stringify(manifest.pi) !== JSON.stringify({ extensions: ["./dist/pi.js"], skills: ["./skills"] }))
7540
8026
  throw new Error(
7541
8027
  "Installed package identity or Pi manifest is inconsistent."
7542
8028
  );
7543
- const assets = JSON.parse(readFileSync11(assetsPath, "utf8"));
8029
+ const assets = JSON.parse(readFileSync13(assetsPath, "utf8"));
7544
8030
  const expectedAgentPaths = PI_SPECIALIST_ROLES.map(
7545
8031
  (role) => `agents/${piSpecialistName(role)}.md`
7546
8032
  );
7547
8033
  if (assets.schemaVersion !== 1 || assets.owner !== "thoth-agents" || !isRecord6(assets.files) || Object.keys(assets.files).sort().join("|") !== [...expectedAgentPaths].sort().join("|"))
7548
8034
  throw new Error("Installed Pi specialist provenance is invalid.");
7549
8035
  for (const relativePath of expectedAgentPaths) {
7550
- const path = join11(input.packageRoot, "pi", relativePath);
7551
- if (!existsSync14(path) || !lstatSync3(path).isFile() || lstatSync3(path).isSymbolicLink() || sha2562(path) !== assets.files[relativePath])
8036
+ const path = join12(input.packageRoot, "pi", relativePath);
8037
+ if (!existsSync14(path) || !lstatSync4(path).isFile() || lstatSync4(path).isSymbolicLink() || sha2562(path) !== assets.files[relativePath])
7552
8038
  throw new Error(
7553
8039
  `Installed Pi specialist asset is missing, symlinked, or stale: ${relativePath}`
7554
8040
  );
7555
8041
  }
7556
8042
  for (const skill of THOTH_OWNED_SKILL_NAMES) {
7557
- const path = join11(input.packageRoot, "skills", skill, "SKILL.md");
7558
- if (!existsSync14(path) || !lstatSync3(path).isFile() || lstatSync3(path).isSymbolicLink())
8043
+ const path = join12(input.packageRoot, "skills", skill, "SKILL.md");
8044
+ if (!existsSync14(path) || !lstatSync4(path).isFile() || lstatSync4(path).isSymbolicLink())
7559
8045
  throw new Error(
7560
8046
  `Installed Pi manifest skill is missing or symlinked: ${skill}`
7561
8047
  );
7562
8048
  }
7563
8049
  const manifestSha256 = sha2562(manifestPath);
7564
8050
  const extensionSha256 = sha2562(extensionPath);
7565
- const load = defaultCommandExecutor4("node", [
7566
- "--input-type=module",
7567
- "--eval",
7568
- `import(${JSON.stringify(pathToFileURL(extensionPath).href)}).then(m=>{if(typeof m.default!=="function")process.exit(2)})`
7569
- ]);
7570
- if (load.exitCode !== 0)
7571
- throw new Error(`Compiled Pi extension is not loadable: ${load.stderr}`);
7572
8051
  const observation = observePiNativeRoot({
7573
8052
  extensionPath,
7574
8053
  manifestSha256,
@@ -7601,11 +8080,264 @@ function isRecord6(value) {
7601
8080
  return typeof value === "object" && value !== null && !Array.isArray(value);
7602
8081
  }
7603
8082
  function readJsonObject(path) {
8083
+ assertSafePiManagedPath(path);
7604
8084
  if (!existsSync14(path)) return {};
7605
- const parsed = JSON.parse(readFileSync11(path, "utf8"));
8085
+ const parsed = JSON.parse(readFileSync13(path, "utf8"));
7606
8086
  if (!isRecord6(parsed)) throw new Error(`${path} must contain a JSON object.`);
7607
8087
  return parsed;
7608
8088
  }
8089
+ function isPiIncumbentDelegationSource(source) {
8090
+ return /^npm:pi-subagents(?:-j0k3r)?(?:@|$)/.test(source);
8091
+ }
8092
+ function getPiExternalPackageSpecs(options = {}) {
8093
+ const runtimePackageRoot = options.runtimePackageRoot;
8094
+ if (!runtimePackageRoot) return PI_PACKAGE_SPECS;
8095
+ return PI_PACKAGE_SPECS.map(
8096
+ (spec) => spec.id === "delegation" ? { ...spec, source: runtimePackageRoot } : spec
8097
+ );
8098
+ }
8099
+ var PI_INCUMBENT_TODO_NAME = "@juicesharp/rpiv-todo";
8100
+ var PI_INCUMBENT_QUESTIONS_NAME = "@juicesharp/rpiv-ask-user-question";
8101
+ function piQuestionSourceName(source) {
8102
+ if (!source.startsWith("npm:")) return void 0;
8103
+ const spec = source.slice(4).trim();
8104
+ const alias = spec.indexOf("@npm:");
8105
+ const target = alias >= 0 ? spec.slice(alias + 5) : spec;
8106
+ return target.match(/^(@[^/@\s]+\/[^/@\s]+|[^/@\s]+)(?:@.+)?$/)?.[1];
8107
+ }
8108
+ function findPiIncumbentQuestions(packages) {
8109
+ return packages.filter(
8110
+ (candidate) => piQuestionSourceName(candidate.source) === PI_INCUMBENT_QUESTIONS_NAME || configuredPackageIdentity(candidate).packageName === PI_INCUMBENT_QUESTIONS_NAME
8111
+ );
8112
+ }
8113
+ function piQuestionIdentityBlockers(packages, plan) {
8114
+ return packages.flatMap((candidate) => {
8115
+ const manifest = configuredPackageManifest(candidate);
8116
+ if (typeof manifest?.name === "string" && manifest.name) return [];
8117
+ const npmSource = candidate.source.startsWith("npm:");
8118
+ const questionSource = /(?:^|[\\/:@?#._-])rpiv-ask-user-question(?:$|[\\/:@?#._-])/i.test(
8119
+ candidate.source
8120
+ );
8121
+ const unresolvedNpmLocation = npmSource && !candidate.installedPath;
8122
+ const unresolvedAlias = npmSource && candidate.source.includes("@npm:") && !piQuestionSourceName(candidate.source);
8123
+ const previewedQuestion = candidate.scope === "user" && plan?.items.some(
8124
+ ({ command }) => command?.command === "pi" && command.args[0] === "remove" && command.args[1] === candidate.source && command.args[2] === "--no-approve"
8125
+ );
8126
+ if (manifest && !previewedQuestion) return [];
8127
+ if (!questionSource && !unresolvedNpmLocation && !unresolvedAlias && !previewedQuestion)
8128
+ return [];
8129
+ const flags = candidate.scope === "project" ? "--local --approve" : "--no-approve";
8130
+ return [
8131
+ `Cannot verify configured Pi package ${candidate.source}: installed manifest identity is unavailable. Review its ownership and inspect its package.json using pi list; if it is ${PI_INCUMBENT_QUESTIONS_NAME}, manually run pi remove ${candidate.source} ${flags}. Verify removal, then rerun setup preview.`
8132
+ ];
8133
+ });
8134
+ }
8135
+ function piUnpreviewedQuestionRecovery(plan, packages) {
8136
+ const previewed = new Set(
8137
+ plan.items.flatMap(
8138
+ ({ command }) => command?.command === "pi" && command.args[0] === "remove" && command.args[2] === "--no-approve" ? [command.args[1]] : []
8139
+ )
8140
+ );
8141
+ const unpreviewed = packages.filter(
8142
+ ({ scope, source }) => scope === "user" && !previewed.has(source)
8143
+ );
8144
+ if (unpreviewed.length === 0) return void 0;
8145
+ return unpreviewed.map(
8146
+ ({ source }) => `Pi question migration blocked: incumbent ${source} was not previewed for removal. Review its ownership, then manually run: pi remove ${source} --no-approve. Verify with pi list, then rerun setup preview; no unpreviewed incumbent may be removed automatically.`
8147
+ ).join("\n");
8148
+ }
8149
+ function piProjectQuestionConflict(candidate) {
8150
+ const source = candidate.unmappedSource ? "<source>" : candidate.source;
8151
+ return `Incumbent Pi question package ${candidate.source} conflicts with @thoth-agents/pi-questions-user. ${candidate.identityLimitation ? `${candidate.identityLimitation} ` : ""}Review the project's ownership and trust first; --approve trusts project-local settings for this command only without saving a trust decision. ${candidate.unmappedSource ? "Find the matching project settings entry, then" : "Then"} run: pi remove ${source} --local --approve. Verify removal, then rerun setup.`;
8152
+ }
8153
+ function isPiIncumbentTodoSource(source) {
8154
+ return /^npm:@juicesharp\/rpiv-todo(?:@|$)/.test(source);
8155
+ }
8156
+ function piIncumbentTodoRecovery(source, scope, identityLimitation, unmappedSource = false) {
8157
+ const flags = scope === "project" ? "--local --approve" : "--no-approve";
8158
+ const review = scope === "project" ? "Review the project's ownership and trust first; --approve trusts project-local settings for this command only without saving a trust decision. Then run:" : "Review its ownership, then run:";
8159
+ const localRecovery = unmappedSource ? " Find the matching project settings entry; after reviewing its ownership, remove it with: pi remove <source> --local --approve." : isPiIncumbentTodoSource(source) ? "" : ` For the actual configured source, run: pi remove ${source} ${flags}.`;
8160
+ return `${identityLimitation ? `${identityLimitation} ` : ""}${review} pi remove npm:${PI_INCUMBENT_TODO_NAME} ${flags}.${localRecovery} Verify with pi list, then rerun setup.`;
8161
+ }
8162
+ function configuredPackageSources(settings) {
8163
+ if (!Array.isArray(settings.packages)) return [];
8164
+ return settings.packages.flatMap((entry) => {
8165
+ const source = typeof entry === "string" ? entry : isRecord6(entry) && typeof entry.source === "string" ? entry.source : void 0;
8166
+ return source ? [source] : [];
8167
+ });
8168
+ }
8169
+ function configuredPackageSource(settings, matches) {
8170
+ return configuredPackageSources(settings).find(matches);
8171
+ }
8172
+ function projectLocalPackagePath(source, projectRoot, homeDir) {
8173
+ let path = source.trim();
8174
+ if (process.platform === "win32" && !path.startsWith("//") && !path.includes("\\")) {
8175
+ const drivePath = path.match(
8176
+ /^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/i
8177
+ );
8178
+ if (drivePath)
8179
+ path = `${drivePath[1]?.toUpperCase()}:\\${drivePath[2]?.replaceAll("/", "\\") ?? ""}`;
8180
+ }
8181
+ if (path === "~") path = homeDir;
8182
+ else if (path.startsWith("~/") || process.platform === "win32" && path.startsWith("~\\"))
8183
+ path = join12(homeDir, path.slice(2));
8184
+ else if (path.startsWith("file://")) path = fileURLToPath5(path);
8185
+ return resolve8(projectRoot, path);
8186
+ }
8187
+ function legacyPiNpmPackagePath(name, settings, execute) {
8188
+ try {
8189
+ const commandParts = settings.npmCommand === void 0 ? ["npm"] : settings.npmCommand;
8190
+ if (!Array.isArray(commandParts) || commandParts.length !== 1 || typeof commandParts[0] !== "string")
8191
+ return void 0;
8192
+ const command = commandParts[0];
8193
+ if (/[\0\r\n&|<>^%"'`$;!]/.test(command)) return void 0;
8194
+ const manager = basename3(command).replace(/\.(cmd|exe)$/i, "");
8195
+ if (!["npm", "pnpm", "bun"].includes(manager)) return void 0;
8196
+ if (/\s/.test(command) && !statSync3(command, { throwIfNoEntry: false })?.isFile())
8197
+ return void 0;
8198
+ const query = (args) => {
8199
+ const result = execute(command, args);
8200
+ if (result.error || result.exitCode !== 0)
8201
+ throw new Error("Package metadata unavailable");
8202
+ return (result.stdout || result.stderr).trim();
8203
+ };
8204
+ if (manager === "pnpm") {
8205
+ const entries = JSON.parse(query(["list", "-g", "--json"]));
8206
+ if (!Array.isArray(entries)) return void 0;
8207
+ for (const entry of entries) {
8208
+ const dependencies = isRecord6(entry) ? entry.dependencies : void 0;
8209
+ const dependency = isRecord6(dependencies) ? dependencies[name] : void 0;
8210
+ if (isRecord6(dependency) && typeof dependency.path === "string" && dependency.path)
8211
+ return dependency.path;
8212
+ }
8213
+ return void 0;
8214
+ }
8215
+ const root = manager === "bun" ? join12(
8216
+ dirname10(query(["pm", "bin", "-g"])),
8217
+ "install",
8218
+ "global",
8219
+ "node_modules"
8220
+ ) : query(["root", "-g"]);
8221
+ return root ? join12(root, name) : void 0;
8222
+ } catch {
8223
+ return void 0;
8224
+ }
8225
+ }
8226
+ function parsePiPackageSource(source, baseDir) {
8227
+ if (source.startsWith("npm:")) {
8228
+ const spec = source.slice(4).trim();
8229
+ const name = spec.match(/^(@?[^@]+(?:\/[^@]+)?)(?:@(.+))?$/)?.[1] ?? spec;
8230
+ return { type: "npm", name };
8231
+ }
8232
+ const gitPath = projectGitPackagePath(source, baseDir);
8233
+ return gitPath ? { type: "git", path: gitPath } : { type: "local", path: source };
8234
+ }
8235
+ function resolvePiPackage(candidate, options, userSettings) {
8236
+ if (candidate.installedPath) return candidate;
8237
+ const paths = resolvePiPaths(options);
8238
+ const baseDir = candidate.scope === "user" ? paths.piRoot : join12(resolve8(options.cwd ?? process.cwd()), ".pi");
8239
+ const parsed = parsePiPackageSource(candidate.source, baseDir);
8240
+ const npmName = parsed.type === "npm" ? parsed.name : void 0;
8241
+ let path = npmName ? join12(baseDir, "npm", "node_modules", npmName) : parsed.type === "git" ? parsed.path : projectLocalPackagePath(candidate.source, baseDir, paths.homeDir);
8242
+ if (npmName && candidate.scope === "user" && !existsSync14(path))
8243
+ path = legacyPiNpmPackagePath(
8244
+ npmName,
8245
+ userSettings,
8246
+ options.commandExecutor ?? defaultCommandExecutor4
8247
+ ) ?? path;
8248
+ return {
8249
+ ...candidate,
8250
+ installedPath: existsSync14(path) ? path : candidate.installedPath
8251
+ };
8252
+ }
8253
+ function resolvePiConfiguredPackages(settings, scope, options) {
8254
+ return configuredPackageSources(settings).map(
8255
+ (source) => resolvePiPackage({ scope, source }, options, settings)
8256
+ );
8257
+ }
8258
+ function resolvePiListedPackages(output, options) {
8259
+ const packages = parsePiPackageList(output);
8260
+ const paths = resolvePiPaths(options);
8261
+ const settings = readJsonObject(paths.settingsPath);
8262
+ const projectRoot = join12(resolve8(options.cwd ?? process.cwd()), ".pi");
8263
+ let projectSettings;
8264
+ for (const { scope, source, installedPath } of packages) {
8265
+ const baseDir = scope === "user" ? paths.piRoot : projectRoot;
8266
+ if (parsePiPackageSource(source, baseDir).type !== "local") continue;
8267
+ let inventory = settings;
8268
+ if (scope === "project") {
8269
+ projectSettings ??= readJsonObject(join12(projectRoot, "settings.json"));
8270
+ inventory = projectSettings;
8271
+ }
8272
+ if (!configuredPackageSources(inventory).includes(source) && (Array.isArray(inventory.packages) || !installedPath || !isAbsolute4(installedPath)))
8273
+ throw new Error(
8274
+ `Pi package manifest identity cannot be verified: malformed pi list source ${source}. Rerun pi list --no-approve and inspect the configured sources before setup.`
8275
+ );
8276
+ }
8277
+ return packages.map(
8278
+ (candidate) => resolvePiPackage(candidate, options, settings)
8279
+ );
8280
+ }
8281
+ function readPiProjectPackages(options) {
8282
+ const projectRoot = join12(resolve8(options.cwd ?? process.cwd()), ".pi");
8283
+ return resolvePiConfiguredPackages(
8284
+ readJsonObject(join12(projectRoot, "settings.json")),
8285
+ "project",
8286
+ options
8287
+ );
8288
+ }
8289
+ function scanPiProjectInstalledIncumbents(options, configuredPackages) {
8290
+ const projectRoot = join12(resolve8(options.cwd ?? process.cwd()), ".pi");
8291
+ const incumbents = [];
8292
+ const visited = /* @__PURE__ */ new Set();
8293
+ const pathKey = (path) => process.platform === "win32" ? path.toLowerCase() : path;
8294
+ const knownPaths = new Set(
8295
+ configuredPackages.flatMap(
8296
+ ({ installedPath }) => installedPath ? [pathKey(realpathSync3(installedPath))] : []
8297
+ )
8298
+ );
8299
+ for (const root of [join12(projectRoot, "git"), join12(projectRoot, "npm")]) {
8300
+ if (!existsSync14(root)) continue;
8301
+ const rootKey = pathKey(realpathSync3(root));
8302
+ const pending = [root];
8303
+ while (pending.length > 0) {
8304
+ const path = pending.pop();
8305
+ if (!path || !statSync3(path, { throwIfNoEntry: false })?.isDirectory())
8306
+ continue;
8307
+ const key = pathKey(realpathSync3(path));
8308
+ if (visited.has(key)) continue;
8309
+ visited.add(key);
8310
+ const manifestPath = join12(path, "package.json");
8311
+ if (existsSync14(manifestPath) && statSync3(manifestPath).isFile()) {
8312
+ const candidate = {
8313
+ scope: "project",
8314
+ source: path,
8315
+ installedPath: path,
8316
+ unmappedSource: true
8317
+ };
8318
+ const identity = configuredPackageIdentity(candidate);
8319
+ if ((identity.packageName === PI_INCUMBENT_TODO_NAME || identity.packageName === PI_INCUMBENT_QUESTIONS_NAME) && !knownPaths.has(key))
8320
+ incumbents.push({
8321
+ ...candidate,
8322
+ ...identity,
8323
+ identityLimitation: `Installed manifest at ${path} identifies ${identity.packageName}, but no configured settings source maps to this directory.`
8324
+ });
8325
+ }
8326
+ if (key !== rootKey && !key.startsWith(`${rootKey}${sep3}`)) continue;
8327
+ for (const entry of readdirSync3(path, { withFileTypes: true }))
8328
+ if (entry.isDirectory() || entry.isSymbolicLink())
8329
+ pending.push(join12(path, entry.name));
8330
+ }
8331
+ }
8332
+ return incumbents;
8333
+ }
8334
+ function mergePiSubagentsConfig(current) {
8335
+ return {
8336
+ ...current,
8337
+ session_resources: "lean",
8338
+ enable_continue: false
8339
+ };
8340
+ }
7609
8341
  function mergePiGrepMcpConfig(current) {
7610
8342
  const rawServers = current.mcpServers;
7611
8343
  if (rawServers !== void 0 && !isRecord6(rawServers)) {
@@ -7646,8 +8378,8 @@ function findAgentConflicts(paths) {
7646
8378
  if (!existsSync14(root)) continue;
7647
8379
  for (const name of readdirSync3(root)) {
7648
8380
  if (!name.endsWith(".md")) continue;
7649
- const path = join11(root, name);
7650
- const content = readFileSync11(path, "utf8");
8381
+ const path = join12(root, name);
8382
+ const content = readFileSync13(path, "utf8");
7651
8383
  const role = frontmatterValue(content, "name");
7652
8384
  const filenameRole = name.slice(0, -".md".length).toLowerCase();
7653
8385
  const canonicalRole = role && canonical.has(role) ? role : canonical.has(filenameRole) ? filenameRole : void 0;
@@ -7679,8 +8411,57 @@ function buildPiSetupPlan(options = {}) {
7679
8411
  );
7680
8412
  }
7681
8413
  blockers.push(...findAgentConflicts(paths));
8414
+ if (options.runtimePackageRoot) {
8415
+ const runtimeRoot = options.runtimePackageRoot;
8416
+ const spec = getPiExternalPackageSpecs(options)[0];
8417
+ const sourceIsNormalizedAbsolutePath = isAbsolute4(runtimeRoot) && resolve8(runtimeRoot) === runtimeRoot;
8418
+ const inspection = sourceIsNormalizedAbsolutePath ? inspectPiExternalPackage(
8419
+ [{ scope: "user", source: runtimeRoot, installedPath: runtimeRoot }],
8420
+ spec
8421
+ ) : void 0;
8422
+ if (inspection?.state !== "installed") {
8423
+ blockers.push(
8424
+ `Local Pi delegation runtime root ${runtimeRoot} is invalid: ${inspection?.reason ?? "the source must be a normalized absolute path"}.`
8425
+ );
8426
+ }
8427
+ }
8428
+ let plannedQuestionRemovals = [];
8429
+ let subagentsConfigContent;
7682
8430
  let mcpContent;
7683
8431
  try {
8432
+ const userSettings = readJsonObject(paths.settingsPath);
8433
+ const incumbentSource = configuredPackageSource(
8434
+ userSettings,
8435
+ isPiIncumbentDelegationSource
8436
+ );
8437
+ const incumbentBlockers = [];
8438
+ if (incumbentSource)
8439
+ incumbentBlockers.push(
8440
+ `Incumbent delegation runtime ${incumbentSource} is configured in ${paths.settingsPath}. Review its ownership, then run: pi remove ${incumbentSource} --no-approve. Rerun setup after removing it; ${PI_PACKAGE_SPECS[0].source} cannot be loaded beside it.`
8441
+ );
8442
+ const incumbentTodoSource = configuredPackageSource(
8443
+ userSettings,
8444
+ isPiIncumbentTodoSource
8445
+ );
8446
+ if (incumbentTodoSource)
8447
+ incumbentBlockers.push(
8448
+ `Incumbent Pi task-list package ${incumbentTodoSource} is configured in ${paths.settingsPath} and conflicts with @thoth-agents/pi-todo. ${piIncumbentTodoRecovery(incumbentTodoSource, "user")}`
8449
+ );
8450
+ if (incumbentBlockers.length > 0)
8451
+ throw new Error(incumbentBlockers.join("\n"));
8452
+ const userPackages = resolvePiConfiguredPackages(
8453
+ userSettings,
8454
+ "user",
8455
+ options
8456
+ );
8457
+ blockers.push(...piQuestionIdentityBlockers(userPackages));
8458
+ plannedQuestionRemovals = findPiIncumbentQuestions(userPackages);
8459
+ subagentsConfigContent = `${JSON.stringify(
8460
+ mergePiSubagentsConfig(readJsonObject(paths.subagentsConfigPath)),
8461
+ null,
8462
+ 2
8463
+ )}
8464
+ `;
7684
8465
  mcpContent = `${JSON.stringify(
7685
8466
  mergePiGrepMcpConfig(readJsonObject(paths.mcpConfigPath)),
7686
8467
  null,
@@ -7690,6 +8471,40 @@ function buildPiSetupPlan(options = {}) {
7690
8471
  } catch (error) {
7691
8472
  blockers.push(error instanceof Error ? error.message : String(error));
7692
8473
  }
8474
+ const projectSettingsPath = join12(
8475
+ resolve8(options.cwd ?? process.cwd()),
8476
+ ".pi",
8477
+ "settings.json"
8478
+ );
8479
+ let projectPackages = [];
8480
+ try {
8481
+ projectPackages = readPiProjectPackages(options);
8482
+ } catch (error) {
8483
+ blockers.push(error instanceof Error ? error.message : String(error));
8484
+ }
8485
+ try {
8486
+ projectPackages.push(
8487
+ ...scanPiProjectInstalledIncumbents(options, projectPackages)
8488
+ );
8489
+ } catch (error) {
8490
+ blockers.push(error instanceof Error ? error.message : String(error));
8491
+ }
8492
+ blockers.push(...piQuestionIdentityBlockers(projectPackages));
8493
+ const projectIncumbentTodos = findPiIncumbentTodos(projectPackages);
8494
+ for (const incumbent of projectIncumbentTodos) {
8495
+ const location = incumbent.unmappedSource ? "was found in the project install roots" : `is configured in ${projectSettingsPath}`;
8496
+ const recovery = piIncumbentTodoRecovery(
8497
+ incumbent.source,
8498
+ "project",
8499
+ incumbent.identityLimitation,
8500
+ incumbent.unmappedSource
8501
+ );
8502
+ blockers.push(
8503
+ `Incumbent Pi task-list package ${incumbent.source} ${location} and conflicts with @thoth-agents/pi-todo. ${recovery}`
8504
+ );
8505
+ }
8506
+ for (const incumbent of findPiIncumbentQuestions(projectPackages))
8507
+ blockers.push(piProjectQuestionConflict(incumbent));
7693
8508
  for (const path of [...paths.projectAgentRoots, ...paths.projectMcpPaths]) {
7694
8509
  if (existsSync14(path))
7695
8510
  diagnostics.push(
@@ -7707,7 +8522,7 @@ function buildPiSetupPlan(options = {}) {
7707
8522
  const items = [
7708
8523
  {
7709
8524
  kind: "preflight",
7710
- description: "Verify Node.js >=22.19 and Pi >=0.84.4 before mutation",
8525
+ description: `Verify Node.js >=22.19 and Pi >=${PI_MINIMUM_VERSION} before mutation`,
7711
8526
  target: "node/pi runtime"
7712
8527
  },
7713
8528
  {
@@ -7719,12 +8534,26 @@ function buildPiSetupPlan(options = {}) {
7719
8534
  args: ["install", firstPartySource, "--no-approve"]
7720
8535
  }
7721
8536
  },
7722
- ...PI_PACKAGE_SPECS.map((pkg) => ({
8537
+ ...plannedQuestionRemovals.map(({ source }) => ({
8538
+ kind: "preflight",
8539
+ description: "Remove the conflicting user question provider through native Pi removal after root-package verification",
8540
+ target: source,
8541
+ command: { command: "pi", args: ["remove", source, "--no-approve"] }
8542
+ })),
8543
+ ...getPiExternalPackageSpecs(options).map((pkg) => ({
7723
8544
  kind: "package",
7724
8545
  description: `Install and verify Pi package ${pkg.source}`,
7725
8546
  target: pkg.source,
7726
8547
  command: { command: "pi", args: ["install", pkg.source, "--no-approve"] }
7727
8548
  })),
8549
+ ...subagentsConfigContent === void 0 ? [] : [
8550
+ {
8551
+ kind: "settings",
8552
+ description: "Configure global subagent lean resources and disable continuation",
8553
+ target: paths.subagentsConfigPath,
8554
+ content: subagentsConfigContent
8555
+ }
8556
+ ],
7728
8557
  ...mcpContent === void 0 ? [] : [
7729
8558
  {
7730
8559
  kind: "mcp",
@@ -7736,7 +8565,7 @@ function buildPiSetupPlan(options = {}) {
7736
8565
  ...rendered.artifacts.filter((artifact) => artifact.kind === "agent-config").map((artifact) => ({
7737
8566
  kind: "agent",
7738
8567
  description: artifact.description ?? `Install ${artifact.path}`,
7739
- target: join11(paths.piRoot, artifact.path),
8568
+ target: join12(paths.piRoot, artifact.path),
7740
8569
  content: String(artifact.content)
7741
8570
  }))
7742
8571
  ];
@@ -7747,10 +8576,12 @@ function buildPiSetupPlan(options = {}) {
7747
8576
  items,
7748
8577
  blockers,
7749
8578
  diagnostics,
8579
+ projectIncumbentTodos,
7750
8580
  disclaimers: [
7751
- "Pi extensions execute with the invoking user's system permissions; package pins and tool allowlists are not a security sandbox.",
8581
+ "Pi extensions execute with the invoking user's system permissions; package minimums and tool allowlists are not a security sandbox.",
7752
8582
  "Context7 and web access are native Pi extensions; only grep.app uses pi-mcp-adapter and directTools is intentionally omitted.",
7753
- "Project-local resources require Pi trust and may shadow global resources."
8583
+ "Project-local resources require Pi trust and may shadow global resources.",
8584
+ "Global subagents.json requests lean session resources; project-local subagents.json may override it, and full child resource mode is unsupported."
7754
8585
  ],
7755
8586
  options: { ...options, expectedVersion, packageRoot, firstPartySource }
7756
8587
  };
@@ -7801,33 +8632,108 @@ function parsePiPackageList(output) {
7801
8632
  }
7802
8633
  return packages;
7803
8634
  }
8635
+ function npmPackageName2(source) {
8636
+ if (!source.startsWith("npm:")) return void 0;
8637
+ const spec = source.slice("npm:".length);
8638
+ const separator = spec.lastIndexOf("@");
8639
+ return separator <= 0 ? spec || void 0 : spec.slice(0, separator);
8640
+ }
8641
+ var incumbentDelegationNames = /* @__PURE__ */ new Set([
8642
+ "pi-subagents",
8643
+ "pi-subagents-j0k3r"
8644
+ ]);
8645
+ function isPiLocalOrGitSource(source) {
8646
+ return /^(?:git\+|https?:\/\/|ssh:\/\/|git@|github:|file:|\.\.?[\\/]|[a-z]:[\\/]|\/)/i.test(
8647
+ source
8648
+ );
8649
+ }
8650
+ function sourceSuggestsIncumbentDelegation(source) {
8651
+ if (!isPiLocalOrGitSource(source)) return false;
8652
+ return /(?:^|[\\/:@?#._-])pi-subagents(?:-j0k3r)?(?:$|[\\/:@?#._-])/i.test(
8653
+ source
8654
+ );
8655
+ }
8656
+ function findPiIncumbentDelegation(packages) {
8657
+ for (const candidate of packages) {
8658
+ const sourceName = npmPackageName2(candidate.source);
8659
+ const { packageName: installedName } = configuredPackageIdentity(candidate);
8660
+ if (installedName && incumbentDelegationNames.has(installedName)) {
8661
+ return {
8662
+ candidate,
8663
+ classification: "confirmed",
8664
+ reason: `installed manifest identifies ${installedName}`
8665
+ };
8666
+ }
8667
+ if (sourceName && incumbentDelegationNames.has(sourceName)) {
8668
+ return {
8669
+ candidate,
8670
+ classification: "ambiguous",
8671
+ reason: installedName ? `source names ${sourceName}, but its installed manifest identifies ${installedName}` : `source names ${sourceName}, but its installed manifest is unavailable`
8672
+ };
8673
+ }
8674
+ if (!installedName && !sourceName && sourceSuggestsIncumbentDelegation(candidate.source)) {
8675
+ return {
8676
+ candidate,
8677
+ classification: "ambiguous",
8678
+ reason: "the source suggests an incumbent, but its manifest is unavailable"
8679
+ };
8680
+ }
8681
+ }
8682
+ return void 0;
8683
+ }
8684
+ function findPiIncumbentTodos(packages) {
8685
+ const incumbents = [];
8686
+ for (const candidate of packages) {
8687
+ const { packageName: installedName } = configuredPackageIdentity(candidate);
8688
+ const matches = installedName === PI_INCUMBENT_TODO_NAME || isPiIncumbentTodoSource(candidate.source) || !installedName && !npmPackageName2(candidate.source) && (isPiLocalOrGitSource(candidate.source) || candidate.scope === "project") && /(?:^|[\\/:@?#._-])rpiv-todo(?:$|[\\/:@?#._-])/i.test(
8689
+ candidate.source
8690
+ );
8691
+ if (!matches) continue;
8692
+ incumbents.push(
8693
+ candidate.scope === "project" && !installedName ? {
8694
+ ...candidate,
8695
+ identityLimitation: "Project package manifest identity is unavailable; detection relies on its source and read-only inspection cannot confirm its installed identity."
8696
+ } : candidate
8697
+ );
8698
+ }
8699
+ return incumbents;
8700
+ }
8701
+ function piIncumbentDelegationRecovery(incumbent) {
8702
+ const source = incumbent.candidate.source;
8703
+ const inspectManifest = incumbent.candidate.installedPath ? ` Inspect the installed manifest at ${incumbent.candidate.installedPath}.` : " Inspect the package manifest through Pi\u2019s package directory.";
8704
+ return incumbent.classification === "confirmed" ? `Review its ownership, then run: pi remove ${source} --no-approve. Rerun setup after verifying it is removed.` : `${incumbent.reason}.${inspectManifest} If it is an incumbent delegation runtime, run: pi remove ${source} --no-approve. Verify with pi list, then rerun setup.`;
8705
+ }
7804
8706
  function isThothPackageLocation(candidate, knownSource) {
7805
8707
  if (candidate.source === knownSource || /^npm:thoth-agents@/.test(candidate.source))
7806
8708
  return true;
7807
8709
  if (!candidate.installedPath) return false;
7808
8710
  try {
7809
8711
  const manifest = JSON.parse(
7810
- readFileSync11(join11(candidate.installedPath, "package.json"), "utf8")
8712
+ readFileSync13(join12(candidate.installedPath, "package.json"), "utf8")
7811
8713
  );
7812
8714
  return manifest.name === "thoth-agents";
7813
8715
  } catch {
7814
8716
  return false;
7815
8717
  }
7816
8718
  }
7817
- function configuredPackageIdentity(candidate) {
7818
- if (!candidate.installedPath) return { packageName: "", packageVersion: "" };
8719
+ function configuredPackageManifest(candidate) {
8720
+ if (!candidate.installedPath) return void 0;
7819
8721
  try {
7820
8722
  const manifest = JSON.parse(
7821
- readFileSync11(join11(candidate.installedPath, "package.json"), "utf8")
8723
+ readFileSync13(join12(candidate.installedPath, "package.json"), "utf8")
7822
8724
  );
7823
- return {
7824
- packageName: typeof manifest.name === "string" ? manifest.name : "",
7825
- packageVersion: typeof manifest.version === "string" ? manifest.version : ""
7826
- };
8725
+ return isRecord6(manifest) ? manifest : void 0;
7827
8726
  } catch {
7828
- return { packageName: "", packageVersion: "" };
8727
+ return void 0;
7829
8728
  }
7830
8729
  }
8730
+ function configuredPackageIdentity(candidate) {
8731
+ const manifest = configuredPackageManifest(candidate);
8732
+ return {
8733
+ packageName: typeof manifest?.name === "string" ? manifest.name : "",
8734
+ packageVersion: typeof manifest?.version === "string" ? manifest.version : ""
8735
+ };
8736
+ }
7831
8737
  function getPiFirstPartyPackages(packages, knownSource) {
7832
8738
  return packages.filter((candidate) => isThothPackageLocation(candidate, knownSource)).map((candidate) => ({
7833
8739
  ...candidate,
@@ -7835,30 +8741,25 @@ function getPiFirstPartyPackages(packages, knownSource) {
7835
8741
  }));
7836
8742
  }
7837
8743
  function matchesInstallSource(candidate, installSource) {
7838
- return isAbsolute3(installSource) ? candidate.installedPath !== void 0 && piPackagePathsEqual(candidate.installedPath, installSource) : candidate.source === installSource;
8744
+ return isAbsolute4(installSource) ? candidate.installedPath !== void 0 && piPackagePathsEqual(candidate.installedPath, installSource) : candidate.source === installSource;
7839
8745
  }
7840
8746
  function defaultCommandExecutor4(command, args) {
7841
8747
  let executable = command;
7842
8748
  let executableArgs = [...args];
7843
8749
  if (process.platform === "win32" && command === "pi") {
7844
- const located = spawnSync6("where.exe", ["pi.cmd"], { encoding: "utf8" }).stdout?.split(/\r?\n/).find(Boolean);
7845
- const cli = located ? join11(
7846
- dirname9(located),
7847
- "node_modules",
7848
- "@earendil-works",
7849
- "pi-coding-agent",
7850
- "dist",
7851
- "bundle",
7852
- "cli.js"
7853
- ) : void 0;
7854
- if (cli && existsSync14(cli)) {
8750
+ const cli = findPiWindowsCli();
8751
+ if (cli) {
7855
8752
  executable = process.execPath;
7856
8753
  executableArgs = [cli, ...args];
7857
8754
  }
7858
8755
  }
7859
8756
  const result = spawnSync6(executable, executableArgs, {
8757
+ windowsHide: true,
7860
8758
  encoding: "utf8",
7861
- timeout: PI_COMMAND_TIMEOUT_MS
8759
+ timeout: PI_COMMAND_TIMEOUT_MS,
8760
+ // Like the SDK's cross-spawn, support npm/pnpm's Windows command shims
8761
+ // for the offline metadata queries used by package resolution.
8762
+ shell: process.platform === "win32" && /^(npm|pnpm)(\.cmd)?$/i.test(basename3(executable))
7862
8763
  });
7863
8764
  return {
7864
8765
  exitCode: result.status,
@@ -7867,21 +8768,6 @@ function defaultCommandExecutor4(command, args) {
7867
8768
  error: result.error
7868
8769
  };
7869
8770
  }
7870
- function writePiManagedText(path, content) {
7871
- if (existsSync14(path) && readFileSync11(path, "utf8") === content) return false;
7872
- mkdirSync7(dirname9(path), { recursive: true });
7873
- const temporaryPath = `${path}.tmp`;
7874
- const backupPath = `${path}.bak`;
7875
- writeFileSync7(temporaryPath, content);
7876
- try {
7877
- if (existsSync14(path)) copyFileSync5(path, backupPath);
7878
- renameSync6(temporaryPath, path);
7879
- } catch (error) {
7880
- rmSync6(temporaryPath, { force: true });
7881
- throw error;
7882
- }
7883
- return true;
7884
- }
7885
8771
  function applyPiSetup(plan) {
7886
8772
  const diagnostics = [...plan.diagnostics];
7887
8773
  if (!plan.ready)
@@ -7900,7 +8786,14 @@ function applyPiSetup(plan) {
7900
8786
  const installedPackages = [];
7901
8787
  let receiptCommitted = false;
7902
8788
  let configuredPackageRoot;
8789
+ let manualRecovery;
7903
8790
  try {
8791
+ for (const path of [
8792
+ plan.paths.settingsPath,
8793
+ plan.paths.subagentsConfigPath,
8794
+ plan.paths.mcpConfigPath
8795
+ ])
8796
+ assertSafePiManagedPath(path);
7904
8797
  const node = execute("node", ["--version"]);
7905
8798
  if (node.exitCode !== 0 || !isVersionAtLeast(node.stdout.trim(), PI_NODE_MINIMUM))
7906
8799
  throw new Error(
@@ -7923,7 +8816,104 @@ function applyPiSetup(plan) {
7923
8816
  ...plan.options.receiptOptions
7924
8817
  };
7925
8818
  const receipt = readPiPackageReceipt(receiptOptions);
7926
- const configuredBefore = parsePiPackageList(before.stdout);
8819
+ let configuredBefore;
8820
+ try {
8821
+ configuredBefore = resolvePiListedPackages(before.stdout, plan.options);
8822
+ } catch (error) {
8823
+ return {
8824
+ success: false,
8825
+ changed,
8826
+ diagnostics,
8827
+ error: error instanceof Error ? error.message : String(error),
8828
+ failedStep: "preflight",
8829
+ installedPackages
8830
+ };
8831
+ }
8832
+ const incumbentDelegation = findPiIncumbentDelegation(configuredBefore);
8833
+ let configuredProjectPackages;
8834
+ try {
8835
+ configuredProjectPackages = readPiProjectPackages(plan.options);
8836
+ configuredProjectPackages.push(
8837
+ ...scanPiProjectInstalledIncumbents(
8838
+ plan.options,
8839
+ configuredProjectPackages
8840
+ )
8841
+ );
8842
+ } catch (error) {
8843
+ return {
8844
+ success: false,
8845
+ changed,
8846
+ diagnostics,
8847
+ error: error instanceof Error ? error.message : String(error),
8848
+ failedStep: "preflight",
8849
+ installedPackages
8850
+ };
8851
+ }
8852
+ const projectQuestions = findPiIncumbentQuestions([
8853
+ ...configuredBefore.filter(({ scope }) => scope === "project"),
8854
+ ...configuredProjectPackages
8855
+ ]);
8856
+ if (projectQuestions.length > 0) {
8857
+ const recovery = projectQuestions.map(piProjectQuestionConflict).join("\n");
8858
+ return {
8859
+ success: false,
8860
+ changed,
8861
+ diagnostics,
8862
+ error: recovery,
8863
+ failedStep: "preflight",
8864
+ installedPackages,
8865
+ manualRecovery: recovery
8866
+ };
8867
+ }
8868
+ const unpreviewedQuestions = piUnpreviewedQuestionRecovery(
8869
+ plan,
8870
+ findPiIncumbentQuestions(configuredBefore)
8871
+ );
8872
+ if (unpreviewedQuestions)
8873
+ return {
8874
+ success: false,
8875
+ changed,
8876
+ diagnostics,
8877
+ error: unpreviewedQuestions,
8878
+ failedStep: "preflight",
8879
+ installedPackages,
8880
+ manualRecovery: unpreviewedQuestions
8881
+ };
8882
+ const incumbentTodos = findPiIncumbentTodos([
8883
+ ...configuredBefore,
8884
+ ...configuredProjectPackages
8885
+ ]);
8886
+ if (incumbentDelegation || incumbentTodos.length > 0) {
8887
+ const incumbentBlockers = [];
8888
+ const recovery = [];
8889
+ if (incumbentDelegation) {
8890
+ incumbentBlockers.push(
8891
+ `Pi delegation runtime preflight blocked ${incumbentDelegation.candidate.source}: ${incumbentDelegation.reason}. Loading it beside ${PI_PACKAGE_SPECS[0].source} is unsupported.`
8892
+ );
8893
+ recovery.push(piIncumbentDelegationRecovery(incumbentDelegation));
8894
+ }
8895
+ for (const incumbentTodo of incumbentTodos) {
8896
+ const todoRecovery = piIncumbentTodoRecovery(
8897
+ incumbentTodo.source,
8898
+ incumbentTodo.scope,
8899
+ incumbentTodo.identityLimitation,
8900
+ incumbentTodo.unmappedSource
8901
+ );
8902
+ incumbentBlockers.push(
8903
+ `Pi task-list preflight blocked ${incumbentTodo.source}: it conflicts with @thoth-agents/pi-todo. ${todoRecovery}`
8904
+ );
8905
+ recovery.push(todoRecovery);
8906
+ }
8907
+ return {
8908
+ success: false,
8909
+ changed,
8910
+ diagnostics,
8911
+ error: incumbentBlockers.join("\n"),
8912
+ failedStep: "preflight",
8913
+ installedPackages,
8914
+ manualRecovery: `Manual recovery: ${recovery.join(" ")}`
8915
+ };
8916
+ }
7927
8917
  const knownReceiptSource = receipt.status === "valid" ? receipt.receipt.source : void 0;
7928
8918
  const firstPartyBefore = getPiFirstPartyPackages(
7929
8919
  configuredBefore,
@@ -7944,6 +8934,20 @@ function applyPiSetup(plan) {
7944
8934
  throw new Error(
7945
8935
  `First-party Pi package ownership conflict: ${ownership.reason ?? ownership.state}`
7946
8936
  );
8937
+ const identityRecovery = piQuestionIdentityBlockers(
8938
+ [...configuredBefore, ...configuredProjectPackages],
8939
+ plan
8940
+ ).join("\n");
8941
+ if (identityRecovery)
8942
+ return {
8943
+ success: false,
8944
+ changed,
8945
+ diagnostics,
8946
+ error: identityRecovery,
8947
+ failedStep: "preflight",
8948
+ installedPackages,
8949
+ manualRecovery: identityRecovery
8950
+ };
7947
8951
  const installed = execute("pi", ["install", desiredSource, "--no-approve"]);
7948
8952
  if (installed.exitCode !== 0)
7949
8953
  throw new Error(
@@ -7973,7 +8977,7 @@ function applyPiSetup(plan) {
7973
8977
  `Pi did not verify the exact installed package source ${desiredSource}.`
7974
8978
  );
7975
8979
  const resolvedPackageRoot = candidates[0]?.installedPath;
7976
- if (!resolvedPackageRoot || !isAbsolute3(resolvedPackageRoot) || !existsSync14(resolvedPackageRoot) || !lstatSync3(resolvedPackageRoot).isDirectory() || lstatSync3(resolvedPackageRoot).isSymbolicLink())
8980
+ if (!resolvedPackageRoot || !isAbsolute4(resolvedPackageRoot) || !existsSync14(resolvedPackageRoot) || !lstatSync4(resolvedPackageRoot).isDirectory() || lstatSync4(resolvedPackageRoot).isSymbolicLink())
7977
8981
  throw new Error(
7978
8982
  `Pi did not report one absolute regular installed directory for ${desiredSource}.`
7979
8983
  );
@@ -8051,25 +9055,130 @@ function applyPiSetup(plan) {
8051
9055
  if (!migration.success)
8052
9056
  throw new Error(migration.error ?? "Pi legacy migration failed.");
8053
9057
  changed.push(...migration.changed);
8054
- for (const pkg of PI_PACKAGE_SPECS) {
8055
- const result = execute("pi", ["install", pkg.source, "--no-approve"]);
8056
- if (result.exitCode !== 0)
9058
+ const questionListing = execute("pi", ["list", "--no-approve"]);
9059
+ if (questionListing.exitCode !== 0)
9060
+ throw new Error(
9061
+ `Unable to inspect conflicting Pi question packages: ${questionListing.stderr.trim() || "pi list unavailable"}`
9062
+ );
9063
+ const questionPackages = resolvePiListedPackages(
9064
+ questionListing.stdout,
9065
+ plan.options
9066
+ );
9067
+ const freshProjectQuestions = findPiIncumbentQuestions(
9068
+ questionPackages
9069
+ ).filter(({ scope }) => scope === "project");
9070
+ const questionRecovery = freshProjectQuestions.length > 0 ? freshProjectQuestions.map(piProjectQuestionConflict).join("\n") : piUnpreviewedQuestionRecovery(
9071
+ plan,
9072
+ findPiIncumbentQuestions(questionPackages)
9073
+ ) ?? (piQuestionIdentityBlockers(questionPackages, plan).join("\n") || void 0);
9074
+ if (questionRecovery) {
9075
+ manualRecovery = questionRecovery;
9076
+ throw new Error(questionRecovery);
9077
+ }
9078
+ const incumbentQuestions = findPiIncumbentQuestions(
9079
+ questionPackages
9080
+ ).filter(({ scope }) => scope === "user");
9081
+ for (const incumbent of incumbentQuestions) {
9082
+ const unpreviewed = piUnpreviewedQuestionRecovery(plan, [incumbent]);
9083
+ if (unpreviewed) {
9084
+ manualRecovery = unpreviewed;
9085
+ throw new Error(unpreviewed);
9086
+ }
9087
+ const removed = execute("pi", [
9088
+ "remove",
9089
+ incumbent.source,
9090
+ "--no-approve"
9091
+ ]);
9092
+ if (removed.exitCode !== 0)
8057
9093
  throw new Error(
8058
- `Failed to install ${pkg.source}: ${result.stderr.trim() || "unknown Pi error"}`
9094
+ `Failed to remove conflicting Pi question package ${incumbent.source}: ${removed.stderr.trim() || "unknown Pi error"}`
8059
9095
  );
9096
+ }
9097
+ if (incumbentQuestions.length > 0) {
9098
+ const afterRemoval = execute("pi", ["list", "--no-approve"]);
9099
+ const remaining = resolvePiListedPackages(
9100
+ afterRemoval.stdout,
9101
+ plan.options
9102
+ );
9103
+ const identityRecovery2 = piQuestionIdentityBlockers(remaining, plan).join(
9104
+ "\n"
9105
+ );
9106
+ if (identityRecovery2) {
9107
+ manualRecovery = identityRecovery2;
9108
+ throw new Error(identityRecovery2);
9109
+ }
9110
+ if (afterRemoval.exitCode !== 0 || findPiIncumbentQuestions(remaining).some(
9111
+ ({ scope }) => scope === "user"
9112
+ ) || remaining.some(
9113
+ ({ scope, source }) => scope === "user" && incumbentQuestions.some((incumbent) => incumbent.source === source)
9114
+ ))
9115
+ throw new Error(
9116
+ "Pi did not verify removal of the conflicting user question package; rerun pi list and remove it before retrying setup."
9117
+ );
9118
+ }
9119
+ for (const pkg of getPiExternalPackageSpecs(plan.options)) {
9120
+ const beforeInstall = execute("pi", ["list", "--no-approve"]);
9121
+ if (beforeInstall.exitCode !== 0)
9122
+ throw new Error(
9123
+ `Unable to inspect ${pkg.packageName} before installation: ${beforeInstall.stderr.trim() || "pi list unavailable"}`
9124
+ );
9125
+ const prior = inspectPiExternalPackage(
9126
+ parsePiPackageList(beforeInstall.stdout),
9127
+ pkg,
9128
+ false
9129
+ );
9130
+ const priorSourceMatches = prior.state === "installed" && prior.source !== void 0 && prior.installedPath !== void 0 && piExternalSourceMatches(
9131
+ {
9132
+ scope: "user",
9133
+ source: prior.source,
9134
+ installedPath: prior.installedPath
9135
+ },
9136
+ pkg.source
9137
+ );
9138
+ if (!priorSourceMatches) {
9139
+ const result = execute("pi", ["install", pkg.source, "--no-approve"]);
9140
+ if (result.exitCode !== 0)
9141
+ throw new Error(
9142
+ `Failed to install ${pkg.source}: ${result.stderr.trim() || "unknown Pi error"}`
9143
+ );
9144
+ }
8060
9145
  const listed = execute("pi", ["list", "--no-approve"]);
8061
- if (listed.exitCode !== 0 || !hasExactInstalledPiPackage(listed.stdout, pkg.source)) {
9146
+ const verified = listed.exitCode === 0 ? inspectPiExternalPackage(parsePiPackageList(listed.stdout), pkg) : {
9147
+ state: "drift",
9148
+ version: void 0,
9149
+ reason: listed.stderr.trim() || "pi list unavailable"
9150
+ };
9151
+ if (prior.state === "installed" && verified.version !== void 0 && lt(verified.version, prior.version)) {
9152
+ const restoreSource = prior.source ?? `npm:${pkg.packageName}@${prior.version}`;
9153
+ const restored = execute("pi", [
9154
+ "install",
9155
+ restoreSource,
9156
+ "--no-approve"
9157
+ ]);
9158
+ const restoredListing = execute("pi", ["list", "--no-approve"]);
9159
+ const restoredEvidence = restored.exitCode === 0 && restoredListing.exitCode === 0 ? inspectPiExternalPackage(
9160
+ parsePiPackageList(restoredListing.stdout),
9161
+ pkg,
9162
+ false
9163
+ ) : void 0;
9164
+ const recoverySucceeded = restoredEvidence?.state === "installed" && restoredEvidence.source === prior.source && restoredEvidence.version === prior.version;
9165
+ const recoveryGuidance = `Manual recovery: run pi install ${restoreSource} --no-approve, then verify with pi list and the installed package manifest.`;
9166
+ manualRecovery = recoveryGuidance;
8062
9167
  throw new Error(
8063
- `Pi did not verify the exact installed package source ${pkg.source}.`
9168
+ `Pi attempted to downgrade ${pkg.packageName} from ${prior.version} to ${verified.version}; exact-version recovery ${recoverySucceeded ? "verified" : "failed or unverifiable"}. ${recoveryGuidance}`
8064
9169
  );
8065
9170
  }
9171
+ if (verified.state !== "installed")
9172
+ throw new Error(`Pi did not verify ${pkg.source}: ${verified.reason}.`);
8066
9173
  installedPackages.push(pkg.source);
8067
9174
  }
8068
9175
  for (const item of plan.items.filter(
8069
- (candidate) => candidate.content !== void 0 && candidate.kind === "mcp"
9176
+ (candidate) => candidate.content !== void 0 && (candidate.kind === "settings" || candidate.kind === "mcp")
8070
9177
  )) {
8071
- if (writePiManagedText(item.target, item.content ?? ""))
8072
- changed.push(item.target);
9178
+ const content = item.target === plan.paths.subagentsConfigPath ? `${JSON.stringify(mergePiSubagentsConfig(readJsonObject(item.target)), null, 2)}
9179
+ ` : item.target === plan.paths.mcpConfigPath ? `${JSON.stringify(mergePiGrepMcpConfig(readJsonObject(item.target)), null, 2)}
9180
+ ` : item.content ?? "";
9181
+ if (writePiManagedText(item.target, content)) changed.push(item.target);
8073
9182
  }
8074
9183
  const resources = syncPiSpecialists({
8075
9184
  packageRoot: configuredPackageRoot,
@@ -8098,7 +9207,8 @@ function applyPiSetup(plan) {
8098
9207
  error: error instanceof Error ? error.message : String(error),
8099
9208
  failedStep: installedPackages.length < plan.items.filter((item) => item.kind === "package").length ? "package" : "managed-surface",
8100
9209
  installedPackages,
8101
- receiptCommitted
9210
+ receiptCommitted,
9211
+ manualRecovery
8102
9212
  };
8103
9213
  }
8104
9214
  }
@@ -8178,7 +9288,11 @@ function disclaimers() {
8178
9288
  },
8179
9289
  {
8180
9290
  code: "pi-runtime-owned",
8181
- message: "Pi and pi-subagents own execution, concurrency, task/history storage, trust, and lifecycle."
9291
+ message: `Pi and ${PI_PACKAGE_SPECS[0].packageName} own execution, concurrency, task/history storage, trust, and lifecycle.`
9292
+ },
9293
+ {
9294
+ code: "pi-lean-resources-global-only",
9295
+ message: "Global subagents.json requests lean session resources; project-local subagents.json may override it, and full child resource mode is unsupported."
8182
9296
  },
8183
9297
  {
8184
9298
  code: "pi-research-independent",
@@ -8238,6 +9352,12 @@ function runtimeTarget(provider, evidence) {
8238
9352
  description: evidence.basis.join("; ")
8239
9353
  };
8240
9354
  }
9355
+ function incumbentDelegationRecovery(incumbent) {
9356
+ return `Incumbent Pi delegation runtime ${incumbent.candidate.source} conflicts with ${PI_PACKAGE_SPECS[0].source}. ${piIncumbentDelegationRecovery(incumbent)}`;
9357
+ }
9358
+ function incumbentTodoRecovery(incumbent) {
9359
+ return `Incumbent Pi task-list package ${incumbent.source} conflicts with @thoth-agents/pi-todo. ${piIncumbentTodoRecovery(incumbent.source, incumbent.scope, incumbent.identityLimitation, incumbent.unmappedSource)}`;
9360
+ }
8241
9361
  function runtimeDiagnostic(provider, evidence) {
8242
9362
  if (evidence.state === "ready") return void 0;
8243
9363
  const code = `pi-${provider}-runtime-${evidence.state}`;
@@ -8253,12 +9373,13 @@ function statusFromPlan(plan, context2, evidence = {}) {
8253
9373
  kind: item.kind === "package" ? "package" : item.kind === "agent" ? "file" : item.kind === "mcp" ? "config" : "file",
8254
9374
  path: item.target,
8255
9375
  label: item.description,
8256
- state: item.kind === "package" ? "unknown" : existsSync15(item.target) ? item.content === void 0 || readFileSync12(item.target, "utf8") === item.content ? "installed" : "drift" : "missing",
9376
+ state: item.kind === "package" ? "unknown" : existsSync15(item.target) ? item.content === void 0 || readFileSync14(item.target, "utf8") === item.content ? "installed" : "drift" : "missing",
8257
9377
  expected: item.kind === "package" ? item.target : "attributable managed content",
8258
- description: item.kind === "package" ? "Exact installed-package evidence; this does not prove live tool availability." : void 0
9378
+ description: item.kind === "package" ? "Validated installed-package manifest evidence; this does not prove live tool availability." : void 0
8259
9379
  }));
8260
9380
  const execute = context2.piCommandExecutor ?? ((command, args) => {
8261
9381
  const result = spawnSync7(command, [...args], {
9382
+ windowsHide: true,
8262
9383
  encoding: "utf8",
8263
9384
  timeout: 5e3,
8264
9385
  env: {
@@ -8294,19 +9415,64 @@ function statusFromPlan(plan, context2, evidence = {}) {
8294
9415
  observed: pi.stdout.trim() || pi.stderr.trim() || "unavailable"
8295
9416
  }
8296
9417
  );
9418
+ const configuredPackages = packages.exitCode === 0 ? parsePiPackageList(packages.stdout) : [];
9419
+ const incumbentDelegation = findPiIncumbentDelegation(configuredPackages);
9420
+ const incumbentTodos = [
9421
+ ...findPiIncumbentTodos(configuredPackages),
9422
+ ...plan.projectIncumbentTodos ?? []
9423
+ ];
9424
+ const packageSpecs = getPiExternalPackageSpecs(plan.options);
8297
9425
  for (const target of targets.filter(
8298
- (candidate) => candidate.kind === "package" && candidate.path?.startsWith("npm:")
9426
+ (candidate) => candidate.kind === "package" && packageSpecs.some(({ source }) => source === candidate.path)
8299
9427
  )) {
8300
- const packageName = target.path?.replace(/^npm:/, "").replace(/@[^@]+$/, "");
8301
- target.state = packages.exitCode !== 0 ? "unknown" : hasExactInstalledPiPackage(packages.stdout, target.path ?? "") ? "installed" : packageName && packages.stdout.includes(packageName) ? "drift" : "missing";
8302
- target.observed = packages.exitCode === 0 ? target.state : packages.stderr.trim() || "pi list unavailable";
9428
+ const externalSpec = packageSpecs.find(
9429
+ ({ source }) => source === target.path
9430
+ );
9431
+ if (packages.exitCode !== 0) {
9432
+ target.state = "unknown";
9433
+ target.observed = packages.stderr.trim() || "pi list unavailable";
9434
+ } else if (externalSpec) {
9435
+ const inspected = inspectPiExternalPackage(
9436
+ configuredPackages,
9437
+ externalSpec,
9438
+ externalSpec.id !== "delegation" || plan.options.runtimePackageRoot !== void 0
9439
+ );
9440
+ target.state = inspected.state;
9441
+ target.observed = inspected.state === "installed" ? inspected.version : inspected.reason;
9442
+ } else {
9443
+ const packageName = target.path?.replace(/^npm:/, "").replace(/@[^@]+$/, "");
9444
+ target.state = hasExactInstalledPiPackage(
9445
+ packages.stdout,
9446
+ target.path ?? ""
9447
+ ) ? "installed" : packageName && packages.stdout.includes(packageName) ? "drift" : "missing";
9448
+ target.observed = target.state;
9449
+ }
8303
9450
  }
9451
+ if (incumbentDelegation)
9452
+ targets.push({
9453
+ kind: "package",
9454
+ path: incumbentDelegation.candidate.source,
9455
+ label: "Pi incumbent delegation runtime",
9456
+ state: "drift",
9457
+ expected: `not configured alongside ${PI_PACKAGE_SPECS[0].packageName}`,
9458
+ observed: incumbentDelegation.candidate.source,
9459
+ description: incumbentDelegationRecovery(incumbentDelegation)
9460
+ });
9461
+ for (const incumbentTodo of incumbentTodos)
9462
+ targets.push({
9463
+ kind: "package",
9464
+ path: incumbentTodo.source,
9465
+ label: "Pi incumbent task-list package",
9466
+ state: "drift",
9467
+ expected: "not configured alongside @thoth-agents/pi-todo",
9468
+ observed: incumbentTodo.source,
9469
+ description: incumbentTodoRecovery(incumbentTodo)
9470
+ });
8304
9471
  const receiptOptions = context2.installLedgerOptions ?? {
8305
9472
  env: context2.env,
8306
9473
  homeDir: context2.homeDir
8307
9474
  };
8308
9475
  const receipt = readPiPackageReceipt(receiptOptions);
8309
- const configuredPackages = packages.exitCode === 0 ? parsePiPackageList(packages.stdout) : [];
8310
9476
  const firstPartyPackages = getPiFirstPartyPackages(
8311
9477
  configuredPackages,
8312
9478
  receipt.status === "valid" ? receipt.receipt.source : void 0
@@ -8341,7 +9507,7 @@ function statusFromPlan(plan, context2, evidence = {}) {
8341
9507
  const configuredPackage = configuredCandidates[0];
8342
9508
  const configuredPath = configuredPackage?.installedPath;
8343
9509
  const localPathMatches = receipt.receipt.source === receipt.receipt.installSource || configuredPath !== void 0 && piPackagePathsEqual(configuredPath, receipt.receipt.installSource);
8344
- const configured = ownership.state === "owned-current" && configuredCandidates.length === 1 && configuredPath !== void 0 && isAbsolute4(configuredPath) && localPathMatches;
9510
+ const configured = ownership.state === "owned-current" && configuredCandidates.length === 1 && configuredPath !== void 0 && isAbsolute5(configuredPath) && localPathMatches;
8345
9511
  if (configured) validatedPackageRoot = configuredPath;
8346
9512
  targets.push({
8347
9513
  kind: "surface",
@@ -8351,11 +9517,11 @@ function statusFromPlan(plan, context2, evidence = {}) {
8351
9517
  expected: `${receipt.receipt.source} -> ${receipt.receipt.installSource}`,
8352
9518
  observed: configuredPackage ? `${configuredPackage.source} -> ${configuredPath ?? "unavailable"}` : "unavailable"
8353
9519
  });
8354
- const extensionPath = configuredPath ? join12(configuredPath, "dist", "pi.js") : void 0;
8355
- const manifestPath = configuredPath ? join12(configuredPath, "package.json") : void 0;
9520
+ const extensionPath = configuredPath ? join13(configuredPath, "dist", "pi.js") : void 0;
9521
+ const manifestPath = configuredPath ? join13(configuredPath, "package.json") : void 0;
8356
9522
  let receiptMatches = false;
8357
9523
  if (extensionPath && manifestPath && existsSync15(extensionPath) && existsSync15(manifestPath))
8358
- receiptMatches = createHash5("sha256").update(readFileSync12(extensionPath)).digest("hex") === receipt.receipt.extensionSha256 && createHash5("sha256").update(readFileSync12(manifestPath)).digest("hex") === receipt.receipt.manifestSha256;
9524
+ receiptMatches = createHash6("sha256").update(readFileSync14(extensionPath)).digest("hex") === receipt.receipt.extensionSha256 && createHash6("sha256").update(readFileSync14(manifestPath)).digest("hex") === receipt.receipt.manifestSha256;
8359
9525
  targets.push({
8360
9526
  kind: "surface",
8361
9527
  path: extensionPath,
@@ -8382,7 +9548,7 @@ function statusFromPlan(plan, context2, evidence = {}) {
8382
9548
  } : void 0);
8383
9549
  return {
8384
9550
  kind: "skill",
8385
- path: join12(skill.destinationPath, "SKILL.md"),
9551
+ path: join13(skill.destinationPath, "SKILL.md"),
8386
9552
  label: `Pi package-declared skill: ${skill.name}`,
8387
9553
  state: issue?.state ?? "installed",
8388
9554
  ...issue ? { observed: issue.message } : {}
@@ -8418,6 +9584,33 @@ function statusFromPlan(plan, context2, evidence = {}) {
8418
9584
  ...plan.blockers.map(
8419
9585
  (message) => warning3(message, "pi-preflight-blocked", "critical")
8420
9586
  ),
9587
+ ...incumbentDelegation && !plan.blockers.some(
9588
+ (message) => message.includes(incumbentDelegation.candidate.source)
9589
+ ) ? [
9590
+ warning3(
9591
+ incumbentDelegationRecovery(incumbentDelegation),
9592
+ "pi-incumbent-delegation-conflict",
9593
+ "critical"
9594
+ )
9595
+ ] : [],
9596
+ ...incumbentTodos.filter(
9597
+ (incumbentTodo) => !plan.blockers.some(
9598
+ (message) => message.includes(incumbentTodo.source) && message.includes(
9599
+ piIncumbentTodoRecovery(
9600
+ incumbentTodo.source,
9601
+ incumbentTodo.scope,
9602
+ incumbentTodo.identityLimitation,
9603
+ incumbentTodo.unmappedSource
9604
+ )
9605
+ )
9606
+ )
9607
+ ).map(
9608
+ (incumbentTodo) => warning3(
9609
+ incumbentTodoRecovery(incumbentTodo),
9610
+ "pi-incumbent-todo-conflict",
9611
+ "critical"
9612
+ )
9613
+ ),
8421
9614
  ...plan.diagnostics.map(
8422
9615
  (message) => warning3(message, "pi-resource-shadowing")
8423
9616
  ),
@@ -8458,6 +9651,7 @@ function contextPlan(context2, dryRun = true) {
8458
9651
  homeDir: context2.homeDir,
8459
9652
  commandExecutor: context2.piCommandExecutor,
8460
9653
  packageRoot: context2.packageRoot ?? (version.ok ? version.packageRoot : void 0),
9654
+ runtimePackageRoot: context2.runtimePackageRoot,
8461
9655
  expectedVersion: version.ok ? version.version : void 0,
8462
9656
  receiptOptions: context2.installLedgerOptions
8463
9657
  });
@@ -8477,7 +9671,7 @@ function configuredPackageRootFromStatus(status) {
8477
9671
  const configured = status.targets.find(
8478
9672
  ({ label }) => label === "First-party Pi configured source"
8479
9673
  );
8480
- return ownership?.observed === "owned-current" && configured?.state === "installed" && configured.path && isAbsolute4(configured.path) ? configured.path : void 0;
9674
+ return ownership?.observed === "owned-current" && configured?.state === "installed" && configured.path && isAbsolute5(configured.path) ? configured.path : void 0;
8481
9675
  }
8482
9676
  function piPlan(action, context2) {
8483
9677
  const setup = contextPlan(context2);
@@ -8503,7 +9697,7 @@ function piPlan(action, context2) {
8503
9697
  items.push({
8504
9698
  title: "Inspect five package-declared Pi skills",
8505
9699
  target: { kind: "skill", label: "Pi package-declared skills" },
8506
- preview: configuredPackageRoot ? getPiOwnedSkillEntries({ packageRoot: configuredPackageRoot }).map((entry) => join12(entry.destinationPath, "SKILL.md")).join(", ") : "unavailable until the first-party Pi package is receipt-validated"
9700
+ preview: configuredPackageRoot ? getPiOwnedSkillEntries({ packageRoot: configuredPackageRoot }).map((entry) => join13(entry.destinationPath, "SKILL.md")).join(", ") : "unavailable until the first-party Pi package is receipt-validated"
8507
9701
  });
8508
9702
  if (complete) {
8509
9703
  items.push(
@@ -8554,6 +9748,25 @@ function piPlan(action, context2) {
8554
9748
  observed: `${ownershipState}; remove the configured first-party package manually or restore its matching receipt before applying ${action}.`
8555
9749
  }
8556
9750
  ] : [];
9751
+ const incumbentDelegation = status.targets.find(
9752
+ ({ label }) => label === "Pi incumbent delegation runtime"
9753
+ );
9754
+ const delegationBlockers = incumbentDelegation ? [
9755
+ {
9756
+ kind: "package",
9757
+ path: incumbentDelegation.path,
9758
+ label: "Pi delegation runtime blocker",
9759
+ state: "drift",
9760
+ observed: incumbentDelegation.description ?? `${incumbentDelegation.observed} conflicts with ${PI_PACKAGE_SPECS[0].source}. Remove it manually before applying ${action}.`
9761
+ }
9762
+ ] : [];
9763
+ const todoBlockers = status.targets.filter(({ label }) => label === "Pi incumbent task-list package").map((incumbentTodo) => ({
9764
+ kind: "package",
9765
+ path: incumbentTodo.path,
9766
+ label: "Pi task-list package blocker",
9767
+ state: "drift",
9768
+ observed: incumbentTodo.description ?? `${incumbentTodo.observed} conflicts with @thoth-agents/pi-todo. Remove it manually before applying ${action}.`
9769
+ }));
8557
9770
  const syncSkillsUnavailable = action === "sync" && (!configuredPackageRoot || status.targets.some(
8558
9771
  ({ label, state }) => label?.startsWith("Pi package-declared skill:") && state !== "installed"
8559
9772
  ));
@@ -8572,7 +9785,7 @@ function piPlan(action, context2) {
8572
9785
  title: `${action === "install" ? "Install" : action === "update" ? "Update" : "Sync"} Pi`,
8573
9786
  summary: complete ? "Preview complete pinned Pi packages, owned resources, skills, provider setup, and final ledger commit." : "Preview only attributable Pi root, specialist, package-skill evidence, and grep.app surfaces.",
8574
9787
  dryRun: true,
8575
- canApply: setup.ready && (!complete || version.ok) && ownershipBlockers.length === 0 && syncSkillBlockers.length === 0,
9788
+ canApply: setup.ready && (!complete || version.ok) && ownershipBlockers.length === 0 && delegationBlockers.length === 0 && todoBlockers.length === 0 && syncSkillBlockers.length === 0,
8576
9789
  targets: status.targets,
8577
9790
  blockerTargets: [
8578
9791
  ...setup.blockers.map((message) => ({
@@ -8582,6 +9795,8 @@ function piPlan(action, context2) {
8582
9795
  observed: message
8583
9796
  })),
8584
9797
  ...ownershipBlockers,
9798
+ ...delegationBlockers,
9799
+ ...todoBlockers,
8585
9800
  ...syncSkillBlockers
8586
9801
  ],
8587
9802
  surfaces: setup.items.filter((item) => item.kind !== "preflight").map((item) => ({
@@ -8629,17 +9844,18 @@ function defaultPiModelRoles(context2 = { cwd: process.cwd() }) {
8629
9844
  (role) => isPiSpecialistRole(role.name)
8630
9845
  ).map((role) => {
8631
9846
  const path = plan.items.find(
8632
- (item) => item.kind === "agent" && basename3(item.target) === `${piSpecialistName(role.name)}.md`
9847
+ (item) => item.kind === "agent" && basename4(item.target) === `${piSpecialistName(role.name)}.md`
8633
9848
  )?.target;
8634
- const content = path && existsSync15(path) ? readFileSync12(path, "utf8") : "";
8635
- const model = /^model:\s*["']?([^"'\r\n]+)/m.exec(content)?.[1]?.trim();
9849
+ if (path && existsSync15(path)) {
9850
+ const configured = readPiModelConfig(dirname11(dirname11(path)), [
9851
+ role.name
9852
+ ]).roles[0];
9853
+ if (configured) return configured;
9854
+ }
8636
9855
  return {
8637
9856
  role: role.name,
8638
- model: model && model !== "default" ? model : "inherit",
8639
- effort: (() => {
8640
- const value = /^effort:\s*["']?([^"'\r\n]+)/m.exec(content)?.[1]?.trim();
8641
- return value && value !== "default" && value !== "inherit" ? { kind: "effort", value } : { kind: "inherit" };
8642
- })()
9857
+ model: "inherit",
9858
+ effort: { kind: "inherit" }
8643
9859
  };
8644
9860
  });
8645
9861
  }
@@ -8657,7 +9873,7 @@ function buildPiModelPlan(input, context2 = { cwd: process.cwd() }) {
8657
9873
  target: {
8658
9874
  kind: "file",
8659
9875
  path: specialist ? setup.items.find(
8660
- (item) => item.kind === "agent" && basename3(item.target) === `${specialist}.md`
9876
+ (item) => item.kind === "agent" && basename4(item.target) === `${specialist}.md`
8661
9877
  )?.target : void 0,
8662
9878
  label: `Pi ${role.role} specialist`
8663
9879
  },
@@ -8697,26 +9913,35 @@ function buildPiModelPlan(input, context2 = { cwd: process.cwd() }) {
8697
9913
  ],
8698
9914
  disclaimers: disclaimers()
8699
9915
  };
8700
- planSources.set(plan, { setup, context: context2, model: input });
9916
+ let modelSnapshot;
9917
+ if (!invalid && input.roles.length > 0) {
9918
+ const path = items[0]?.target.path;
9919
+ if (path && items.every((item) => item.target.path && existsSync15(item.target.path))) {
9920
+ try {
9921
+ modelSnapshot = readPiModelConfig(
9922
+ dirname11(dirname11(path)),
9923
+ input.roles.map(({ role }) => role)
9924
+ );
9925
+ } catch (error) {
9926
+ plan.canApply = false;
9927
+ plan.warnings.push(
9928
+ warning3(
9929
+ error instanceof Error ? error.message : String(error),
9930
+ "pi-model-config-invalid",
9931
+ "critical"
9932
+ )
9933
+ );
9934
+ }
9935
+ }
9936
+ }
9937
+ planSources.set(plan, {
9938
+ setup,
9939
+ context: context2,
9940
+ model: structuredClone(input),
9941
+ modelSnapshot
9942
+ });
8701
9943
  return plan;
8702
9944
  }
8703
- function replaceFrontmatterField(content, field, value) {
8704
- const newline = content.includes("\r\n") ? "\r\n" : "\n";
8705
- const lines = content.split(/\r?\n/);
8706
- if (lines[0]?.trim() !== "---") return content;
8707
- const end = lines.findIndex(
8708
- (line, index2) => index2 > 0 && line.trim() === "---"
8709
- );
8710
- if (end === -1) return content;
8711
- const prefix = `${field}:`;
8712
- const index = lines.slice(1, end).findIndex((line) => line.startsWith(prefix));
8713
- const absoluteIndex = index === -1 ? -1 : index + 1;
8714
- const nativeValue = value === void 0 || value === "inherit" ? "default" : value;
8715
- const replacement = `${field}: ${JSON.stringify(nativeValue)}`;
8716
- if (absoluteIndex === -1) lines.splice(1, 0, replacement);
8717
- else lines[absoluteIndex] = replacement;
8718
- return lines.join(newline);
8719
- }
8720
9945
  function applyPiPlan(plan) {
8721
9946
  const source = planSources.get(plan);
8722
9947
  const fileTargets = (paths) => paths.map((path) => ({ kind: "file", path, state: "installed" }));
@@ -8738,28 +9963,23 @@ function applyPiPlan(plan) {
8738
9963
  "Pi operation plan is not applicable or was not built in this process."
8739
9964
  );
8740
9965
  if (plan.action === "model-config" && source.model) {
8741
- const changedTargets = [];
8742
- for (const role of source.model.roles) {
8743
- const target = plan.items.find((item) => item.title.endsWith(role.role))?.target.path;
8744
- if (!target || !existsSync15(target))
8745
- return reject(
8746
- `Owned Pi specialist definition is missing: ${role.role}.`
8747
- );
8748
- let content = readFileSync12(target, "utf8");
8749
- content = replaceFrontmatterField(content, "model", role.model);
8750
- content = replaceFrontmatterField(
8751
- content,
8752
- "effort",
8753
- role.effort?.kind === "effort" ? role.effort.value : void 0
9966
+ if (!source.modelSnapshot)
9967
+ return reject(
9968
+ "Owned Pi specialist definitions were missing at preview. Reopen model configuration."
9969
+ );
9970
+ const snapshot = source.modelSnapshot;
9971
+ const result = savePiModelConfig(snapshot, source.model.roles);
9972
+ const changedTargets = fileTargets(
9973
+ result.changedRoles.map(
9974
+ (role) => join13(snapshot.piRoot, "agents", `${piSpecialistName(role)}.md`)
9975
+ )
9976
+ );
9977
+ source.modelSnapshot = result.snapshot;
9978
+ if (!result.success)
9979
+ return reject(
9980
+ result.error ?? "Pi model configuration failed.",
9981
+ changedTargets
8754
9982
  );
8755
- writePiManagedText(target, content);
8756
- changedTargets.push({
8757
- kind: "file",
8758
- path: target,
8759
- label: `Pi ${role.role} specialist`,
8760
- state: "installed"
8761
- });
8762
- }
8763
9983
  return {
8764
9984
  harness: "pi",
8765
9985
  action: plan.action,
@@ -8833,7 +10053,7 @@ function applyPiPlan(plan) {
8833
10053
  changedTargets: fileTargets(changed),
8834
10054
  diagnosticTargets: packageSkills.skills.map((skill) => ({
8835
10055
  kind: "skill",
8836
- path: join12(skill.destinationPath, "SKILL.md"),
10056
+ path: join13(skill.destinationPath, "SKILL.md"),
8837
10057
  label: `Pi package-declared skill: ${skill.name}`,
8838
10058
  state: "installed"
8839
10059
  })),
@@ -8920,7 +10140,7 @@ function applyPiPlan(plan) {
8920
10140
  ],
8921
10141
  diagnosticTargets: packageSkills.skills.map((skill) => ({
8922
10142
  kind: "skill",
8923
- path: join12(skill.destinationPath, "SKILL.md"),
10143
+ path: join13(skill.destinationPath, "SKILL.md"),
8924
10144
  label: `Pi package-declared skill: ${skill.name}`,
8925
10145
  state: "installed"
8926
10146
  })),
@@ -8957,14 +10177,14 @@ import { execFileSync } from "child_process";
8957
10177
  // src/cli/model-catalog/cache.ts
8958
10178
  import {
8959
10179
  existsSync as existsSync16,
8960
- mkdirSync as mkdirSync8,
8961
- readFileSync as readFileSync13,
8962
- renameSync as renameSync7,
10180
+ mkdirSync as mkdirSync7,
10181
+ readFileSync as readFileSync15,
10182
+ renameSync as renameSync6,
8963
10183
  unlinkSync,
8964
- writeFileSync as writeFileSync8
10184
+ writeFileSync as writeFileSync7
8965
10185
  } from "fs";
8966
10186
  import { homedir as homedir7 } from "os";
8967
- import { dirname as dirname10, posix, win32 } from "path";
10187
+ import { dirname as dirname12, posix, win32 as win322 } from "path";
8968
10188
  import { z as z2 } from "zod";
8969
10189
  var modelCatalogSourceSchema = z2.enum(["remote", "lkg", "manual"]);
8970
10190
  var cachedModelOptionSchema = z2.object({
@@ -8986,7 +10206,7 @@ function resolveModelsDevCachePath(options = {}) {
8986
10206
  const env = options.env ?? process.env;
8987
10207
  const home = options.homeDir ?? homedir7();
8988
10208
  if (platform === "win32") {
8989
- const path = win32;
10209
+ const path = win322;
8990
10210
  const base2 = env.LOCALAPPDATA ?? env.APPDATA ?? path.join(home, "AppData", "Local");
8991
10211
  return path.join(base2, "thoth-agents", "models-dev-v1.json");
8992
10212
  }
@@ -8996,7 +10216,7 @@ function resolveModelsDevCachePath(options = {}) {
8996
10216
  function readModelsDevCache(cachePath) {
8997
10217
  if (!existsSync16(cachePath)) return void 0;
8998
10218
  try {
8999
- const input = JSON.parse(readFileSync13(cachePath, "utf8"));
10219
+ const input = JSON.parse(readFileSync15(cachePath, "utf8"));
9000
10220
  const parsed = modelsDevCacheSchema.safeParse(input);
9001
10221
  return parsed.success ? parsed.data : void 0;
9002
10222
  } catch {
@@ -9008,15 +10228,15 @@ function writeModelsDevCacheAtomic(cachePath, cache) {
9008
10228
  if (!validated.success) {
9009
10229
  throw new Error(`Invalid models.dev cache: ${validated.error.message}`);
9010
10230
  }
9011
- mkdirSync8(dirname10(cachePath), { recursive: true });
10231
+ mkdirSync7(dirname12(cachePath), { recursive: true });
9012
10232
  const temporaryPath = `${cachePath}.${process.pid}.${Date.now()}.tmp`;
9013
10233
  try {
9014
- writeFileSync8(
10234
+ writeFileSync7(
9015
10235
  temporaryPath,
9016
10236
  `${JSON.stringify(validated.data, null, 2)}
9017
10237
  `
9018
10238
  );
9019
- renameSync7(temporaryPath, cachePath);
10239
+ renameSync6(temporaryPath, cachePath);
9020
10240
  } catch (error) {
9021
10241
  if (existsSync16(temporaryPath)) unlinkSync(temporaryPath);
9022
10242
  throw error;
@@ -9211,7 +10431,7 @@ var CODEX_DOCUMENTED_EFFORTS2 = /* @__PURE__ */ new Set([
9211
10431
  "ultra"
9212
10432
  ]);
9213
10433
  var CLAUDE_CODE_EFFORTS2 = ["low", "medium", "high", "xhigh", "max"];
9214
- var PI_EFFORTS2 = new Set(PI_EFFORT_VALUES);
10434
+ var PI_EFFORTS = new Set(PI_EFFORT_VALUES);
9215
10435
  function parseOpenCodeModels(output) {
9216
10436
  const seen = /* @__PURE__ */ new Set();
9217
10437
  const options = [];
@@ -9248,7 +10468,7 @@ function writableEfforts(harness, option) {
9248
10468
  );
9249
10469
  }
9250
10470
  if (harness === "pi") {
9251
- return option.efforts.filter((effort) => PI_EFFORTS2.has(effort));
10471
+ return option.efforts.filter((effort) => PI_EFFORTS.has(effort));
9252
10472
  }
9253
10473
  return option.efforts.filter(
9254
10474
  (effort) => resolveOpenCodeEffort({
@@ -9281,7 +10501,10 @@ function getNativeOpenCodeOptions() {
9281
10501
  try {
9282
10502
  const invocation = getOpenCodeModelsInvocation();
9283
10503
  return parseOpenCodeModels(
9284
- execFileSync(invocation.command, invocation.args, invocation.options)
10504
+ execFileSync(invocation.command, invocation.args, {
10505
+ ...invocation.options,
10506
+ windowsHide: true
10507
+ })
9285
10508
  );
9286
10509
  } catch {
9287
10510
  return [];
@@ -9341,7 +10564,7 @@ async function getModelOptions(harness) {
9341
10564
  }
9342
10565
 
9343
10566
  // src/cli/tui/operations.ts
9344
- import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
10567
+ import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
9345
10568
 
9346
10569
  // src/cli/model-defaults.ts
9347
10570
  function getShippedModelRoles(harness) {
@@ -9407,7 +10630,7 @@ function getCodexModelRoles(source = codexContext) {
9407
10630
  const item = plan.items.find(
9408
10631
  (candidate) => candidate.action === "write-role-toml" && candidate.role === role
9409
10632
  );
9410
- const content = item && existsSync17(item.targetPath) ? readFileSync14(item.targetPath, "utf8") : item?.content;
10633
+ const content = item && existsSync17(item.targetPath) ? readFileSync16(item.targetPath, "utf8") : item?.content;
9411
10634
  const effort = content ? parseRoleTomlEffort(content) : void 0;
9412
10635
  return {
9413
10636
  role,
@@ -9607,3 +10830,9 @@ export {
9607
10830
  getOpenCodeModelRoles,
9608
10831
  defaultTuiOperations
9609
10832
  };
10833
+ /*! @license
10834
+ * Portions ported from @earendil-works/pi-coding-agent 1.0.2
10835
+ * (dist/utils/git.js:parseGitUrl and its helpers).
10836
+ * Copyright (c) 2025 Mario Zechner. MIT License; see the third-party notice
10837
+ * in this package's LICENSE file.
10838
+ */