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.
- package/LICENSE +30 -0
- package/README.md +86 -58
- package/dist/agents/prompt-sections.d.ts +5 -3
- package/dist/agents/prompt-utils.d.ts +1 -1
- package/dist/agents/worker.d.ts +2 -0
- package/dist/{chunk-STCURXAQ.js → chunk-MRFCA3U2.js} +33 -67
- package/dist/{chunk-EFGSGPYM.js → chunk-OW6I7ZUU.js} +391 -416
- package/dist/chunk-V7HSGRMB.js +1016 -0
- package/dist/{chunk-WCLUZZHI.js → chunk-VUNYH263.js} +1580 -351
- package/dist/cli/claude-code-paths.d.ts +2 -2
- package/dist/cli/codex-install.d.ts +5 -2
- package/dist/cli/codex-paths.d.ts +2 -2
- package/dist/cli/index.js +47 -11
- package/dist/cli/operations/pi.d.ts +1 -0
- package/dist/cli/pi-effort.d.ts +1 -1
- package/dist/cli/pi-external-package.d.ts +24 -0
- package/dist/cli/pi-git-source.d.ts +1 -0
- package/dist/cli/pi-install.d.ts +92 -15
- package/dist/cli/pi-managed-write.d.ts +3 -0
- package/dist/cli/pi-model-config.d.ts +17 -0
- package/dist/cli/pi-native-probe.d.ts +2 -0
- package/dist/cli/pi-paths.d.ts +2 -0
- package/dist/cli/pi-resources.d.ts +3 -1
- package/dist/cli/pi-tool-config.d.ts +32 -0
- package/dist/cli/tui/index.js +3 -3
- package/dist/cli/types.d.ts +2 -0
- package/dist/config/constants.d.ts +4 -8
- package/dist/config/schema.d.ts +7 -36
- package/dist/harness/adapters/claude-code.d.ts +2 -4
- package/dist/harness/core/agent-pack.d.ts +52 -42
- package/dist/harness/core/memory-governance.d.ts +2 -2
- package/dist/harness/core/sdd.d.ts +159 -319
- package/dist/harness/writers/pi-agent.d.ts +3 -1
- package/dist/index.js +12 -6
- package/dist/pi/tools-panel.d.ts +51 -0
- package/dist/pi.d.ts +44 -2
- package/dist/pi.js +471 -9
- package/dist/utils/subprocess.d.ts +1 -0
- package/package.json +23 -6
- package/pi/.thoth-agents-assets.json +5 -6
- package/pi/agents/thoth-designer.md +23 -8
- package/pi/agents/thoth-explorer.md +29 -9
- package/pi/agents/thoth-librarian.md +30 -8
- package/pi/agents/thoth-oracle.md +18 -10
- package/pi/agents/thoth-worker.md +96 -0
- package/plugin/.claude-plugin/.thoth-agents-plugin-assets.json +7 -12
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/.codex-plugin/plugin.json +2 -2
- package/plugin/agents/designer.md +13 -5
- package/plugin/agents/explorer.md +16 -5
- package/plugin/agents/librarian.md +15 -4
- package/plugin/agents/oracle.md +13 -7
- package/plugin/agents/orchestrator.md +82 -91
- package/plugin/agents/worker.md +76 -0
- package/plugin/skills/plan-reviewer/SKILL.md +39 -141
- package/plugin/skills/thoth-archive/SKILL.md +50 -47
- package/plugin/skills/thoth-archive/scripts/archive.mjs +287 -654
- package/plugin/skills/thoth-constitution/SKILL.md +23 -42
- package/plugin/skills/thoth-constitution/templates/constitution.md +54 -44
- package/plugin/skills/thoth-init/SKILL.md +19 -29
- package/plugin/skills/thoth-init/scripts/init.mjs +73 -66
- package/plugin/skills/thoth-sdd/SKILL.md +164 -117
- package/plugin/skills/thoth-sdd/references/phases/checklist.md +2 -22
- package/plugin/skills/thoth-sdd/references/phases/clarify.md +14 -12
- package/plugin/skills/thoth-sdd/references/phases/converge.md +2 -13
- package/plugin/skills/thoth-sdd/references/phases/explore.md +3 -8
- package/plugin/skills/thoth-sdd/references/phases/implement.md +58 -57
- package/plugin/skills/thoth-sdd/references/phases/plan.md +2 -18
- package/plugin/skills/thoth-sdd/references/phases/specify.md +2 -33
- package/plugin/skills/thoth-sdd/references/phases/tasks.md +4 -45
- package/plugin/skills/thoth-sdd/references/phases/verify.md +4 -22
- package/plugin/skills/thoth-sdd/scripts/validate.mjs +610 -1232
- package/plugin/skills/thoth-sdd/templates/change.md +94 -0
- package/skills/README.md +11 -13
- package/skills/plan-reviewer/SKILL.md +39 -141
- package/skills/thoth-archive/SKILL.md +50 -47
- package/skills/thoth-archive/scripts/archive.mjs +287 -654
- package/skills/thoth-constitution/SKILL.md +23 -42
- package/skills/thoth-constitution/templates/constitution.md +54 -44
- package/skills/thoth-init/SKILL.md +19 -29
- package/skills/thoth-init/scripts/init.mjs +73 -66
- package/skills/thoth-sdd/SKILL.md +164 -117
- package/skills/thoth-sdd/references/phases/checklist.md +2 -22
- package/skills/thoth-sdd/references/phases/clarify.md +14 -12
- package/skills/thoth-sdd/references/phases/converge.md +2 -13
- package/skills/thoth-sdd/references/phases/explore.md +3 -8
- package/skills/thoth-sdd/references/phases/implement.md +58 -57
- package/skills/thoth-sdd/references/phases/plan.md +2 -18
- package/skills/thoth-sdd/references/phases/specify.md +2 -33
- package/skills/thoth-sdd/references/phases/tasks.md +4 -45
- package/skills/thoth-sdd/references/phases/verify.md +4 -22
- package/skills/thoth-sdd/scripts/validate.mjs +610 -1232
- package/skills/thoth-sdd/templates/change.md +94 -0
- package/thoth-agents.schema.json +9 -77
- package/dist/agents/deep.d.ts +0 -2
- package/dist/agents/quick.d.ts +0 -2
- package/dist/chunk-2U5MBLLV.js +0 -312
- package/pi/agents/thoth-deep.md +0 -81
- package/pi/agents/thoth-quick.md +0 -81
- package/plugin/agents/deep.md +0 -68
- package/plugin/agents/quick.md +0 -68
- package/plugin/skills/plan-reviewer/templates/plan-review.md +0 -65
- package/plugin/skills/thoth-sdd/templates/archive-report.md +0 -25
- package/plugin/skills/thoth-sdd/templates/checklist.md +0 -27
- package/plugin/skills/thoth-sdd/templates/plan.md +0 -46
- package/plugin/skills/thoth-sdd/templates/spec.md +0 -62
- package/plugin/skills/thoth-sdd/templates/tasks.md +0 -58
- package/plugin/skills/thoth-sdd/templates/verify-report.md +0 -28
- package/skills/plan-reviewer/templates/plan-review.md +0 -65
- package/skills/thoth-sdd/templates/archive-report.md +0 -25
- package/skills/thoth-sdd/templates/checklist.md +0 -27
- package/skills/thoth-sdd/templates/plan.md +0 -46
- package/skills/thoth-sdd/templates/spec.md +0 -62
- package/skills/thoth-sdd/templates/tasks.md +0 -58
- 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
|
-
|
|
13
|
-
|
|
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-
|
|
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
|
-
"
|
|
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 {
|
|
97
|
-
import {
|
|
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
|
|
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
|
-
"-
|
|
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
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
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
|
|
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:${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
`-
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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(
|
|
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
|
-
|
|
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, {
|
|
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
|
|
3817
|
+
rmSync as rmSync3
|
|
3622
3818
|
} from "fs";
|
|
3623
|
-
import { isAbsolute, join as join4, relative, resolve as
|
|
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
|
|
3893
|
+
function lstatIfPresent2(path) {
|
|
3698
3894
|
try {
|
|
3699
|
-
return
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3988
|
+
const target = lstatIfPresent2(path);
|
|
3793
3989
|
if (!target) return null;
|
|
3794
|
-
const home =
|
|
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 =
|
|
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 =
|
|
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 }) => !
|
|
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 =
|
|
4393
|
+
const exactPath = resolve3(
|
|
4198
4394
|
plan.codexHome,
|
|
4199
4395
|
...definition.relativePath.split("/")
|
|
4200
4396
|
);
|
|
4201
|
-
if (
|
|
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 (!
|
|
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
|
-
|
|
4218
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
5338
|
+
rmSync as rmSync4,
|
|
5126
5339
|
statSync
|
|
5127
5340
|
} from "fs";
|
|
5128
5341
|
import { homedir as homedir4 } from "os";
|
|
5129
|
-
import { dirname as
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
5493
|
+
rmSync4(backupPath, { recursive: true, force: true });
|
|
5281
5494
|
}
|
|
5282
5495
|
}
|
|
5283
5496
|
} finally {
|
|
5284
|
-
|
|
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:
|
|
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: `
|
|
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
|
|
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
|
|
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
|
|
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: "
|
|
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
|
|
6961
|
-
import { existsSync as existsSync15, readFileSync as
|
|
6962
|
-
import { basename as
|
|
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-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
"
|
|
6970
|
-
"
|
|
6971
|
-
"
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
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
|
-
|
|
6979
|
-
|
|
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
|
-
|
|
6982
|
-
|
|
7218
|
+
state: "drift",
|
|
7219
|
+
reason: "project package shadows the required global package"
|
|
6983
7220
|
};
|
|
6984
|
-
}
|
|
6985
|
-
if (
|
|
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
|
-
|
|
6988
|
-
|
|
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
|
-
|
|
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
|
|
7294
|
+
import { createHash as createHash5 } from "crypto";
|
|
6997
7295
|
import {
|
|
6998
|
-
copyFileSync as copyFileSync5,
|
|
6999
7296
|
existsSync as existsSync14,
|
|
7000
|
-
lstatSync as
|
|
7001
|
-
mkdirSync as mkdirSync7,
|
|
7297
|
+
lstatSync as lstatSync4,
|
|
7002
7298
|
readdirSync as readdirSync3,
|
|
7003
|
-
readFileSync as
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
writeFileSync as writeFileSync7
|
|
7299
|
+
readFileSync as readFileSync13,
|
|
7300
|
+
realpathSync as realpathSync3,
|
|
7301
|
+
statSync as statSync3
|
|
7007
7302
|
} from "fs";
|
|
7008
|
-
import { dirname as
|
|
7009
|
-
import { fileURLToPath as fileURLToPath5
|
|
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
|
|
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
|
|
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 =
|
|
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] &&
|
|
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 =
|
|
7495
|
+
const appendPath = join8(options.piRoot, "APPEND_SYSTEM.md");
|
|
7050
7496
|
let nextRoot;
|
|
7051
7497
|
try {
|
|
7052
7498
|
if (existsSync11(appendPath))
|
|
7053
|
-
nextRoot = removeRootBlock(
|
|
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 =
|
|
7505
|
+
const source = join8(options.piRoot, "skills", name);
|
|
7060
7506
|
if (!existsSync11(source)) continue;
|
|
7061
|
-
const canonical =
|
|
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(
|
|
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 {
|
|
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
|
|
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
|
|
7122
|
-
|
|
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(
|
|
7154
|
-
const observerPath =
|
|
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
|
-
|
|
7209
|
-
|
|
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:'
|
|
7213
|
-
|
|
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
|
-
|
|
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
|
|
7283
|
-
return
|
|
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
|
-
|
|
7303
|
-
else
|
|
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
|
|
7795
|
+
readFileSync as readFileSync12,
|
|
7312
7796
|
renameSync as renameSync5,
|
|
7313
|
-
rmSync as
|
|
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
|
|
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(
|
|
7333
|
-
const normalizedRight = normalize(
|
|
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() ??
|
|
7339
|
-
return
|
|
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
|
|
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 && !
|
|
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:
|
|
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 =
|
|
7381
|
-
mkdirSync6(
|
|
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
|
-
|
|
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
|
|
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 =
|
|
7912
|
+
const homeDir = resolve7(
|
|
7429
7913
|
options.homeDir ?? env.HOME ?? env.USERPROFILE ?? homedir6()
|
|
7430
7914
|
);
|
|
7431
|
-
const defaultPiRoot =
|
|
7915
|
+
const defaultPiRoot = join11(homeDir, ".pi", "agent");
|
|
7432
7916
|
const configured = env.PI_CODING_AGENT_DIR?.trim();
|
|
7433
|
-
const piRoot = configured ?
|
|
7434
|
-
const cwd =
|
|
7435
|
-
const configRoot = env.XDG_CONFIG_HOME?.trim() ?
|
|
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:
|
|
7443
|
-
agentsRoot:
|
|
7444
|
-
alternateAgentsRoot:
|
|
7445
|
-
ownedSkillsRoot:
|
|
7446
|
-
|
|
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
|
-
|
|
7449
|
-
|
|
7934
|
+
join11(cwd, ".pi", "agents"),
|
|
7935
|
+
join11(cwd, ".pi", "subagents")
|
|
7450
7936
|
],
|
|
7451
|
-
projectMcpPaths: [
|
|
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.
|
|
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
|
|
7463
|
-
packageName: "pi-subagents
|
|
7464
|
-
version: "1.
|
|
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
|
|
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
|
|
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
|
|
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:@
|
|
7487
|
-
packageName: "@
|
|
7488
|
-
version: "
|
|
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:@
|
|
7493
|
-
packageName: "@
|
|
7494
|
-
version: "
|
|
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(
|
|
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
|
-
|
|
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
|
|
8008
|
+
return createHash5("sha256").update(readFileSync13(path)).digest("hex");
|
|
7523
8009
|
}
|
|
7524
8010
|
function verifyPiFirstPartyPackage(input) {
|
|
7525
8011
|
try {
|
|
7526
|
-
const manifestPath =
|
|
7527
|
-
const extensionPath =
|
|
7528
|
-
const assetsPath =
|
|
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) || !
|
|
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(
|
|
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(
|
|
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 =
|
|
7551
|
-
if (!existsSync14(path) || !
|
|
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 =
|
|
7558
|
-
if (!existsSync14(path) || !
|
|
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(
|
|
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 =
|
|
7650
|
-
const content =
|
|
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:
|
|
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
|
-
...
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
7818
|
-
if (!candidate.installedPath) return
|
|
8719
|
+
function configuredPackageManifest(candidate) {
|
|
8720
|
+
if (!candidate.installedPath) return void 0;
|
|
7819
8721
|
try {
|
|
7820
8722
|
const manifest = JSON.parse(
|
|
7821
|
-
|
|
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
|
|
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
|
|
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
|
|
7845
|
-
|
|
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
|
-
|
|
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 || !
|
|
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
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
8072
|
-
|
|
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:
|
|
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 ||
|
|
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" ? "
|
|
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
|
|
9426
|
+
(candidate) => candidate.kind === "package" && packageSpecs.some(({ source }) => source === candidate.path)
|
|
8299
9427
|
)) {
|
|
8300
|
-
const
|
|
8301
|
-
|
|
8302
|
-
|
|
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 &&
|
|
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 ?
|
|
8355
|
-
const manifestPath = configuredPath ?
|
|
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 =
|
|
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:
|
|
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 &&
|
|
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) =>
|
|
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" &&
|
|
9847
|
+
(item) => item.kind === "agent" && basename4(item.target) === `${piSpecialistName(role.name)}.md`
|
|
8633
9848
|
)?.target;
|
|
8634
|
-
|
|
8635
|
-
|
|
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:
|
|
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" &&
|
|
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
|
-
|
|
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
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
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:
|
|
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:
|
|
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
|
|
8961
|
-
readFileSync as
|
|
8962
|
-
renameSync as
|
|
10180
|
+
mkdirSync as mkdirSync7,
|
|
10181
|
+
readFileSync as readFileSync15,
|
|
10182
|
+
renameSync as renameSync6,
|
|
8963
10183
|
unlinkSync,
|
|
8964
|
-
writeFileSync as
|
|
10184
|
+
writeFileSync as writeFileSync7
|
|
8965
10185
|
} from "fs";
|
|
8966
10186
|
import { homedir as homedir7 } from "os";
|
|
8967
|
-
import { dirname as
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
10231
|
+
mkdirSync7(dirname12(cachePath), { recursive: true });
|
|
9012
10232
|
const temporaryPath = `${cachePath}.${process.pid}.${Date.now()}.tmp`;
|
|
9013
10233
|
try {
|
|
9014
|
-
|
|
10234
|
+
writeFileSync7(
|
|
9015
10235
|
temporaryPath,
|
|
9016
10236
|
`${JSON.stringify(validated.data, null, 2)}
|
|
9017
10237
|
`
|
|
9018
10238
|
);
|
|
9019
|
-
|
|
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
|
|
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) =>
|
|
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,
|
|
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
|
|
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) ?
|
|
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
|
+
*/
|