thoth-agents 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-2D7E7GMP.js → chunk-VUNYH263.js} +1650 -380
- 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/model-defaults.d.ts +4 -0
- 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 +32 -7
- package/dist/cli/tui/operations.d.ts +3 -1
- 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 +18 -4
- 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,
|
|
@@ -19,6 +25,7 @@ import {
|
|
|
19
25
|
CONFIRMED_OPENAI_SUBAGENT_PRESET,
|
|
20
26
|
DEFAULT_MODELS,
|
|
21
27
|
PI_SPECIALIST_ROLES,
|
|
28
|
+
SUBAGENT_NAMES,
|
|
22
29
|
claudeCodeSubagentType,
|
|
23
30
|
ensureConfigDir,
|
|
24
31
|
findPackageRoot,
|
|
@@ -40,7 +47,7 @@ import {
|
|
|
40
47
|
updateOpenCodeMainConfig,
|
|
41
48
|
writeConfig,
|
|
42
49
|
writeLiteConfig
|
|
43
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-OW6I7ZUU.js";
|
|
44
51
|
|
|
45
52
|
// src/cli/codex-paths.ts
|
|
46
53
|
import { homedir } from "os";
|
|
@@ -50,8 +57,7 @@ var CODEX_ROLE_NAMES = [
|
|
|
50
57
|
"librarian",
|
|
51
58
|
"oracle",
|
|
52
59
|
"designer",
|
|
53
|
-
"
|
|
54
|
-
"deep"
|
|
60
|
+
"worker"
|
|
55
61
|
];
|
|
56
62
|
function getCodexHome(options = {}) {
|
|
57
63
|
const explicit = options.codexHome ?? process.env.CODEX_HOME?.trim();
|
|
@@ -92,8 +98,9 @@ function isExplicitEffort(selection) {
|
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
// src/cli/codex-install.ts
|
|
95
|
-
import {
|
|
96
|
-
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";
|
|
97
104
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
98
105
|
|
|
99
106
|
// src/harness/adapters/codex.ts
|
|
@@ -1019,7 +1026,7 @@ function createCodexPluginPackageManifest(context2) {
|
|
|
1019
1026
|
return {
|
|
1020
1027
|
name: "thoth-agents",
|
|
1021
1028
|
version: readRootPackageVersion(context2),
|
|
1022
|
-
description: "Bundled
|
|
1029
|
+
description: "Bundled AI-first work skills and MCP configuration; the six-role global Codex layer requires thoth-agents CLI setup."
|
|
1023
1030
|
};
|
|
1024
1031
|
}
|
|
1025
1032
|
function stableJson2(value) {
|
|
@@ -1064,7 +1071,7 @@ function codexRuntimeGuidance() {
|
|
|
1064
1071
|
return [
|
|
1065
1072
|
"<codex-runtime>",
|
|
1066
1073
|
"- The ambient Codex session is the adaptive root; no orchestrator child TOML is generated.",
|
|
1067
|
-
"-
|
|
1074
|
+
"- When delegation is selected under the shared ownership policy, use `collaboration.spawn_agent`; honor explicit direct-work instructions.",
|
|
1068
1075
|
"- Collaboration tools are direct tools and must not be called from inside `functions.exec`.",
|
|
1069
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.",
|
|
1070
1077
|
"- Keep maximum depth 1: children do not delegate. Use one writer per mutable surface and parallelize only independent work.",
|
|
@@ -1496,6 +1503,90 @@ function readManagedModelState(path, version) {
|
|
|
1496
1503
|
var ROOT_START = "<!-- thoth-agents:codex-root:start -->";
|
|
1497
1504
|
var ROOT_END = "<!-- thoth-agents:codex-root:end -->";
|
|
1498
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
|
+
}
|
|
1499
1590
|
function mergeManagedBlock(existing, managedBlock) {
|
|
1500
1591
|
const start = existing.indexOf(ROOT_START);
|
|
1501
1592
|
const end = existing.indexOf(ROOT_END);
|
|
@@ -1713,8 +1804,82 @@ function buildCodexSetupPlan(config) {
|
|
|
1713
1804
|
...packageRoot ? { packageRoot } : {}
|
|
1714
1805
|
});
|
|
1715
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);
|
|
1716
1815
|
const managedModelState2 = readManagedModelState2(targets.managedModelsPath);
|
|
1816
|
+
const recovery = readInstallRecovery(recoveryPath);
|
|
1717
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
|
+
);
|
|
1718
1883
|
const items = [
|
|
1719
1884
|
{
|
|
1720
1885
|
kind: "root-instructions",
|
|
@@ -1724,34 +1889,24 @@ function buildCodexSetupPlan(config) {
|
|
|
1724
1889
|
requiresBackup: true,
|
|
1725
1890
|
content: rootBlock
|
|
1726
1891
|
},
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
content: resolveRoleTomlContent({
|
|
1739
|
-
renderedContent: roleArtifactContent(target.role, render.artifacts),
|
|
1740
|
-
targetPath: target.path,
|
|
1741
|
-
state: managedModelState2,
|
|
1742
|
-
nextState: nextManagedModelState,
|
|
1743
|
-
reset: config.reset
|
|
1744
|
-
})
|
|
1745
|
-
})
|
|
1746
|
-
),
|
|
1747
|
-
{
|
|
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({
|
|
1748
1903
|
kind: "managed-model-state",
|
|
1749
1904
|
action: "write-managed-model-state",
|
|
1750
1905
|
targetPath: targets.managedModelsPath,
|
|
1751
1906
|
description: "Record thoth-agents-managed Codex role model ownership state.",
|
|
1752
1907
|
requiresBackup: existsSync4(targets.managedModelsPath),
|
|
1753
1908
|
content: stableJson3(nextManagedModelState)
|
|
1754
|
-
},
|
|
1909
|
+
}),
|
|
1755
1910
|
{
|
|
1756
1911
|
kind: "user-config",
|
|
1757
1912
|
action: "merge-toml",
|
|
@@ -1801,8 +1956,32 @@ function applyCodexSetup(plan) {
|
|
|
1801
1956
|
]);
|
|
1802
1957
|
if (plan.dryRun) return { success: true, changed, diagnostics };
|
|
1803
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
|
+
}
|
|
1804
1971
|
for (const item of plan.items) {
|
|
1805
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
|
+
}
|
|
1806
1985
|
if (item.action === "merge-toml") {
|
|
1807
1986
|
const result = writeCodexConfigMerge({
|
|
1808
1987
|
configPath: item.targetPath,
|
|
@@ -1815,12 +1994,29 @@ function applyCodexSetup(plan) {
|
|
|
1815
1994
|
continue;
|
|
1816
1995
|
}
|
|
1817
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
|
+
}
|
|
1818
2001
|
const content = item.action === "merge-managed-block" ? mergeManagedBlock(
|
|
1819
2002
|
existsSync4(item.targetPath) ? readFileSync4(item.targetPath, "utf8") : "",
|
|
1820
2003
|
item.content
|
|
1821
2004
|
) : item.content;
|
|
1822
2005
|
if (writeTextWithBackup(item.targetPath, content))
|
|
1823
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
|
+
}
|
|
1824
2020
|
}
|
|
1825
2021
|
return { success: true, changed, diagnostics: uniqueMessages(diagnostics) };
|
|
1826
2022
|
} catch (error) {
|
|
@@ -1840,7 +2036,7 @@ import { existsSync as existsSync7 } from "fs";
|
|
|
1840
2036
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1841
2037
|
|
|
1842
2038
|
// src/harness/writers/claude-code-plugin-package.ts
|
|
1843
|
-
import { createHash as
|
|
2039
|
+
import { createHash as createHash3 } from "crypto";
|
|
1844
2040
|
var CLAUDE_PLUGIN_MANIFEST_FIELDS = [
|
|
1845
2041
|
"name",
|
|
1846
2042
|
"version",
|
|
@@ -1857,7 +2053,7 @@ function stableJson4(value) {
|
|
|
1857
2053
|
`;
|
|
1858
2054
|
}
|
|
1859
2055
|
function sha256Hash(content) {
|
|
1860
|
-
return `sha256:${
|
|
2056
|
+
return `sha256:${createHash3("sha256").update(content).digest("hex")}`;
|
|
1861
2057
|
}
|
|
1862
2058
|
function withoutProviderAsset(artifact) {
|
|
1863
2059
|
if (!normalizePath2(artifact.path).endsWith(".mcp.json")) return artifact;
|
|
@@ -1951,14 +2147,12 @@ var CLAUDE_CODE_SUBAGENT_DEFAULT_MODELS = {
|
|
|
1951
2147
|
librarian: "sonnet",
|
|
1952
2148
|
oracle: "opus",
|
|
1953
2149
|
designer: "sonnet",
|
|
1954
|
-
|
|
1955
|
-
deep: "sonnet"
|
|
2150
|
+
worker: "sonnet"
|
|
1956
2151
|
};
|
|
1957
2152
|
var CLAUDE_CODE_SUBAGENT_DEFAULT_EFFORTS = {
|
|
1958
2153
|
explorer: "low",
|
|
1959
|
-
quick: "low",
|
|
1960
2154
|
designer: "medium",
|
|
1961
|
-
|
|
2155
|
+
worker: "medium",
|
|
1962
2156
|
librarian: "high",
|
|
1963
2157
|
oracle: "high"
|
|
1964
2158
|
};
|
|
@@ -2012,7 +2206,7 @@ function renderClaudeCodeRootInstructions(config) {
|
|
|
2012
2206
|
rootPrompt,
|
|
2013
2207
|
"<claude-code-runtime>",
|
|
2014
2208
|
"- You are the Claude Code adaptive root activated by plugin settings.json.",
|
|
2015
|
-
`-
|
|
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.`,
|
|
2016
2210
|
"- Subagents cannot delegate further. Parallelize only independent work and maintain one writer per mutable surface.",
|
|
2017
2211
|
"- Read-only roles deny Write and Edit while retaining other inherited tools, including MCP tools. Coordination-agent path scope remains instruction-level.",
|
|
2018
2212
|
"- Use AskUserQuestion only for blocking material choices and TodoWrite only for genuine multi-step progress.",
|
|
@@ -2055,7 +2249,7 @@ function createPluginManifest(context2) {
|
|
|
2055
2249
|
return {
|
|
2056
2250
|
name: "thoth-agents",
|
|
2057
2251
|
version: readRootPackageVersion2(context2),
|
|
2058
|
-
description: "Adaptive multi-harness agent pack with
|
|
2252
|
+
description: "Adaptive multi-harness agent pack with six roles and proportional SDD governance for Claude Code.",
|
|
2059
2253
|
author: { name: "thoth-agents" }
|
|
2060
2254
|
};
|
|
2061
2255
|
}
|
|
@@ -2088,7 +2282,7 @@ function renderOrchestratorArtifact(config) {
|
|
|
2088
2282
|
);
|
|
2089
2283
|
const content = renderClaudeCodeSubagent({
|
|
2090
2284
|
name: "orchestrator",
|
|
2091
|
-
description: orchestrator?.responsibility ?? "Adaptive root coordinator for
|
|
2285
|
+
description: orchestrator?.responsibility ?? "Adaptive root coordinator for human agreement, SDD classification, and specialist dispatch.",
|
|
2092
2286
|
model: "inherit",
|
|
2093
2287
|
instructions: renderClaudeCodeRootInstructions(config)
|
|
2094
2288
|
});
|
|
@@ -2145,8 +2339,7 @@ var CLAUDE_CODE_ROLE_NAMES = [
|
|
|
2145
2339
|
"librarian",
|
|
2146
2340
|
"oracle",
|
|
2147
2341
|
"designer",
|
|
2148
|
-
"
|
|
2149
|
-
"deep"
|
|
2342
|
+
"worker"
|
|
2150
2343
|
];
|
|
2151
2344
|
|
|
2152
2345
|
// src/cli/claude-code-install.ts
|
|
@@ -2452,11 +2645,11 @@ import {
|
|
|
2452
2645
|
mkdirSync as mkdirSync3,
|
|
2453
2646
|
readFileSync as readFileSync5,
|
|
2454
2647
|
renameSync as renameSync2,
|
|
2455
|
-
rmSync,
|
|
2648
|
+
rmSync as rmSync2,
|
|
2456
2649
|
writeFileSync as writeFileSync3
|
|
2457
2650
|
} from "fs";
|
|
2458
2651
|
import { homedir as homedir2 } from "os";
|
|
2459
|
-
import { dirname as
|
|
2652
|
+
import { dirname as dirname5, join as join2 } from "path";
|
|
2460
2653
|
var INSTALL_LEDGER_SCHEMA_VERSION = 1;
|
|
2461
2654
|
var INSTALL_HARNESSES = /* @__PURE__ */ new Set([
|
|
2462
2655
|
"opencode",
|
|
@@ -2536,7 +2729,7 @@ function recordCompletedInstall(options) {
|
|
|
2536
2729
|
const repairedInvalidState = current.status === "invalid";
|
|
2537
2730
|
const backupPath = repairedInvalidState ? `${path}.bak` : void 0;
|
|
2538
2731
|
try {
|
|
2539
|
-
mkdirSync3(
|
|
2732
|
+
mkdirSync3(dirname5(path), { recursive: true });
|
|
2540
2733
|
writeFileSync3(temporaryPath, `${JSON.stringify(ledger, null, 2)}
|
|
2541
2734
|
`);
|
|
2542
2735
|
if (backupPath) copyFileSync3(path, backupPath);
|
|
@@ -2550,7 +2743,7 @@ function recordCompletedInstall(options) {
|
|
|
2550
2743
|
};
|
|
2551
2744
|
} catch (error) {
|
|
2552
2745
|
try {
|
|
2553
|
-
|
|
2746
|
+
rmSync2(temporaryPath, { recursive: true, force: true });
|
|
2554
2747
|
} catch {
|
|
2555
2748
|
}
|
|
2556
2749
|
return {
|
|
@@ -2616,6 +2809,7 @@ function parseProviderSetupResult(stdout, expectedHarness) {
|
|
|
2616
2809
|
}
|
|
2617
2810
|
function defaultCommandExecutor2(command, args, options) {
|
|
2618
2811
|
const result = spawnSync2(command, [...args], {
|
|
2812
|
+
windowsHide: true,
|
|
2619
2813
|
cwd: options.cwd,
|
|
2620
2814
|
encoding: "utf8",
|
|
2621
2815
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -2901,7 +3095,10 @@ function installRequiredSkill(skill, harness, options = {}) {
|
|
|
2901
3095
|
options
|
|
2902
3096
|
);
|
|
2903
3097
|
try {
|
|
2904
|
-
const result = spawnSync3(command, args, {
|
|
3098
|
+
const result = spawnSync3(command, args, {
|
|
3099
|
+
windowsHide: true,
|
|
3100
|
+
stdio: "inherit"
|
|
3101
|
+
});
|
|
2905
3102
|
if (result.status === 0) {
|
|
2906
3103
|
return {
|
|
2907
3104
|
skill,
|
|
@@ -3613,13 +3810,13 @@ import { basename as basename2 } from "path";
|
|
|
3613
3810
|
// src/cli/codex-plugin-install.ts
|
|
3614
3811
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
3615
3812
|
import {
|
|
3616
|
-
lstatSync,
|
|
3813
|
+
lstatSync as lstatSync2,
|
|
3617
3814
|
readdirSync,
|
|
3618
3815
|
readFileSync as readFileSync6,
|
|
3619
3816
|
realpathSync,
|
|
3620
|
-
rmSync as
|
|
3817
|
+
rmSync as rmSync3
|
|
3621
3818
|
} from "fs";
|
|
3622
|
-
import { isAbsolute, join as join4, relative, resolve as
|
|
3819
|
+
import { isAbsolute, join as join4, relative, resolve as resolve3, sep } from "path";
|
|
3623
3820
|
var PLUGIN_NAME2 = "thoth-agents";
|
|
3624
3821
|
var MARKETPLACE_NAME2 = "thoth-plugins";
|
|
3625
3822
|
var MARKETPLACE_SOURCE2 = "https://github.com/EremesNG/thoth-plugins.git";
|
|
@@ -3693,9 +3890,9 @@ function parseManagerRecord(content) {
|
|
|
3693
3890
|
function normalizeMarketplaceSource2(value) {
|
|
3694
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();
|
|
3695
3892
|
}
|
|
3696
|
-
function
|
|
3893
|
+
function lstatIfPresent2(path) {
|
|
3697
3894
|
try {
|
|
3698
|
-
return
|
|
3895
|
+
return lstatSync2(path);
|
|
3699
3896
|
} catch (error) {
|
|
3700
3897
|
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
3701
3898
|
return null;
|
|
@@ -3708,7 +3905,7 @@ function strictDescendant(base, candidate) {
|
|
|
3708
3905
|
return suffix !== "" && suffix !== ".." && !suffix.startsWith(`..${sep}`) && !isAbsolute(suffix);
|
|
3709
3906
|
}
|
|
3710
3907
|
function parseObjectFile(path, label) {
|
|
3711
|
-
const file =
|
|
3908
|
+
const file = lstatIfPresent2(path);
|
|
3712
3909
|
if (!file || file.isSymbolicLink() || !file.isFile()) {
|
|
3713
3910
|
throw new Error(`${label} must be a regular non-link file`);
|
|
3714
3911
|
}
|
|
@@ -3731,7 +3928,7 @@ function validateCacheManifest(root) {
|
|
|
3731
3928
|
);
|
|
3732
3929
|
}
|
|
3733
3930
|
const productRoot = join4(root, PLUGIN_NAME2);
|
|
3734
|
-
const product =
|
|
3931
|
+
const product = lstatIfPresent2(productRoot);
|
|
3735
3932
|
if (!product || product.isSymbolicLink() || !product.isDirectory()) {
|
|
3736
3933
|
throw new Error(`Legacy cache product root ${productRoot} is unsafe`);
|
|
3737
3934
|
}
|
|
@@ -3741,7 +3938,7 @@ function validateCacheManifest(root) {
|
|
|
3741
3938
|
}
|
|
3742
3939
|
for (const entry of versions) {
|
|
3743
3940
|
const versionRoot = join4(productRoot, entry.name);
|
|
3744
|
-
const version =
|
|
3941
|
+
const version = lstatIfPresent2(versionRoot);
|
|
3745
3942
|
if (!entry.isDirectory() || !version || version.isSymbolicLink() || !version.isDirectory()) {
|
|
3746
3943
|
throw new Error(`Legacy cache version root ${versionRoot} is unsafe`);
|
|
3747
3944
|
}
|
|
@@ -3782,22 +3979,22 @@ function validateSnapshotManifest(root, marketplaceName) {
|
|
|
3782
3979
|
}
|
|
3783
3980
|
}
|
|
3784
3981
|
function validateLegacyRoot(codexHome, definition, expectedRealPath) {
|
|
3785
|
-
const path =
|
|
3982
|
+
const path = resolve3(codexHome, ...definition.relativePath.split("/"));
|
|
3786
3983
|
if (!strictDescendant(codexHome, path)) {
|
|
3787
3984
|
throw new Error(
|
|
3788
3985
|
`Legacy cleanup target escapes CODEX_HOME: ${definition.relativePath}`
|
|
3789
3986
|
);
|
|
3790
3987
|
}
|
|
3791
|
-
const target =
|
|
3988
|
+
const target = lstatIfPresent2(path);
|
|
3792
3989
|
if (!target) return null;
|
|
3793
|
-
const home =
|
|
3990
|
+
const home = lstatIfPresent2(codexHome);
|
|
3794
3991
|
if (!home || !home.isDirectory()) {
|
|
3795
3992
|
throw new Error(`Resolved CODEX_HOME is not a directory: ${codexHome}`);
|
|
3796
3993
|
}
|
|
3797
3994
|
let boundary = codexHome;
|
|
3798
3995
|
for (const segment of definition.relativePath.split("/")) {
|
|
3799
3996
|
boundary = join4(boundary, segment);
|
|
3800
|
-
const value =
|
|
3997
|
+
const value = lstatIfPresent2(boundary);
|
|
3801
3998
|
if (!value || value.isSymbolicLink() || !value.isDirectory()) {
|
|
3802
3999
|
throw new Error(
|
|
3803
4000
|
`Legacy cleanup boundary is not a real directory: ${boundary}`
|
|
@@ -3830,7 +4027,7 @@ function preflightLegacyRoots(codexHomeInput) {
|
|
|
3830
4027
|
if (typeof codexHomeInput !== "string" || codexHomeInput.trim() === "") {
|
|
3831
4028
|
throw new Error("CODEX_HOME could not be resolved");
|
|
3832
4029
|
}
|
|
3833
|
-
const codexHome =
|
|
4030
|
+
const codexHome = resolve3(codexHomeInput);
|
|
3834
4031
|
const roots = LEGACY_ROOTS.map(
|
|
3835
4032
|
(definition) => validateLegacyRoot(codexHome, definition)
|
|
3836
4033
|
).filter((root) => root !== null);
|
|
@@ -3965,7 +4162,7 @@ function legacyManagerClean(inspection) {
|
|
|
3965
4162
|
}
|
|
3966
4163
|
function legacyRootsAbsent(codexHome) {
|
|
3967
4164
|
return LEGACY_ROOTS.every(
|
|
3968
|
-
({ relativePath }) => !
|
|
4165
|
+
({ relativePath }) => !lstatIfPresent2(resolve3(codexHome, ...relativePath.split("/")))
|
|
3969
4166
|
);
|
|
3970
4167
|
}
|
|
3971
4168
|
function legacyPluginCommandItem(pluginId, projectRoot) {
|
|
@@ -4193,11 +4390,11 @@ function applyCodexPluginSetup(plan) {
|
|
|
4193
4390
|
error: `Central ${PLUGIN_ID2} remains installed, but the fixed legacy cleanup plan is invalid; close Codex and retry.`
|
|
4194
4391
|
};
|
|
4195
4392
|
}
|
|
4196
|
-
const exactPath =
|
|
4393
|
+
const exactPath = resolve3(
|
|
4197
4394
|
plan.codexHome,
|
|
4198
4395
|
...definition.relativePath.split("/")
|
|
4199
4396
|
);
|
|
4200
|
-
if (
|
|
4397
|
+
if (resolve3(root.path) !== exactPath || resolve3(item.targetPath) !== exactPath || root.kind !== definition.kind || root.marketplaceName !== definition.marketplaceName) {
|
|
4201
4398
|
return {
|
|
4202
4399
|
success: false,
|
|
4203
4400
|
changed,
|
|
@@ -4205,7 +4402,7 @@ function applyCodexPluginSetup(plan) {
|
|
|
4205
4402
|
error: `Central ${PLUGIN_ID2} remains installed, but the fixed legacy cleanup target changed; close Codex and retry.`
|
|
4206
4403
|
};
|
|
4207
4404
|
}
|
|
4208
|
-
if (!
|
|
4405
|
+
if (!lstatIfPresent2(exactPath)) continue;
|
|
4209
4406
|
try {
|
|
4210
4407
|
const validated = validateLegacyRoot(
|
|
4211
4408
|
plan.codexHome,
|
|
@@ -4213,8 +4410,8 @@ function applyCodexPluginSetup(plan) {
|
|
|
4213
4410
|
root.realPath
|
|
4214
4411
|
);
|
|
4215
4412
|
if (!validated) continue;
|
|
4216
|
-
|
|
4217
|
-
if (
|
|
4413
|
+
rmSync3(validated.path, { recursive: true, force: false });
|
|
4414
|
+
if (lstatIfPresent2(validated.path)) {
|
|
4218
4415
|
throw new Error(
|
|
4219
4416
|
`Legacy cleanup target still exists: ${root.relativePath}`
|
|
4220
4417
|
);
|
|
@@ -4439,6 +4636,14 @@ function classifyItem(item) {
|
|
|
4439
4636
|
if (item.action === "write-managed-model-state") {
|
|
4440
4637
|
return managedModelState(item);
|
|
4441
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
|
+
}
|
|
4442
4647
|
if (item.action === "merge-toml") return userConfigState(item);
|
|
4443
4648
|
return contentState(item);
|
|
4444
4649
|
}
|
|
@@ -4546,6 +4751,14 @@ function getCodexStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
|
|
|
4546
4751
|
code: "codex-diagnostic"
|
|
4547
4752
|
}));
|
|
4548
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
|
+
});
|
|
4549
4762
|
return {
|
|
4550
4763
|
harness: "codex",
|
|
4551
4764
|
displayName: CODEX_DISPLAY_NAME,
|
|
@@ -5078,7 +5291,7 @@ function applyCodexPlan(plan) {
|
|
|
5078
5291
|
}
|
|
5079
5292
|
|
|
5080
5293
|
// src/cli/operations/opencode.ts
|
|
5081
|
-
import { createHash as
|
|
5294
|
+
import { createHash as createHash4 } from "crypto";
|
|
5082
5295
|
import { existsSync as existsSync10 } from "fs";
|
|
5083
5296
|
import { join as join6 } from "path";
|
|
5084
5297
|
|
|
@@ -5088,7 +5301,8 @@ var OPENAI_GPT_RUNTIME_VARIANTS = /* @__PURE__ */ new Set([
|
|
|
5088
5301
|
"low",
|
|
5089
5302
|
"medium",
|
|
5090
5303
|
"high",
|
|
5091
|
-
"xhigh"
|
|
5304
|
+
"xhigh",
|
|
5305
|
+
"max"
|
|
5092
5306
|
]);
|
|
5093
5307
|
function resolveOpenCodeEffort(input) {
|
|
5094
5308
|
if (!input.effort || input.effort.kind === "inherit") {
|
|
@@ -5116,16 +5330,16 @@ function resolveOpenCodeEffort(input) {
|
|
|
5116
5330
|
import {
|
|
5117
5331
|
cpSync,
|
|
5118
5332
|
existsSync as existsSync9,
|
|
5119
|
-
lstatSync as
|
|
5333
|
+
lstatSync as lstatSync3,
|
|
5120
5334
|
mkdirSync as mkdirSync4,
|
|
5121
5335
|
mkdtempSync,
|
|
5122
5336
|
readFileSync as readFileSync8,
|
|
5123
5337
|
renameSync as renameSync3,
|
|
5124
|
-
rmSync as
|
|
5338
|
+
rmSync as rmSync4,
|
|
5125
5339
|
statSync
|
|
5126
5340
|
} from "fs";
|
|
5127
5341
|
import { homedir as homedir4 } from "os";
|
|
5128
|
-
import { dirname as
|
|
5342
|
+
import { dirname as dirname6, join as join5 } from "path";
|
|
5129
5343
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5130
5344
|
|
|
5131
5345
|
// src/harness/core/owned-skills.ts
|
|
@@ -5169,7 +5383,7 @@ function inspectPiPackageSkill(entry) {
|
|
|
5169
5383
|
state: "missing",
|
|
5170
5384
|
message: `Package-declared Pi skill directory is missing: ${entry.name}`
|
|
5171
5385
|
};
|
|
5172
|
-
const root =
|
|
5386
|
+
const root = lstatSync3(entry.sourcePath);
|
|
5173
5387
|
if (!root.isDirectory() || root.isSymbolicLink())
|
|
5174
5388
|
return {
|
|
5175
5389
|
name: entry.name,
|
|
@@ -5183,7 +5397,7 @@ function inspectPiPackageSkill(entry) {
|
|
|
5183
5397
|
state: "missing",
|
|
5184
5398
|
message: `Package-declared Pi skill contract is missing: ${entry.name}`
|
|
5185
5399
|
};
|
|
5186
|
-
const contract =
|
|
5400
|
+
const contract = lstatSync3(contractPath);
|
|
5187
5401
|
if (!contract.isFile() || contract.isSymbolicLink())
|
|
5188
5402
|
return {
|
|
5189
5403
|
name: entry.name,
|
|
@@ -5201,7 +5415,7 @@ function inspectPiPackageSkill(entry) {
|
|
|
5201
5415
|
return void 0;
|
|
5202
5416
|
}
|
|
5203
5417
|
function resolveOwnedSkillPackageRoot(moduleUrl) {
|
|
5204
|
-
const packageRoot = findPackageRoot(
|
|
5418
|
+
const packageRoot = findPackageRoot(dirname6(fileURLToPath4(moduleUrl)));
|
|
5205
5419
|
if (!packageRoot) {
|
|
5206
5420
|
throw new Error(
|
|
5207
5421
|
"Unable to locate the thoth-agents package root for bundled owned skills."
|
|
@@ -5243,7 +5457,7 @@ function validateCanonicalSkill(entry) {
|
|
|
5243
5457
|
function installOwnedSkills(skills) {
|
|
5244
5458
|
const firstSkill = skills[0];
|
|
5245
5459
|
if (!firstSkill) return;
|
|
5246
|
-
const globalRoot =
|
|
5460
|
+
const globalRoot = dirname6(firstSkill.destinationPath);
|
|
5247
5461
|
mkdirSync4(globalRoot, { recursive: true });
|
|
5248
5462
|
const transactionRoot = mkdtempSync(join5(globalRoot, ".thoth-agents-owned-"));
|
|
5249
5463
|
const stagedRoot = join5(transactionRoot, "staged");
|
|
@@ -5268,7 +5482,7 @@ function installOwnedSkills(skills) {
|
|
|
5268
5482
|
renameSync3(stagedPath, skill.destinationPath);
|
|
5269
5483
|
} catch (error) {
|
|
5270
5484
|
if (existsSync9(skill.destinationPath)) {
|
|
5271
|
-
|
|
5485
|
+
rmSync4(skill.destinationPath, { recursive: true, force: true });
|
|
5272
5486
|
}
|
|
5273
5487
|
if (hadExistingDestination && existsSync9(backupPath)) {
|
|
5274
5488
|
renameSync3(backupPath, skill.destinationPath);
|
|
@@ -5276,11 +5490,11 @@ function installOwnedSkills(skills) {
|
|
|
5276
5490
|
throw error;
|
|
5277
5491
|
}
|
|
5278
5492
|
if (existsSync9(backupPath)) {
|
|
5279
|
-
|
|
5493
|
+
rmSync4(backupPath, { recursive: true, force: true });
|
|
5280
5494
|
}
|
|
5281
5495
|
}
|
|
5282
5496
|
} finally {
|
|
5283
|
-
|
|
5497
|
+
rmSync4(transactionRoot, { recursive: true, force: true });
|
|
5284
5498
|
}
|
|
5285
5499
|
}
|
|
5286
5500
|
function syncOpenCodeOwnedSkills(options = {}) {
|
|
@@ -5434,7 +5648,7 @@ function canonicalDigest(value) {
|
|
|
5434
5648
|
const serialized = canonicalData(value, /* @__PURE__ */ new Set());
|
|
5435
5649
|
return {
|
|
5436
5650
|
ok: true,
|
|
5437
|
-
digest:
|
|
5651
|
+
digest: createHash4("sha256").update(serialized).digest("hex")
|
|
5438
5652
|
};
|
|
5439
5653
|
} catch {
|
|
5440
5654
|
return { ok: false };
|
|
@@ -5558,7 +5772,7 @@ function targetForLiteConfig(state) {
|
|
|
5558
5772
|
path: getExistingLiteConfigPath(),
|
|
5559
5773
|
label: "thoth-agents config",
|
|
5560
5774
|
...state ? { state } : {},
|
|
5561
|
-
expected: `
|
|
5775
|
+
expected: `six-role ${OPENAI_PRESET} roster`
|
|
5562
5776
|
};
|
|
5563
5777
|
}
|
|
5564
5778
|
function titleCaseSkillName(value) {
|
|
@@ -6036,7 +6250,7 @@ function getOpenCodeManagedStatus(context2 = { cwd: process.cwd() }) {
|
|
|
6036
6250
|
harness: "opencode",
|
|
6037
6251
|
displayName: "OpenCode",
|
|
6038
6252
|
state: "drift",
|
|
6039
|
-
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.",
|
|
6040
6254
|
targets: [
|
|
6041
6255
|
{ ...mainTarget, state: "installed" },
|
|
6042
6256
|
{ ...liteTarget, state: "drift" },
|
|
@@ -6240,7 +6454,7 @@ function buildOpenCodeCompletePlan(action, context2) {
|
|
|
6240
6454
|
backup: defaultBackup(getExistingConfigPath())
|
|
6241
6455
|
},
|
|
6242
6456
|
{
|
|
6243
|
-
title: "Write thoth-agents
|
|
6457
|
+
title: "Write thoth-agents six-role config",
|
|
6244
6458
|
target: targetForLiteConfig(),
|
|
6245
6459
|
preview: JSON.stringify(generatedConfig, null, 2),
|
|
6246
6460
|
backup: defaultBackup(getExistingLiteConfigPath())
|
|
@@ -6341,7 +6555,7 @@ function buildOpenCodeSyncPlan(context2 = { cwd: process.cwd() }) {
|
|
|
6341
6555
|
backup: defaultBackup(getExistingConfigPath())
|
|
6342
6556
|
},
|
|
6343
6557
|
{
|
|
6344
|
-
title: "Write thoth-agents
|
|
6558
|
+
title: "Write thoth-agents six-role config",
|
|
6345
6559
|
target: targetForLiteConfig(),
|
|
6346
6560
|
preview: JSON.stringify(generatedConfig, null, 2),
|
|
6347
6561
|
backup: defaultBackup(litePath)
|
|
@@ -6864,7 +7078,7 @@ function applyOpenCodePlan(plan) {
|
|
|
6864
7078
|
}
|
|
6865
7079
|
changedTargets.push({
|
|
6866
7080
|
...targetForLiteConfig("installed"),
|
|
6867
|
-
observed: "
|
|
7081
|
+
observed: "six-role roster written"
|
|
6868
7082
|
});
|
|
6869
7083
|
if (existsSync10(`${liteResult.configPath}.bak`)) {
|
|
6870
7084
|
backups.push({
|
|
@@ -6956,56 +7170,289 @@ function applyOpenCodePlan(plan) {
|
|
|
6956
7170
|
|
|
6957
7171
|
// src/cli/operations/pi.ts
|
|
6958
7172
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
6959
|
-
import { createHash as
|
|
6960
|
-
import { existsSync as existsSync15, readFileSync as
|
|
6961
|
-
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";
|
|
6962
7176
|
|
|
6963
|
-
// src/cli/pi-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
"
|
|
6969
|
-
"
|
|
6970
|
-
"
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
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;
|
|
7200
|
+
}
|
|
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;
|
|
6976
7210
|
}
|
|
6977
|
-
|
|
6978
|
-
|
|
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"))
|
|
6979
7217
|
return {
|
|
6980
|
-
|
|
6981
|
-
|
|
7218
|
+
state: "drift",
|
|
7219
|
+
reason: "project package shadows the required global package"
|
|
6982
7220
|
};
|
|
6983
|
-
}
|
|
6984
|
-
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)
|
|
6985
7228
|
return {
|
|
6986
|
-
|
|
6987
|
-
|
|
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"
|
|
6988
7244
|
};
|
|
6989
7245
|
}
|
|
6990
|
-
|
|
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
|
+
};
|
|
6991
7290
|
}
|
|
6992
7291
|
|
|
6993
7292
|
// src/cli/pi-install.ts
|
|
6994
7293
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
6995
|
-
import { createHash as
|
|
7294
|
+
import { createHash as createHash5 } from "crypto";
|
|
6996
7295
|
import {
|
|
6997
|
-
copyFileSync as copyFileSync5,
|
|
6998
7296
|
existsSync as existsSync14,
|
|
6999
|
-
lstatSync as
|
|
7000
|
-
mkdirSync as mkdirSync7,
|
|
7297
|
+
lstatSync as lstatSync4,
|
|
7001
7298
|
readdirSync as readdirSync3,
|
|
7002
|
-
readFileSync as
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
writeFileSync as writeFileSync7
|
|
7299
|
+
readFileSync as readFileSync13,
|
|
7300
|
+
realpathSync as realpathSync3,
|
|
7301
|
+
statSync as statSync3
|
|
7006
7302
|
} from "fs";
|
|
7007
|
-
import { dirname as
|
|
7008
|
-
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
|
+
}
|
|
7009
7456
|
|
|
7010
7457
|
// src/cli/pi-migration.ts
|
|
7011
7458
|
import {
|
|
@@ -7013,16 +7460,16 @@ import {
|
|
|
7013
7460
|
existsSync as existsSync11,
|
|
7014
7461
|
mkdirSync as mkdirSync5,
|
|
7015
7462
|
readdirSync as readdirSync2,
|
|
7016
|
-
readFileSync as
|
|
7463
|
+
readFileSync as readFileSync10,
|
|
7017
7464
|
renameSync as renameSync4,
|
|
7018
7465
|
statSync as statSync2,
|
|
7019
7466
|
writeFileSync as writeFileSync4
|
|
7020
7467
|
} from "fs";
|
|
7021
|
-
import { dirname as
|
|
7468
|
+
import { dirname as dirname7, join as join8 } from "path";
|
|
7022
7469
|
function files(root, current = root) {
|
|
7023
7470
|
if (!existsSync11(root)) return [];
|
|
7024
7471
|
return readdirSync2(current, { withFileTypes: true }).flatMap((entry) => {
|
|
7025
|
-
const path =
|
|
7472
|
+
const path = join8(current, entry.name);
|
|
7026
7473
|
return entry.isDirectory() ? files(root, path) : [path.slice(root.length + 1).replaceAll("\\", "/")];
|
|
7027
7474
|
}).sort();
|
|
7028
7475
|
}
|
|
@@ -7030,7 +7477,7 @@ function identicalTrees(left, right) {
|
|
|
7030
7477
|
const leftFiles = files(left);
|
|
7031
7478
|
const rightFiles = files(right);
|
|
7032
7479
|
return leftFiles.length === rightFiles.length && leftFiles.every(
|
|
7033
|
-
(name, index) => name === rightFiles[index] &&
|
|
7480
|
+
(name, index) => name === rightFiles[index] && readFileSync10(join8(left, name)).equals(readFileSync10(join8(right, name)))
|
|
7034
7481
|
);
|
|
7035
7482
|
}
|
|
7036
7483
|
function removeRootBlock(content) {
|
|
@@ -7045,19 +7492,19 @@ function removeRootBlock(content) {
|
|
|
7045
7492
|
function migrateLegacyPiResources(options) {
|
|
7046
7493
|
const changed = [];
|
|
7047
7494
|
const manualActions = [];
|
|
7048
|
-
const appendPath =
|
|
7495
|
+
const appendPath = join8(options.piRoot, "APPEND_SYSTEM.md");
|
|
7049
7496
|
let nextRoot;
|
|
7050
7497
|
try {
|
|
7051
7498
|
if (existsSync11(appendPath))
|
|
7052
|
-
nextRoot = removeRootBlock(
|
|
7499
|
+
nextRoot = removeRootBlock(readFileSync10(appendPath, "utf8"));
|
|
7053
7500
|
} catch (error) {
|
|
7054
7501
|
manualActions.push(error instanceof Error ? error.message : String(error));
|
|
7055
7502
|
}
|
|
7056
7503
|
const retire = [];
|
|
7057
7504
|
for (const name of THOTH_OWNED_SKILL_NAMES) {
|
|
7058
|
-
const source =
|
|
7505
|
+
const source = join8(options.piRoot, "skills", name);
|
|
7059
7506
|
if (!existsSync11(source)) continue;
|
|
7060
|
-
const canonical =
|
|
7507
|
+
const canonical = join8(options.packageRoot, "skills", name);
|
|
7061
7508
|
const backup = `${source}.thoth-agents-legacy.bak`;
|
|
7062
7509
|
if (!statSync2(source).isDirectory() || !identicalTrees(source, canonical) || existsSync11(backup)) {
|
|
7063
7510
|
manualActions.push(
|
|
@@ -7078,7 +7525,7 @@ function migrateLegacyPiResources(options) {
|
|
|
7078
7525
|
};
|
|
7079
7526
|
try {
|
|
7080
7527
|
if (nextRoot !== void 0) {
|
|
7081
|
-
mkdirSync5(
|
|
7528
|
+
mkdirSync5(dirname7(appendPath), { recursive: true });
|
|
7082
7529
|
copyFileSync4(appendPath, `${appendPath}.thoth-agents-legacy.bak`);
|
|
7083
7530
|
const temporary = `${appendPath}.tmp-${process.pid}`;
|
|
7084
7531
|
writeFileSync4(temporary, nextRoot);
|
|
@@ -7110,29 +7557,63 @@ function migrateLegacyPiResources(options) {
|
|
|
7110
7557
|
|
|
7111
7558
|
// src/cli/pi-native-probe.ts
|
|
7112
7559
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
7113
|
-
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";
|
|
7114
7567
|
import { tmpdir } from "os";
|
|
7115
|
-
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
|
+
}
|
|
7116
7605
|
function defaultExecutor(command, args, env) {
|
|
7117
7606
|
let executable = command;
|
|
7118
7607
|
let executableArgs = [...args];
|
|
7119
7608
|
if (process.platform === "win32" && command === "pi") {
|
|
7120
|
-
const
|
|
7121
|
-
|
|
7122
|
-
dirname7(located),
|
|
7123
|
-
"node_modules",
|
|
7124
|
-
"@earendil-works",
|
|
7125
|
-
"pi-coding-agent",
|
|
7126
|
-
"dist",
|
|
7127
|
-
"bundle",
|
|
7128
|
-
"cli.js"
|
|
7129
|
-
) : void 0;
|
|
7130
|
-
if (cli && existsSync12(cli)) {
|
|
7609
|
+
const cli = findPiWindowsCli();
|
|
7610
|
+
if (cli) {
|
|
7131
7611
|
executable = process.execPath;
|
|
7132
7612
|
executableArgs = [cli, ...args];
|
|
7133
7613
|
}
|
|
7134
7614
|
}
|
|
7135
7615
|
const result = spawnSync5(executable, executableArgs, {
|
|
7616
|
+
windowsHide: true,
|
|
7136
7617
|
encoding: "utf8",
|
|
7137
7618
|
timeout: 3e4,
|
|
7138
7619
|
env: { ...process.env, ...env }
|
|
@@ -7149,8 +7630,8 @@ function occurrences(text, marker) {
|
|
|
7149
7630
|
}
|
|
7150
7631
|
function observePiNativeRoot(options) {
|
|
7151
7632
|
const ownsIsolatedRoot = options.piHome === void 0;
|
|
7152
|
-
const isolatedRoot = options.piHome ?? mkdtempSync2(
|
|
7153
|
-
const observerPath =
|
|
7633
|
+
const isolatedRoot = options.piHome ?? mkdtempSync2(join9(tmpdir(), "thoth-pi-observer-"));
|
|
7634
|
+
const observerPath = join9(
|
|
7154
7635
|
isolatedRoot,
|
|
7155
7636
|
`observer-${process.pid}-${Date.now()}.mjs`
|
|
7156
7637
|
);
|
|
@@ -7159,7 +7640,7 @@ function observePiNativeRoot(options) {
|
|
|
7159
7640
|
observerPath,
|
|
7160
7641
|
`import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
7161
7642
|
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
7162
|
-
import { createAssistantMessageEventStream } from '@earendil-works/pi-ai';
|
|
7643
|
+
import { createAssistantMessageEventStream, getCurrentSystemPrompt } from '@earendil-works/pi-ai';
|
|
7163
7644
|
const manifestSha256=${JSON.stringify(options.manifestSha256)};
|
|
7164
7645
|
const extensionSha256=${JSON.stringify(options.extensionSha256)};
|
|
7165
7646
|
const packageRoot=${JSON.stringify(options.packageRoot)};
|
|
@@ -7204,12 +7685,16 @@ export default function observer(pi) {
|
|
|
7204
7685
|
const stream=createAssistantMessageEventStream();
|
|
7205
7686
|
queueMicrotask(async () => {
|
|
7206
7687
|
try {
|
|
7207
|
-
|
|
7208
|
-
|
|
7688
|
+
const systemPrompt=getCurrentSystemPrompt(context.messages);
|
|
7689
|
+
skills=discoveredSkills(systemPrompt);
|
|
7690
|
+
const raw={systemPrompt};
|
|
7209
7691
|
if (streamOptions?.onPayload) await streamOptions.onPayload(raw);
|
|
7210
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()};
|
|
7211
|
-
stream.push({type:'
|
|
7212
|
-
|
|
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(); }
|
|
7213
7698
|
});
|
|
7214
7699
|
return stream;
|
|
7215
7700
|
}
|
|
@@ -7225,7 +7710,7 @@ export default function observer(pi) {
|
|
|
7225
7710
|
"--offline",
|
|
7226
7711
|
"--no-extensions",
|
|
7227
7712
|
"--extension",
|
|
7228
|
-
|
|
7713
|
+
resolve5(options.extensionPath),
|
|
7229
7714
|
"--extension",
|
|
7230
7715
|
observerPath,
|
|
7231
7716
|
"--provider",
|
|
@@ -7278,8 +7763,8 @@ ${result.stderr}`.split(/\r?\n/).flatMap((line) => {
|
|
|
7278
7763
|
) : void 0;
|
|
7279
7764
|
const orchestratorChild = typeof record?.orchestratorChild === "boolean" ? record.orchestratorChild : void 0;
|
|
7280
7765
|
const sessionStartCount = typeof record?.sessionStartCount === "number" ? record.sessionStartCount : void 0;
|
|
7281
|
-
const
|
|
7282
|
-
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 ? {
|
|
7283
7768
|
state: "observed-at-install",
|
|
7284
7769
|
basis: [
|
|
7285
7770
|
"Real Pi final provider request contained exactly one receipt-bound root marker."
|
|
@@ -7298,8 +7783,8 @@ ${result.stderr}`.split(/\r?\n/).flatMap((line) => {
|
|
|
7298
7783
|
};
|
|
7299
7784
|
} finally {
|
|
7300
7785
|
if (ownsIsolatedRoot)
|
|
7301
|
-
|
|
7302
|
-
else
|
|
7786
|
+
rmSync5(isolatedRoot, { recursive: true, force: true });
|
|
7787
|
+
else rmSync5(observerPath, { force: true });
|
|
7303
7788
|
}
|
|
7304
7789
|
}
|
|
7305
7790
|
|
|
@@ -7307,13 +7792,13 @@ ${result.stderr}`.split(/\r?\n/).flatMap((line) => {
|
|
|
7307
7792
|
import {
|
|
7308
7793
|
existsSync as existsSync13,
|
|
7309
7794
|
mkdirSync as mkdirSync6,
|
|
7310
|
-
readFileSync as
|
|
7795
|
+
readFileSync as readFileSync12,
|
|
7311
7796
|
renameSync as renameSync5,
|
|
7312
|
-
rmSync as
|
|
7797
|
+
rmSync as rmSync6,
|
|
7313
7798
|
writeFileSync as writeFileSync6
|
|
7314
7799
|
} from "fs";
|
|
7315
7800
|
import { homedir as homedir5 } from "os";
|
|
7316
|
-
import { dirname as
|
|
7801
|
+
import { dirname as dirname9, isAbsolute as isAbsolute3, join as join10, normalize, resolve as resolve6 } from "path";
|
|
7317
7802
|
var keys = [
|
|
7318
7803
|
"schemaVersion",
|
|
7319
7804
|
"owner",
|
|
@@ -7328,16 +7813,16 @@ var keys = [
|
|
|
7328
7813
|
var semver = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
7329
7814
|
var digest = /^[0-9a-f]{64}$/;
|
|
7330
7815
|
function piPackagePathsEqual(left, right) {
|
|
7331
|
-
const normalizedLeft = normalize(
|
|
7332
|
-
const normalizedRight = normalize(
|
|
7816
|
+
const normalizedLeft = normalize(resolve6(left));
|
|
7817
|
+
const normalizedRight = normalize(resolve6(right));
|
|
7333
7818
|
return process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight;
|
|
7334
7819
|
}
|
|
7335
7820
|
function getPiPackageReceiptPath(options = {}) {
|
|
7336
7821
|
const env = options.env ?? process.env;
|
|
7337
|
-
const root = options.configRoot ?? env.XDG_CONFIG_HOME?.trim() ??
|
|
7338
|
-
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");
|
|
7339
7824
|
}
|
|
7340
|
-
function
|
|
7825
|
+
function parse2(value) {
|
|
7341
7826
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
7342
7827
|
throw new Error("Receipt must be a JSON object.");
|
|
7343
7828
|
const record = value;
|
|
@@ -7349,7 +7834,7 @@ function parse(value) {
|
|
|
7349
7834
|
throw new Error("Receipt version is invalid.");
|
|
7350
7835
|
const npmSource = `npm:thoth-agents@${record.version}`;
|
|
7351
7836
|
const npmIdentity = record.source === npmSource && record.installSource === npmSource;
|
|
7352
|
-
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);
|
|
7353
7838
|
if (!npmIdentity && !localIdentity)
|
|
7354
7839
|
throw new Error("Receipt source is not canonical.");
|
|
7355
7840
|
if (typeof record.manifestSha256 !== "string" || !digest.test(record.manifestSha256) || typeof record.extensionSha256 !== "string" || !digest.test(record.extensionSha256))
|
|
@@ -7363,7 +7848,7 @@ function readPiPackageReceipt(options = {}) {
|
|
|
7363
7848
|
return {
|
|
7364
7849
|
status: "valid",
|
|
7365
7850
|
path,
|
|
7366
|
-
receipt:
|
|
7851
|
+
receipt: parse2(JSON.parse(readFileSync12(path, "utf8")))
|
|
7367
7852
|
};
|
|
7368
7853
|
} catch (error) {
|
|
7369
7854
|
return {
|
|
@@ -7376,8 +7861,8 @@ function readPiPackageReceipt(options = {}) {
|
|
|
7376
7861
|
function writePiPackageReceipt(receipt, options = {}) {
|
|
7377
7862
|
const path = getPiPackageReceiptPath(options);
|
|
7378
7863
|
try {
|
|
7379
|
-
const validated =
|
|
7380
|
-
mkdirSync6(
|
|
7864
|
+
const validated = parse2(receipt);
|
|
7865
|
+
mkdirSync6(dirname9(path), { recursive: true });
|
|
7381
7866
|
const temporary = `${path}.tmp-${process.pid}`;
|
|
7382
7867
|
writeFileSync6(temporary, `${JSON.stringify(validated, null, 2)}
|
|
7383
7868
|
`, {
|
|
@@ -7386,7 +7871,7 @@ function writePiPackageReceipt(receipt, options = {}) {
|
|
|
7386
7871
|
try {
|
|
7387
7872
|
renameSync5(temporary, path);
|
|
7388
7873
|
} catch (error) {
|
|
7389
|
-
|
|
7874
|
+
rmSync6(temporary, { force: true });
|
|
7390
7875
|
throw error;
|
|
7391
7876
|
}
|
|
7392
7877
|
return { success: true, path };
|
|
@@ -7421,76 +7906,78 @@ function classifyPiPackageOwnership(input) {
|
|
|
7421
7906
|
|
|
7422
7907
|
// src/cli/pi-paths.ts
|
|
7423
7908
|
import { homedir as homedir6 } from "os";
|
|
7424
|
-
import { join as
|
|
7909
|
+
import { join as join11, resolve as resolve7 } from "path";
|
|
7425
7910
|
function resolvePiPaths(options = {}) {
|
|
7426
7911
|
const env = options.env ?? process.env;
|
|
7427
|
-
const homeDir =
|
|
7912
|
+
const homeDir = resolve7(
|
|
7428
7913
|
options.homeDir ?? env.HOME ?? env.USERPROFILE ?? homedir6()
|
|
7429
7914
|
);
|
|
7430
|
-
const defaultPiRoot =
|
|
7915
|
+
const defaultPiRoot = join11(homeDir, ".pi", "agent");
|
|
7431
7916
|
const configured = env.PI_CODING_AGENT_DIR?.trim();
|
|
7432
|
-
const piRoot = configured ?
|
|
7433
|
-
const cwd =
|
|
7434
|
-
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");
|
|
7435
7920
|
return {
|
|
7436
7921
|
homeDir,
|
|
7437
7922
|
piRoot,
|
|
7438
7923
|
defaultPiRoot,
|
|
7439
7924
|
customPiRoot: piRoot !== defaultPiRoot,
|
|
7440
7925
|
skillsDestinationCompatible: piRoot === defaultPiRoot,
|
|
7441
|
-
appendSystemPath:
|
|
7442
|
-
agentsRoot:
|
|
7443
|
-
alternateAgentsRoot:
|
|
7444
|
-
ownedSkillsRoot:
|
|
7445
|
-
|
|
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"),
|
|
7446
7933
|
projectAgentRoots: [
|
|
7447
|
-
|
|
7448
|
-
|
|
7934
|
+
join11(cwd, ".pi", "agents"),
|
|
7935
|
+
join11(cwd, ".pi", "subagents")
|
|
7449
7936
|
],
|
|
7450
|
-
projectMcpPaths: [
|
|
7937
|
+
projectMcpPaths: [join11(cwd, ".mcp.json"), join11(cwd, ".pi", "mcp.json")]
|
|
7451
7938
|
};
|
|
7452
7939
|
}
|
|
7453
7940
|
|
|
7454
7941
|
// src/cli/pi-install.ts
|
|
7455
|
-
var PI_MINIMUM_VERSION = "0.
|
|
7942
|
+
var PI_MINIMUM_VERSION = "0.99.0";
|
|
7456
7943
|
var PI_NODE_MINIMUM = "22.19.0";
|
|
7457
7944
|
var PI_COMMAND_TIMEOUT_MS = 12e4;
|
|
7458
7945
|
var PI_PACKAGE_SPECS = [
|
|
7459
7946
|
{
|
|
7460
7947
|
id: "delegation",
|
|
7461
|
-
source: "npm
|
|
7462
|
-
packageName: "pi-subagents
|
|
7463
|
-
version: "1.
|
|
7948
|
+
source: "npm:@thoth-agents/pi-subagents@>=0.1.0",
|
|
7949
|
+
packageName: "@thoth-agents/pi-subagents",
|
|
7950
|
+
version: "0.1.0"
|
|
7464
7951
|
},
|
|
7465
7952
|
{
|
|
7466
7953
|
id: "context7",
|
|
7467
|
-
source: "npm:@upstash/context7-pi
|
|
7954
|
+
source: "npm:@upstash/context7-pi@>=0.1.2",
|
|
7468
7955
|
packageName: "@upstash/context7-pi",
|
|
7469
7956
|
version: "0.1.2"
|
|
7470
7957
|
},
|
|
7471
7958
|
{
|
|
7472
7959
|
id: "web-access",
|
|
7473
|
-
source: "npm:pi-web-access
|
|
7960
|
+
source: "npm:pi-web-access@>=0.27.0",
|
|
7474
7961
|
packageName: "pi-web-access",
|
|
7475
7962
|
version: "0.27.0"
|
|
7476
7963
|
},
|
|
7477
7964
|
{
|
|
7478
7965
|
id: "grep-adapter",
|
|
7479
|
-
source: "npm:pi-mcp-adapter
|
|
7966
|
+
source: "npm:pi-mcp-adapter@>=2.32.1",
|
|
7480
7967
|
packageName: "pi-mcp-adapter",
|
|
7481
7968
|
version: "2.32.1"
|
|
7482
7969
|
},
|
|
7483
7970
|
{
|
|
7484
7971
|
id: "ask-user-question",
|
|
7485
|
-
source: "npm:@
|
|
7486
|
-
packageName: "@
|
|
7487
|
-
version: "
|
|
7972
|
+
source: "npm:@thoth-agents/pi-questions-user@>=0.1.0",
|
|
7973
|
+
packageName: "@thoth-agents/pi-questions-user",
|
|
7974
|
+
version: "0.1.0"
|
|
7488
7975
|
},
|
|
7489
7976
|
{
|
|
7490
7977
|
id: "todo",
|
|
7491
|
-
source: "npm:@
|
|
7492
|
-
packageName: "@
|
|
7493
|
-
version: "
|
|
7978
|
+
source: "npm:@thoth-agents/pi-todo@>=0.1.0",
|
|
7979
|
+
packageName: "@thoth-agents/pi-todo",
|
|
7980
|
+
version: "0.1.0"
|
|
7494
7981
|
}
|
|
7495
7982
|
];
|
|
7496
7983
|
var PI_GREP_MCP_ENTRY = {
|
|
@@ -7502,7 +7989,7 @@ function getPiFirstPartySource(version) {
|
|
|
7502
7989
|
return `npm:thoth-agents@${version}`;
|
|
7503
7990
|
}
|
|
7504
7991
|
function packageRootFor(options) {
|
|
7505
|
-
const root = options.packageRoot ?? findPackageRoot(
|
|
7992
|
+
const root = options.packageRoot ?? findPackageRoot(dirname10(fileURLToPath5(import.meta.url)));
|
|
7506
7993
|
if (!root)
|
|
7507
7994
|
throw new Error(
|
|
7508
7995
|
"Unable to locate the executing thoth-agents package root."
|
|
@@ -7511,63 +7998,56 @@ function packageRootFor(options) {
|
|
|
7511
7998
|
}
|
|
7512
7999
|
function packageVersionFor(root) {
|
|
7513
8000
|
const value = JSON.parse(
|
|
7514
|
-
|
|
8001
|
+
readFileSync13(join12(root, "package.json"), "utf8")
|
|
7515
8002
|
);
|
|
7516
8003
|
if (typeof value.version !== "string")
|
|
7517
8004
|
throw new Error("Executing thoth-agents package version is invalid.");
|
|
7518
8005
|
return value.version;
|
|
7519
8006
|
}
|
|
7520
8007
|
function sha2562(path) {
|
|
7521
|
-
return
|
|
8008
|
+
return createHash5("sha256").update(readFileSync13(path)).digest("hex");
|
|
7522
8009
|
}
|
|
7523
8010
|
function verifyPiFirstPartyPackage(input) {
|
|
7524
8011
|
try {
|
|
7525
|
-
const manifestPath =
|
|
7526
|
-
const extensionPath =
|
|
7527
|
-
const assetsPath =
|
|
8012
|
+
const manifestPath = join12(input.packageRoot, "package.json");
|
|
8013
|
+
const extensionPath = join12(input.packageRoot, "dist", "pi.js");
|
|
8014
|
+
const assetsPath = join12(
|
|
7528
8015
|
input.packageRoot,
|
|
7529
8016
|
"pi",
|
|
7530
8017
|
".thoth-agents-assets.json"
|
|
7531
8018
|
);
|
|
7532
8019
|
for (const path of [manifestPath, extensionPath, assetsPath])
|
|
7533
|
-
if (!existsSync14(path) || !
|
|
8020
|
+
if (!existsSync14(path) || !lstatSync4(path).isFile() || lstatSync4(path).isSymbolicLink())
|
|
7534
8021
|
throw new Error(
|
|
7535
8022
|
`Required regular package asset is missing or symlinked: ${path}`
|
|
7536
8023
|
);
|
|
7537
|
-
const manifest = JSON.parse(
|
|
8024
|
+
const manifest = JSON.parse(readFileSync13(manifestPath, "utf8"));
|
|
7538
8025
|
if (manifest.name !== "thoth-agents" || manifest.version !== input.version || JSON.stringify(manifest.pi) !== JSON.stringify({ extensions: ["./dist/pi.js"], skills: ["./skills"] }))
|
|
7539
8026
|
throw new Error(
|
|
7540
8027
|
"Installed package identity or Pi manifest is inconsistent."
|
|
7541
8028
|
);
|
|
7542
|
-
const assets = JSON.parse(
|
|
8029
|
+
const assets = JSON.parse(readFileSync13(assetsPath, "utf8"));
|
|
7543
8030
|
const expectedAgentPaths = PI_SPECIALIST_ROLES.map(
|
|
7544
8031
|
(role) => `agents/${piSpecialistName(role)}.md`
|
|
7545
8032
|
);
|
|
7546
8033
|
if (assets.schemaVersion !== 1 || assets.owner !== "thoth-agents" || !isRecord6(assets.files) || Object.keys(assets.files).sort().join("|") !== [...expectedAgentPaths].sort().join("|"))
|
|
7547
8034
|
throw new Error("Installed Pi specialist provenance is invalid.");
|
|
7548
8035
|
for (const relativePath of expectedAgentPaths) {
|
|
7549
|
-
const path =
|
|
7550
|
-
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])
|
|
7551
8038
|
throw new Error(
|
|
7552
8039
|
`Installed Pi specialist asset is missing, symlinked, or stale: ${relativePath}`
|
|
7553
8040
|
);
|
|
7554
8041
|
}
|
|
7555
8042
|
for (const skill of THOTH_OWNED_SKILL_NAMES) {
|
|
7556
|
-
const path =
|
|
7557
|
-
if (!existsSync14(path) || !
|
|
8043
|
+
const path = join12(input.packageRoot, "skills", skill, "SKILL.md");
|
|
8044
|
+
if (!existsSync14(path) || !lstatSync4(path).isFile() || lstatSync4(path).isSymbolicLink())
|
|
7558
8045
|
throw new Error(
|
|
7559
8046
|
`Installed Pi manifest skill is missing or symlinked: ${skill}`
|
|
7560
8047
|
);
|
|
7561
8048
|
}
|
|
7562
8049
|
const manifestSha256 = sha2562(manifestPath);
|
|
7563
8050
|
const extensionSha256 = sha2562(extensionPath);
|
|
7564
|
-
const load = defaultCommandExecutor4("node", [
|
|
7565
|
-
"--input-type=module",
|
|
7566
|
-
"--eval",
|
|
7567
|
-
`import(${JSON.stringify(pathToFileURL(extensionPath).href)}).then(m=>{if(typeof m.default!=="function")process.exit(2)})`
|
|
7568
|
-
]);
|
|
7569
|
-
if (load.exitCode !== 0)
|
|
7570
|
-
throw new Error(`Compiled Pi extension is not loadable: ${load.stderr}`);
|
|
7571
8051
|
const observation = observePiNativeRoot({
|
|
7572
8052
|
extensionPath,
|
|
7573
8053
|
manifestSha256,
|
|
@@ -7600,11 +8080,264 @@ function isRecord6(value) {
|
|
|
7600
8080
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7601
8081
|
}
|
|
7602
8082
|
function readJsonObject(path) {
|
|
8083
|
+
assertSafePiManagedPath(path);
|
|
7603
8084
|
if (!existsSync14(path)) return {};
|
|
7604
|
-
const parsed = JSON.parse(
|
|
8085
|
+
const parsed = JSON.parse(readFileSync13(path, "utf8"));
|
|
7605
8086
|
if (!isRecord6(parsed)) throw new Error(`${path} must contain a JSON object.`);
|
|
7606
8087
|
return parsed;
|
|
7607
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
|
+
}
|
|
7608
8341
|
function mergePiGrepMcpConfig(current) {
|
|
7609
8342
|
const rawServers = current.mcpServers;
|
|
7610
8343
|
if (rawServers !== void 0 && !isRecord6(rawServers)) {
|
|
@@ -7645,8 +8378,8 @@ function findAgentConflicts(paths) {
|
|
|
7645
8378
|
if (!existsSync14(root)) continue;
|
|
7646
8379
|
for (const name of readdirSync3(root)) {
|
|
7647
8380
|
if (!name.endsWith(".md")) continue;
|
|
7648
|
-
const path =
|
|
7649
|
-
const content =
|
|
8381
|
+
const path = join12(root, name);
|
|
8382
|
+
const content = readFileSync13(path, "utf8");
|
|
7650
8383
|
const role = frontmatterValue(content, "name");
|
|
7651
8384
|
const filenameRole = name.slice(0, -".md".length).toLowerCase();
|
|
7652
8385
|
const canonicalRole = role && canonical.has(role) ? role : canonical.has(filenameRole) ? filenameRole : void 0;
|
|
@@ -7678,8 +8411,57 @@ function buildPiSetupPlan(options = {}) {
|
|
|
7678
8411
|
);
|
|
7679
8412
|
}
|
|
7680
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;
|
|
7681
8430
|
let mcpContent;
|
|
7682
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
|
+
`;
|
|
7683
8465
|
mcpContent = `${JSON.stringify(
|
|
7684
8466
|
mergePiGrepMcpConfig(readJsonObject(paths.mcpConfigPath)),
|
|
7685
8467
|
null,
|
|
@@ -7689,6 +8471,40 @@ function buildPiSetupPlan(options = {}) {
|
|
|
7689
8471
|
} catch (error) {
|
|
7690
8472
|
blockers.push(error instanceof Error ? error.message : String(error));
|
|
7691
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));
|
|
7692
8508
|
for (const path of [...paths.projectAgentRoots, ...paths.projectMcpPaths]) {
|
|
7693
8509
|
if (existsSync14(path))
|
|
7694
8510
|
diagnostics.push(
|
|
@@ -7706,7 +8522,7 @@ function buildPiSetupPlan(options = {}) {
|
|
|
7706
8522
|
const items = [
|
|
7707
8523
|
{
|
|
7708
8524
|
kind: "preflight",
|
|
7709
|
-
description:
|
|
8525
|
+
description: `Verify Node.js >=22.19 and Pi >=${PI_MINIMUM_VERSION} before mutation`,
|
|
7710
8526
|
target: "node/pi runtime"
|
|
7711
8527
|
},
|
|
7712
8528
|
{
|
|
@@ -7718,12 +8534,26 @@ function buildPiSetupPlan(options = {}) {
|
|
|
7718
8534
|
args: ["install", firstPartySource, "--no-approve"]
|
|
7719
8535
|
}
|
|
7720
8536
|
},
|
|
7721
|
-
...
|
|
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) => ({
|
|
7722
8544
|
kind: "package",
|
|
7723
8545
|
description: `Install and verify Pi package ${pkg.source}`,
|
|
7724
8546
|
target: pkg.source,
|
|
7725
8547
|
command: { command: "pi", args: ["install", pkg.source, "--no-approve"] }
|
|
7726
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
|
+
],
|
|
7727
8557
|
...mcpContent === void 0 ? [] : [
|
|
7728
8558
|
{
|
|
7729
8559
|
kind: "mcp",
|
|
@@ -7735,7 +8565,7 @@ function buildPiSetupPlan(options = {}) {
|
|
|
7735
8565
|
...rendered.artifacts.filter((artifact) => artifact.kind === "agent-config").map((artifact) => ({
|
|
7736
8566
|
kind: "agent",
|
|
7737
8567
|
description: artifact.description ?? `Install ${artifact.path}`,
|
|
7738
|
-
target:
|
|
8568
|
+
target: join12(paths.piRoot, artifact.path),
|
|
7739
8569
|
content: String(artifact.content)
|
|
7740
8570
|
}))
|
|
7741
8571
|
];
|
|
@@ -7746,10 +8576,12 @@ function buildPiSetupPlan(options = {}) {
|
|
|
7746
8576
|
items,
|
|
7747
8577
|
blockers,
|
|
7748
8578
|
diagnostics,
|
|
8579
|
+
projectIncumbentTodos,
|
|
7749
8580
|
disclaimers: [
|
|
7750
|
-
"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.",
|
|
7751
8582
|
"Context7 and web access are native Pi extensions; only grep.app uses pi-mcp-adapter and directTools is intentionally omitted.",
|
|
7752
|
-
"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."
|
|
7753
8585
|
],
|
|
7754
8586
|
options: { ...options, expectedVersion, packageRoot, firstPartySource }
|
|
7755
8587
|
};
|
|
@@ -7800,33 +8632,108 @@ function parsePiPackageList(output) {
|
|
|
7800
8632
|
}
|
|
7801
8633
|
return packages;
|
|
7802
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
|
+
}
|
|
7803
8706
|
function isThothPackageLocation(candidate, knownSource) {
|
|
7804
8707
|
if (candidate.source === knownSource || /^npm:thoth-agents@/.test(candidate.source))
|
|
7805
8708
|
return true;
|
|
7806
8709
|
if (!candidate.installedPath) return false;
|
|
7807
8710
|
try {
|
|
7808
8711
|
const manifest = JSON.parse(
|
|
7809
|
-
|
|
8712
|
+
readFileSync13(join12(candidate.installedPath, "package.json"), "utf8")
|
|
7810
8713
|
);
|
|
7811
8714
|
return manifest.name === "thoth-agents";
|
|
7812
8715
|
} catch {
|
|
7813
8716
|
return false;
|
|
7814
8717
|
}
|
|
7815
8718
|
}
|
|
7816
|
-
function
|
|
7817
|
-
if (!candidate.installedPath) return
|
|
8719
|
+
function configuredPackageManifest(candidate) {
|
|
8720
|
+
if (!candidate.installedPath) return void 0;
|
|
7818
8721
|
try {
|
|
7819
8722
|
const manifest = JSON.parse(
|
|
7820
|
-
|
|
8723
|
+
readFileSync13(join12(candidate.installedPath, "package.json"), "utf8")
|
|
7821
8724
|
);
|
|
7822
|
-
return
|
|
7823
|
-
packageName: typeof manifest.name === "string" ? manifest.name : "",
|
|
7824
|
-
packageVersion: typeof manifest.version === "string" ? manifest.version : ""
|
|
7825
|
-
};
|
|
8725
|
+
return isRecord6(manifest) ? manifest : void 0;
|
|
7826
8726
|
} catch {
|
|
7827
|
-
return
|
|
8727
|
+
return void 0;
|
|
7828
8728
|
}
|
|
7829
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
|
+
}
|
|
7830
8737
|
function getPiFirstPartyPackages(packages, knownSource) {
|
|
7831
8738
|
return packages.filter((candidate) => isThothPackageLocation(candidate, knownSource)).map((candidate) => ({
|
|
7832
8739
|
...candidate,
|
|
@@ -7834,30 +8741,25 @@ function getPiFirstPartyPackages(packages, knownSource) {
|
|
|
7834
8741
|
}));
|
|
7835
8742
|
}
|
|
7836
8743
|
function matchesInstallSource(candidate, installSource) {
|
|
7837
|
-
return
|
|
8744
|
+
return isAbsolute4(installSource) ? candidate.installedPath !== void 0 && piPackagePathsEqual(candidate.installedPath, installSource) : candidate.source === installSource;
|
|
7838
8745
|
}
|
|
7839
8746
|
function defaultCommandExecutor4(command, args) {
|
|
7840
8747
|
let executable = command;
|
|
7841
8748
|
let executableArgs = [...args];
|
|
7842
8749
|
if (process.platform === "win32" && command === "pi") {
|
|
7843
|
-
const
|
|
7844
|
-
|
|
7845
|
-
dirname9(located),
|
|
7846
|
-
"node_modules",
|
|
7847
|
-
"@earendil-works",
|
|
7848
|
-
"pi-coding-agent",
|
|
7849
|
-
"dist",
|
|
7850
|
-
"bundle",
|
|
7851
|
-
"cli.js"
|
|
7852
|
-
) : void 0;
|
|
7853
|
-
if (cli && existsSync14(cli)) {
|
|
8750
|
+
const cli = findPiWindowsCli();
|
|
8751
|
+
if (cli) {
|
|
7854
8752
|
executable = process.execPath;
|
|
7855
8753
|
executableArgs = [cli, ...args];
|
|
7856
8754
|
}
|
|
7857
8755
|
}
|
|
7858
8756
|
const result = spawnSync6(executable, executableArgs, {
|
|
8757
|
+
windowsHide: true,
|
|
7859
8758
|
encoding: "utf8",
|
|
7860
|
-
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))
|
|
7861
8763
|
});
|
|
7862
8764
|
return {
|
|
7863
8765
|
exitCode: result.status,
|
|
@@ -7866,21 +8768,6 @@ function defaultCommandExecutor4(command, args) {
|
|
|
7866
8768
|
error: result.error
|
|
7867
8769
|
};
|
|
7868
8770
|
}
|
|
7869
|
-
function writePiManagedText(path, content) {
|
|
7870
|
-
if (existsSync14(path) && readFileSync11(path, "utf8") === content) return false;
|
|
7871
|
-
mkdirSync7(dirname9(path), { recursive: true });
|
|
7872
|
-
const temporaryPath = `${path}.tmp`;
|
|
7873
|
-
const backupPath = `${path}.bak`;
|
|
7874
|
-
writeFileSync7(temporaryPath, content);
|
|
7875
|
-
try {
|
|
7876
|
-
if (existsSync14(path)) copyFileSync5(path, backupPath);
|
|
7877
|
-
renameSync6(temporaryPath, path);
|
|
7878
|
-
} catch (error) {
|
|
7879
|
-
rmSync6(temporaryPath, { force: true });
|
|
7880
|
-
throw error;
|
|
7881
|
-
}
|
|
7882
|
-
return true;
|
|
7883
|
-
}
|
|
7884
8771
|
function applyPiSetup(plan) {
|
|
7885
8772
|
const diagnostics = [...plan.diagnostics];
|
|
7886
8773
|
if (!plan.ready)
|
|
@@ -7899,7 +8786,14 @@ function applyPiSetup(plan) {
|
|
|
7899
8786
|
const installedPackages = [];
|
|
7900
8787
|
let receiptCommitted = false;
|
|
7901
8788
|
let configuredPackageRoot;
|
|
8789
|
+
let manualRecovery;
|
|
7902
8790
|
try {
|
|
8791
|
+
for (const path of [
|
|
8792
|
+
plan.paths.settingsPath,
|
|
8793
|
+
plan.paths.subagentsConfigPath,
|
|
8794
|
+
plan.paths.mcpConfigPath
|
|
8795
|
+
])
|
|
8796
|
+
assertSafePiManagedPath(path);
|
|
7903
8797
|
const node = execute("node", ["--version"]);
|
|
7904
8798
|
if (node.exitCode !== 0 || !isVersionAtLeast(node.stdout.trim(), PI_NODE_MINIMUM))
|
|
7905
8799
|
throw new Error(
|
|
@@ -7922,7 +8816,104 @@ function applyPiSetup(plan) {
|
|
|
7922
8816
|
...plan.options.receiptOptions
|
|
7923
8817
|
};
|
|
7924
8818
|
const receipt = readPiPackageReceipt(receiptOptions);
|
|
7925
|
-
|
|
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
|
+
}
|
|
7926
8917
|
const knownReceiptSource = receipt.status === "valid" ? receipt.receipt.source : void 0;
|
|
7927
8918
|
const firstPartyBefore = getPiFirstPartyPackages(
|
|
7928
8919
|
configuredBefore,
|
|
@@ -7943,6 +8934,20 @@ function applyPiSetup(plan) {
|
|
|
7943
8934
|
throw new Error(
|
|
7944
8935
|
`First-party Pi package ownership conflict: ${ownership.reason ?? ownership.state}`
|
|
7945
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
|
+
};
|
|
7946
8951
|
const installed = execute("pi", ["install", desiredSource, "--no-approve"]);
|
|
7947
8952
|
if (installed.exitCode !== 0)
|
|
7948
8953
|
throw new Error(
|
|
@@ -7972,7 +8977,7 @@ function applyPiSetup(plan) {
|
|
|
7972
8977
|
`Pi did not verify the exact installed package source ${desiredSource}.`
|
|
7973
8978
|
);
|
|
7974
8979
|
const resolvedPackageRoot = candidates[0]?.installedPath;
|
|
7975
|
-
if (!resolvedPackageRoot || !
|
|
8980
|
+
if (!resolvedPackageRoot || !isAbsolute4(resolvedPackageRoot) || !existsSync14(resolvedPackageRoot) || !lstatSync4(resolvedPackageRoot).isDirectory() || lstatSync4(resolvedPackageRoot).isSymbolicLink())
|
|
7976
8981
|
throw new Error(
|
|
7977
8982
|
`Pi did not report one absolute regular installed directory for ${desiredSource}.`
|
|
7978
8983
|
);
|
|
@@ -8050,25 +9055,130 @@ function applyPiSetup(plan) {
|
|
|
8050
9055
|
if (!migration.success)
|
|
8051
9056
|
throw new Error(migration.error ?? "Pi legacy migration failed.");
|
|
8052
9057
|
changed.push(...migration.changed);
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
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)
|
|
9093
|
+
throw new Error(
|
|
9094
|
+
`Failed to remove conflicting Pi question package ${incumbent.source}: ${removed.stderr.trim() || "unknown Pi error"}`
|
|
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
|
+
))
|
|
8056
9115
|
throw new Error(
|
|
8057
|
-
|
|
9116
|
+
"Pi did not verify removal of the conflicting user question package; rerun pi list and remove it before retrying setup."
|
|
8058
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
|
+
}
|
|
8059
9145
|
const listed = execute("pi", ["list", "--no-approve"]);
|
|
8060
|
-
|
|
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;
|
|
8061
9167
|
throw new Error(
|
|
8062
|
-
`Pi
|
|
9168
|
+
`Pi attempted to downgrade ${pkg.packageName} from ${prior.version} to ${verified.version}; exact-version recovery ${recoverySucceeded ? "verified" : "failed or unverifiable"}. ${recoveryGuidance}`
|
|
8063
9169
|
);
|
|
8064
9170
|
}
|
|
9171
|
+
if (verified.state !== "installed")
|
|
9172
|
+
throw new Error(`Pi did not verify ${pkg.source}: ${verified.reason}.`);
|
|
8065
9173
|
installedPackages.push(pkg.source);
|
|
8066
9174
|
}
|
|
8067
9175
|
for (const item of plan.items.filter(
|
|
8068
|
-
(candidate) => candidate.content !== void 0 && candidate.kind === "mcp"
|
|
9176
|
+
(candidate) => candidate.content !== void 0 && (candidate.kind === "settings" || candidate.kind === "mcp")
|
|
8069
9177
|
)) {
|
|
8070
|
-
|
|
8071
|
-
|
|
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);
|
|
8072
9182
|
}
|
|
8073
9183
|
const resources = syncPiSpecialists({
|
|
8074
9184
|
packageRoot: configuredPackageRoot,
|
|
@@ -8097,7 +9207,8 @@ function applyPiSetup(plan) {
|
|
|
8097
9207
|
error: error instanceof Error ? error.message : String(error),
|
|
8098
9208
|
failedStep: installedPackages.length < plan.items.filter((item) => item.kind === "package").length ? "package" : "managed-surface",
|
|
8099
9209
|
installedPackages,
|
|
8100
|
-
receiptCommitted
|
|
9210
|
+
receiptCommitted,
|
|
9211
|
+
manualRecovery
|
|
8101
9212
|
};
|
|
8102
9213
|
}
|
|
8103
9214
|
}
|
|
@@ -8177,7 +9288,11 @@ function disclaimers() {
|
|
|
8177
9288
|
},
|
|
8178
9289
|
{
|
|
8179
9290
|
code: "pi-runtime-owned",
|
|
8180
|
-
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."
|
|
8181
9296
|
},
|
|
8182
9297
|
{
|
|
8183
9298
|
code: "pi-research-independent",
|
|
@@ -8237,6 +9352,12 @@ function runtimeTarget(provider, evidence) {
|
|
|
8237
9352
|
description: evidence.basis.join("; ")
|
|
8238
9353
|
};
|
|
8239
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
|
+
}
|
|
8240
9361
|
function runtimeDiagnostic(provider, evidence) {
|
|
8241
9362
|
if (evidence.state === "ready") return void 0;
|
|
8242
9363
|
const code = `pi-${provider}-runtime-${evidence.state}`;
|
|
@@ -8252,12 +9373,13 @@ function statusFromPlan(plan, context2, evidence = {}) {
|
|
|
8252
9373
|
kind: item.kind === "package" ? "package" : item.kind === "agent" ? "file" : item.kind === "mcp" ? "config" : "file",
|
|
8253
9374
|
path: item.target,
|
|
8254
9375
|
label: item.description,
|
|
8255
|
-
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",
|
|
8256
9377
|
expected: item.kind === "package" ? item.target : "attributable managed content",
|
|
8257
|
-
description: item.kind === "package" ? "
|
|
9378
|
+
description: item.kind === "package" ? "Validated installed-package manifest evidence; this does not prove live tool availability." : void 0
|
|
8258
9379
|
}));
|
|
8259
9380
|
const execute = context2.piCommandExecutor ?? ((command, args) => {
|
|
8260
9381
|
const result = spawnSync7(command, [...args], {
|
|
9382
|
+
windowsHide: true,
|
|
8261
9383
|
encoding: "utf8",
|
|
8262
9384
|
timeout: 5e3,
|
|
8263
9385
|
env: {
|
|
@@ -8293,19 +9415,64 @@ function statusFromPlan(plan, context2, evidence = {}) {
|
|
|
8293
9415
|
observed: pi.stdout.trim() || pi.stderr.trim() || "unavailable"
|
|
8294
9416
|
}
|
|
8295
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);
|
|
8296
9425
|
for (const target of targets.filter(
|
|
8297
|
-
(candidate) => candidate.kind === "package" && candidate.path
|
|
9426
|
+
(candidate) => candidate.kind === "package" && packageSpecs.some(({ source }) => source === candidate.path)
|
|
8298
9427
|
)) {
|
|
8299
|
-
const
|
|
8300
|
-
|
|
8301
|
-
|
|
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
|
+
}
|
|
8302
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
|
+
});
|
|
8303
9471
|
const receiptOptions = context2.installLedgerOptions ?? {
|
|
8304
9472
|
env: context2.env,
|
|
8305
9473
|
homeDir: context2.homeDir
|
|
8306
9474
|
};
|
|
8307
9475
|
const receipt = readPiPackageReceipt(receiptOptions);
|
|
8308
|
-
const configuredPackages = packages.exitCode === 0 ? parsePiPackageList(packages.stdout) : [];
|
|
8309
9476
|
const firstPartyPackages = getPiFirstPartyPackages(
|
|
8310
9477
|
configuredPackages,
|
|
8311
9478
|
receipt.status === "valid" ? receipt.receipt.source : void 0
|
|
@@ -8340,7 +9507,7 @@ function statusFromPlan(plan, context2, evidence = {}) {
|
|
|
8340
9507
|
const configuredPackage = configuredCandidates[0];
|
|
8341
9508
|
const configuredPath = configuredPackage?.installedPath;
|
|
8342
9509
|
const localPathMatches = receipt.receipt.source === receipt.receipt.installSource || configuredPath !== void 0 && piPackagePathsEqual(configuredPath, receipt.receipt.installSource);
|
|
8343
|
-
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;
|
|
8344
9511
|
if (configured) validatedPackageRoot = configuredPath;
|
|
8345
9512
|
targets.push({
|
|
8346
9513
|
kind: "surface",
|
|
@@ -8350,11 +9517,11 @@ function statusFromPlan(plan, context2, evidence = {}) {
|
|
|
8350
9517
|
expected: `${receipt.receipt.source} -> ${receipt.receipt.installSource}`,
|
|
8351
9518
|
observed: configuredPackage ? `${configuredPackage.source} -> ${configuredPath ?? "unavailable"}` : "unavailable"
|
|
8352
9519
|
});
|
|
8353
|
-
const extensionPath = configuredPath ?
|
|
8354
|
-
const manifestPath = configuredPath ?
|
|
9520
|
+
const extensionPath = configuredPath ? join13(configuredPath, "dist", "pi.js") : void 0;
|
|
9521
|
+
const manifestPath = configuredPath ? join13(configuredPath, "package.json") : void 0;
|
|
8355
9522
|
let receiptMatches = false;
|
|
8356
9523
|
if (extensionPath && manifestPath && existsSync15(extensionPath) && existsSync15(manifestPath))
|
|
8357
|
-
receiptMatches =
|
|
9524
|
+
receiptMatches = createHash6("sha256").update(readFileSync14(extensionPath)).digest("hex") === receipt.receipt.extensionSha256 && createHash6("sha256").update(readFileSync14(manifestPath)).digest("hex") === receipt.receipt.manifestSha256;
|
|
8358
9525
|
targets.push({
|
|
8359
9526
|
kind: "surface",
|
|
8360
9527
|
path: extensionPath,
|
|
@@ -8381,7 +9548,7 @@ function statusFromPlan(plan, context2, evidence = {}) {
|
|
|
8381
9548
|
} : void 0);
|
|
8382
9549
|
return {
|
|
8383
9550
|
kind: "skill",
|
|
8384
|
-
path:
|
|
9551
|
+
path: join13(skill.destinationPath, "SKILL.md"),
|
|
8385
9552
|
label: `Pi package-declared skill: ${skill.name}`,
|
|
8386
9553
|
state: issue?.state ?? "installed",
|
|
8387
9554
|
...issue ? { observed: issue.message } : {}
|
|
@@ -8417,6 +9584,33 @@ function statusFromPlan(plan, context2, evidence = {}) {
|
|
|
8417
9584
|
...plan.blockers.map(
|
|
8418
9585
|
(message) => warning3(message, "pi-preflight-blocked", "critical")
|
|
8419
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
|
+
),
|
|
8420
9614
|
...plan.diagnostics.map(
|
|
8421
9615
|
(message) => warning3(message, "pi-resource-shadowing")
|
|
8422
9616
|
),
|
|
@@ -8457,6 +9651,7 @@ function contextPlan(context2, dryRun = true) {
|
|
|
8457
9651
|
homeDir: context2.homeDir,
|
|
8458
9652
|
commandExecutor: context2.piCommandExecutor,
|
|
8459
9653
|
packageRoot: context2.packageRoot ?? (version.ok ? version.packageRoot : void 0),
|
|
9654
|
+
runtimePackageRoot: context2.runtimePackageRoot,
|
|
8460
9655
|
expectedVersion: version.ok ? version.version : void 0,
|
|
8461
9656
|
receiptOptions: context2.installLedgerOptions
|
|
8462
9657
|
});
|
|
@@ -8476,7 +9671,7 @@ function configuredPackageRootFromStatus(status) {
|
|
|
8476
9671
|
const configured = status.targets.find(
|
|
8477
9672
|
({ label }) => label === "First-party Pi configured source"
|
|
8478
9673
|
);
|
|
8479
|
-
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;
|
|
8480
9675
|
}
|
|
8481
9676
|
function piPlan(action, context2) {
|
|
8482
9677
|
const setup = contextPlan(context2);
|
|
@@ -8502,7 +9697,7 @@ function piPlan(action, context2) {
|
|
|
8502
9697
|
items.push({
|
|
8503
9698
|
title: "Inspect five package-declared Pi skills",
|
|
8504
9699
|
target: { kind: "skill", label: "Pi package-declared skills" },
|
|
8505
|
-
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"
|
|
8506
9701
|
});
|
|
8507
9702
|
if (complete) {
|
|
8508
9703
|
items.push(
|
|
@@ -8553,6 +9748,25 @@ function piPlan(action, context2) {
|
|
|
8553
9748
|
observed: `${ownershipState}; remove the configured first-party package manually or restore its matching receipt before applying ${action}.`
|
|
8554
9749
|
}
|
|
8555
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
|
+
}));
|
|
8556
9770
|
const syncSkillsUnavailable = action === "sync" && (!configuredPackageRoot || status.targets.some(
|
|
8557
9771
|
({ label, state }) => label?.startsWith("Pi package-declared skill:") && state !== "installed"
|
|
8558
9772
|
));
|
|
@@ -8571,7 +9785,7 @@ function piPlan(action, context2) {
|
|
|
8571
9785
|
title: `${action === "install" ? "Install" : action === "update" ? "Update" : "Sync"} Pi`,
|
|
8572
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.",
|
|
8573
9787
|
dryRun: true,
|
|
8574
|
-
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,
|
|
8575
9789
|
targets: status.targets,
|
|
8576
9790
|
blockerTargets: [
|
|
8577
9791
|
...setup.blockers.map((message) => ({
|
|
@@ -8581,6 +9795,8 @@ function piPlan(action, context2) {
|
|
|
8581
9795
|
observed: message
|
|
8582
9796
|
})),
|
|
8583
9797
|
...ownershipBlockers,
|
|
9798
|
+
...delegationBlockers,
|
|
9799
|
+
...todoBlockers,
|
|
8584
9800
|
...syncSkillBlockers
|
|
8585
9801
|
],
|
|
8586
9802
|
surfaces: setup.items.filter((item) => item.kind !== "preflight").map((item) => ({
|
|
@@ -8628,17 +9844,18 @@ function defaultPiModelRoles(context2 = { cwd: process.cwd() }) {
|
|
|
8628
9844
|
(role) => isPiSpecialistRole(role.name)
|
|
8629
9845
|
).map((role) => {
|
|
8630
9846
|
const path = plan.items.find(
|
|
8631
|
-
(item) => item.kind === "agent" &&
|
|
9847
|
+
(item) => item.kind === "agent" && basename4(item.target) === `${piSpecialistName(role.name)}.md`
|
|
8632
9848
|
)?.target;
|
|
8633
|
-
|
|
8634
|
-
|
|
9849
|
+
if (path && existsSync15(path)) {
|
|
9850
|
+
const configured = readPiModelConfig(dirname11(dirname11(path)), [
|
|
9851
|
+
role.name
|
|
9852
|
+
]).roles[0];
|
|
9853
|
+
if (configured) return configured;
|
|
9854
|
+
}
|
|
8635
9855
|
return {
|
|
8636
9856
|
role: role.name,
|
|
8637
|
-
model:
|
|
8638
|
-
effort:
|
|
8639
|
-
const value = /^effort:\s*["']?([^"'\r\n]+)/m.exec(content)?.[1]?.trim();
|
|
8640
|
-
return value && value !== "default" && value !== "inherit" ? { kind: "effort", value } : { kind: "inherit" };
|
|
8641
|
-
})()
|
|
9857
|
+
model: "inherit",
|
|
9858
|
+
effort: { kind: "inherit" }
|
|
8642
9859
|
};
|
|
8643
9860
|
});
|
|
8644
9861
|
}
|
|
@@ -8656,7 +9873,7 @@ function buildPiModelPlan(input, context2 = { cwd: process.cwd() }) {
|
|
|
8656
9873
|
target: {
|
|
8657
9874
|
kind: "file",
|
|
8658
9875
|
path: specialist ? setup.items.find(
|
|
8659
|
-
(item) => item.kind === "agent" &&
|
|
9876
|
+
(item) => item.kind === "agent" && basename4(item.target) === `${specialist}.md`
|
|
8660
9877
|
)?.target : void 0,
|
|
8661
9878
|
label: `Pi ${role.role} specialist`
|
|
8662
9879
|
},
|
|
@@ -8696,26 +9913,35 @@ function buildPiModelPlan(input, context2 = { cwd: process.cwd() }) {
|
|
|
8696
9913
|
],
|
|
8697
9914
|
disclaimers: disclaimers()
|
|
8698
9915
|
};
|
|
8699
|
-
|
|
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
|
+
});
|
|
8700
9943
|
return plan;
|
|
8701
9944
|
}
|
|
8702
|
-
function replaceFrontmatterField(content, field, value) {
|
|
8703
|
-
const newline = content.includes("\r\n") ? "\r\n" : "\n";
|
|
8704
|
-
const lines = content.split(/\r?\n/);
|
|
8705
|
-
if (lines[0]?.trim() !== "---") return content;
|
|
8706
|
-
const end = lines.findIndex(
|
|
8707
|
-
(line, index2) => index2 > 0 && line.trim() === "---"
|
|
8708
|
-
);
|
|
8709
|
-
if (end === -1) return content;
|
|
8710
|
-
const prefix = `${field}:`;
|
|
8711
|
-
const index = lines.slice(1, end).findIndex((line) => line.startsWith(prefix));
|
|
8712
|
-
const absoluteIndex = index === -1 ? -1 : index + 1;
|
|
8713
|
-
const nativeValue = value === void 0 || value === "inherit" ? "default" : value;
|
|
8714
|
-
const replacement = `${field}: ${JSON.stringify(nativeValue)}`;
|
|
8715
|
-
if (absoluteIndex === -1) lines.splice(1, 0, replacement);
|
|
8716
|
-
else lines[absoluteIndex] = replacement;
|
|
8717
|
-
return lines.join(newline);
|
|
8718
|
-
}
|
|
8719
9945
|
function applyPiPlan(plan) {
|
|
8720
9946
|
const source = planSources.get(plan);
|
|
8721
9947
|
const fileTargets = (paths) => paths.map((path) => ({ kind: "file", path, state: "installed" }));
|
|
@@ -8737,28 +9963,23 @@ function applyPiPlan(plan) {
|
|
|
8737
9963
|
"Pi operation plan is not applicable or was not built in this process."
|
|
8738
9964
|
);
|
|
8739
9965
|
if (plan.action === "model-config" && source.model) {
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
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
|
|
8753
9982
|
);
|
|
8754
|
-
writePiManagedText(target, content);
|
|
8755
|
-
changedTargets.push({
|
|
8756
|
-
kind: "file",
|
|
8757
|
-
path: target,
|
|
8758
|
-
label: `Pi ${role.role} specialist`,
|
|
8759
|
-
state: "installed"
|
|
8760
|
-
});
|
|
8761
|
-
}
|
|
8762
9983
|
return {
|
|
8763
9984
|
harness: "pi",
|
|
8764
9985
|
action: plan.action,
|
|
@@ -8832,7 +10053,7 @@ function applyPiPlan(plan) {
|
|
|
8832
10053
|
changedTargets: fileTargets(changed),
|
|
8833
10054
|
diagnosticTargets: packageSkills.skills.map((skill) => ({
|
|
8834
10055
|
kind: "skill",
|
|
8835
|
-
path:
|
|
10056
|
+
path: join13(skill.destinationPath, "SKILL.md"),
|
|
8836
10057
|
label: `Pi package-declared skill: ${skill.name}`,
|
|
8837
10058
|
state: "installed"
|
|
8838
10059
|
})),
|
|
@@ -8919,7 +10140,7 @@ function applyPiPlan(plan) {
|
|
|
8919
10140
|
],
|
|
8920
10141
|
diagnosticTargets: packageSkills.skills.map((skill) => ({
|
|
8921
10142
|
kind: "skill",
|
|
8922
|
-
path:
|
|
10143
|
+
path: join13(skill.destinationPath, "SKILL.md"),
|
|
8923
10144
|
label: `Pi package-declared skill: ${skill.name}`,
|
|
8924
10145
|
state: "installed"
|
|
8925
10146
|
})),
|
|
@@ -8956,14 +10177,14 @@ import { execFileSync } from "child_process";
|
|
|
8956
10177
|
// src/cli/model-catalog/cache.ts
|
|
8957
10178
|
import {
|
|
8958
10179
|
existsSync as existsSync16,
|
|
8959
|
-
mkdirSync as
|
|
8960
|
-
readFileSync as
|
|
8961
|
-
renameSync as
|
|
10180
|
+
mkdirSync as mkdirSync7,
|
|
10181
|
+
readFileSync as readFileSync15,
|
|
10182
|
+
renameSync as renameSync6,
|
|
8962
10183
|
unlinkSync,
|
|
8963
|
-
writeFileSync as
|
|
10184
|
+
writeFileSync as writeFileSync7
|
|
8964
10185
|
} from "fs";
|
|
8965
10186
|
import { homedir as homedir7 } from "os";
|
|
8966
|
-
import { dirname as
|
|
10187
|
+
import { dirname as dirname12, posix, win32 as win322 } from "path";
|
|
8967
10188
|
import { z as z2 } from "zod";
|
|
8968
10189
|
var modelCatalogSourceSchema = z2.enum(["remote", "lkg", "manual"]);
|
|
8969
10190
|
var cachedModelOptionSchema = z2.object({
|
|
@@ -8985,7 +10206,7 @@ function resolveModelsDevCachePath(options = {}) {
|
|
|
8985
10206
|
const env = options.env ?? process.env;
|
|
8986
10207
|
const home = options.homeDir ?? homedir7();
|
|
8987
10208
|
if (platform === "win32") {
|
|
8988
|
-
const path =
|
|
10209
|
+
const path = win322;
|
|
8989
10210
|
const base2 = env.LOCALAPPDATA ?? env.APPDATA ?? path.join(home, "AppData", "Local");
|
|
8990
10211
|
return path.join(base2, "thoth-agents", "models-dev-v1.json");
|
|
8991
10212
|
}
|
|
@@ -8995,7 +10216,7 @@ function resolveModelsDevCachePath(options = {}) {
|
|
|
8995
10216
|
function readModelsDevCache(cachePath) {
|
|
8996
10217
|
if (!existsSync16(cachePath)) return void 0;
|
|
8997
10218
|
try {
|
|
8998
|
-
const input = JSON.parse(
|
|
10219
|
+
const input = JSON.parse(readFileSync15(cachePath, "utf8"));
|
|
8999
10220
|
const parsed = modelsDevCacheSchema.safeParse(input);
|
|
9000
10221
|
return parsed.success ? parsed.data : void 0;
|
|
9001
10222
|
} catch {
|
|
@@ -9007,15 +10228,15 @@ function writeModelsDevCacheAtomic(cachePath, cache) {
|
|
|
9007
10228
|
if (!validated.success) {
|
|
9008
10229
|
throw new Error(`Invalid models.dev cache: ${validated.error.message}`);
|
|
9009
10230
|
}
|
|
9010
|
-
|
|
10231
|
+
mkdirSync7(dirname12(cachePath), { recursive: true });
|
|
9011
10232
|
const temporaryPath = `${cachePath}.${process.pid}.${Date.now()}.tmp`;
|
|
9012
10233
|
try {
|
|
9013
|
-
|
|
10234
|
+
writeFileSync7(
|
|
9014
10235
|
temporaryPath,
|
|
9015
10236
|
`${JSON.stringify(validated.data, null, 2)}
|
|
9016
10237
|
`
|
|
9017
10238
|
);
|
|
9018
|
-
|
|
10239
|
+
renameSync6(temporaryPath, cachePath);
|
|
9019
10240
|
} catch (error) {
|
|
9020
10241
|
if (existsSync16(temporaryPath)) unlinkSync(temporaryPath);
|
|
9021
10242
|
throw error;
|
|
@@ -9210,7 +10431,7 @@ var CODEX_DOCUMENTED_EFFORTS2 = /* @__PURE__ */ new Set([
|
|
|
9210
10431
|
"ultra"
|
|
9211
10432
|
]);
|
|
9212
10433
|
var CLAUDE_CODE_EFFORTS2 = ["low", "medium", "high", "xhigh", "max"];
|
|
9213
|
-
var
|
|
10434
|
+
var PI_EFFORTS = new Set(PI_EFFORT_VALUES);
|
|
9214
10435
|
function parseOpenCodeModels(output) {
|
|
9215
10436
|
const seen = /* @__PURE__ */ new Set();
|
|
9216
10437
|
const options = [];
|
|
@@ -9247,7 +10468,7 @@ function writableEfforts(harness, option) {
|
|
|
9247
10468
|
);
|
|
9248
10469
|
}
|
|
9249
10470
|
if (harness === "pi") {
|
|
9250
|
-
return option.efforts.filter((effort) =>
|
|
10471
|
+
return option.efforts.filter((effort) => PI_EFFORTS.has(effort));
|
|
9251
10472
|
}
|
|
9252
10473
|
return option.efforts.filter(
|
|
9253
10474
|
(effort) => resolveOpenCodeEffort({
|
|
@@ -9280,7 +10501,10 @@ function getNativeOpenCodeOptions() {
|
|
|
9280
10501
|
try {
|
|
9281
10502
|
const invocation = getOpenCodeModelsInvocation();
|
|
9282
10503
|
return parseOpenCodeModels(
|
|
9283
|
-
execFileSync(invocation.command, invocation.args,
|
|
10504
|
+
execFileSync(invocation.command, invocation.args, {
|
|
10505
|
+
...invocation.options,
|
|
10506
|
+
windowsHide: true
|
|
10507
|
+
})
|
|
9284
10508
|
);
|
|
9285
10509
|
} catch {
|
|
9286
10510
|
return [];
|
|
@@ -9340,7 +10564,38 @@ async function getModelOptions(harness) {
|
|
|
9340
10564
|
}
|
|
9341
10565
|
|
|
9342
10566
|
// src/cli/tui/operations.ts
|
|
9343
|
-
import { existsSync as existsSync17, readFileSync as
|
|
10567
|
+
import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
|
|
10568
|
+
|
|
10569
|
+
// src/cli/model-defaults.ts
|
|
10570
|
+
function getShippedModelRoles(harness) {
|
|
10571
|
+
if (harness === "opencode") {
|
|
10572
|
+
return ALL_AGENT_NAMES.map((role) => ({
|
|
10573
|
+
role,
|
|
10574
|
+
model: getDefaultOpenCodeModel(role),
|
|
10575
|
+
effort: { kind: "effort", value: getDefaultOpenCodeVariant(role) }
|
|
10576
|
+
}));
|
|
10577
|
+
}
|
|
10578
|
+
return SUBAGENT_NAMES.map((role) => {
|
|
10579
|
+
if (harness === "claude") {
|
|
10580
|
+
return {
|
|
10581
|
+
role,
|
|
10582
|
+
model: CLAUDE_CODE_SUBAGENT_DEFAULT_MODELS[role],
|
|
10583
|
+
effort: {
|
|
10584
|
+
kind: "effort",
|
|
10585
|
+
value: CLAUDE_CODE_SUBAGENT_DEFAULT_EFFORTS[role]
|
|
10586
|
+
}
|
|
10587
|
+
};
|
|
10588
|
+
}
|
|
10589
|
+
const preset = CONFIRMED_OPENAI_SUBAGENT_PRESET[role];
|
|
10590
|
+
return {
|
|
10591
|
+
role,
|
|
10592
|
+
model: harness === "pi" ? `openai-codex/${preset.model}` : preset.model,
|
|
10593
|
+
effort: { kind: "effort", value: preset.effort }
|
|
10594
|
+
};
|
|
10595
|
+
});
|
|
10596
|
+
}
|
|
10597
|
+
|
|
10598
|
+
// src/cli/tui/operations.ts
|
|
9344
10599
|
var context = { cwd: process.cwd() };
|
|
9345
10600
|
var codexContext = { cwd: process.cwd() };
|
|
9346
10601
|
var claudeCodeContext = {
|
|
@@ -9354,15 +10609,7 @@ var opencodeModelRoles = ALL_AGENT_NAMES.map(
|
|
|
9354
10609
|
effort: { kind: "effort", value: getDefaultOpenCodeVariant(role) }
|
|
9355
10610
|
})
|
|
9356
10611
|
);
|
|
9357
|
-
var codexModelRoles =
|
|
9358
|
-
(role) => ({
|
|
9359
|
-
role,
|
|
9360
|
-
model: "gpt-5.4-mini"
|
|
9361
|
-
})
|
|
9362
|
-
);
|
|
9363
|
-
var codexDefaultModels = new Map(
|
|
9364
|
-
codexModelRoles.map((role) => [role.role, role.model])
|
|
9365
|
-
);
|
|
10612
|
+
var codexModelRoles = getShippedModelRoles("codex");
|
|
9366
10613
|
function codexInstallConfig(source, dryRun) {
|
|
9367
10614
|
return {
|
|
9368
10615
|
dryRun,
|
|
@@ -9378,20 +10625,22 @@ function codexInstallConfig(source, dryRun) {
|
|
|
9378
10625
|
function getCodexModelRoles(source = codexContext) {
|
|
9379
10626
|
try {
|
|
9380
10627
|
const plan = buildCodexSetupPlan(codexInstallConfig(source, true));
|
|
9381
|
-
return
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
10628
|
+
return getShippedModelRoles("codex").map(
|
|
10629
|
+
({ role, model: defaultModel }) => {
|
|
10630
|
+
const item = plan.items.find(
|
|
10631
|
+
(candidate) => candidate.action === "write-role-toml" && candidate.role === role
|
|
10632
|
+
);
|
|
10633
|
+
const content = item && existsSync17(item.targetPath) ? readFileSync16(item.targetPath, "utf8") : item?.content;
|
|
10634
|
+
const effort = content ? parseRoleTomlEffort(content) : void 0;
|
|
10635
|
+
return {
|
|
10636
|
+
role,
|
|
10637
|
+
model: (content ? parseRoleTomlModel(content) : void 0) ?? defaultModel,
|
|
10638
|
+
effort: effort ? { kind: "effort", value: effort } : { kind: "inherit" }
|
|
10639
|
+
};
|
|
10640
|
+
}
|
|
10641
|
+
);
|
|
9393
10642
|
} catch {
|
|
9394
|
-
return
|
|
10643
|
+
return getShippedModelRoles("codex");
|
|
9395
10644
|
}
|
|
9396
10645
|
}
|
|
9397
10646
|
function getClaudeCodeModelRoles(_source = claudeCodeContext) {
|
|
@@ -9432,20 +10681,32 @@ function getOpenCodeModelRoles() {
|
|
|
9432
10681
|
};
|
|
9433
10682
|
});
|
|
9434
10683
|
}
|
|
9435
|
-
function buildTuiModelPlan(harness, roles) {
|
|
10684
|
+
function buildTuiModelPlan(harness, roles, source = context) {
|
|
9436
10685
|
if (harness === "opencode") {
|
|
9437
|
-
return buildOpenCodeModelPlan({ harness, dryRun: true, roles },
|
|
10686
|
+
return buildOpenCodeModelPlan({ harness, dryRun: true, roles }, source);
|
|
9438
10687
|
}
|
|
9439
10688
|
if (harness === "claude") {
|
|
9440
|
-
return buildClaudeCodeModelPlan(
|
|
9441
|
-
{ harness, dryRun: true, roles },
|
|
9442
|
-
claudeCodeContext
|
|
9443
|
-
);
|
|
10689
|
+
return buildClaudeCodeModelPlan({ harness, dryRun: true, roles }, source);
|
|
9444
10690
|
}
|
|
9445
10691
|
if (harness === "pi") {
|
|
9446
|
-
return buildPiModelPlan({ harness, dryRun: true, roles },
|
|
10692
|
+
return buildPiModelPlan({ harness, dryRun: true, roles }, source);
|
|
9447
10693
|
}
|
|
9448
|
-
return buildCodexModelPlan({ harness, dryRun: true, roles },
|
|
10694
|
+
return buildCodexModelPlan({ harness, dryRun: true, roles }, source);
|
|
10695
|
+
}
|
|
10696
|
+
function buildRestoreModelPlan(harness, options, source = context) {
|
|
10697
|
+
const roles = getShippedModelRoles(harness).map((role) => {
|
|
10698
|
+
const catalogId = harness === "codex" ? `openai/${role.model}` : harness === "pi" ? role.model.replace(/^openai-codex\//, "openai/") : role.model;
|
|
10699
|
+
const option = options.find((candidate) => candidate.id === role.model) ?? options.find((candidate) => candidate.catalogId === catalogId);
|
|
10700
|
+
return {
|
|
10701
|
+
...role,
|
|
10702
|
+
...option ? {
|
|
10703
|
+
provider: option.provider,
|
|
10704
|
+
catalogId: option.catalogId,
|
|
10705
|
+
availableEfforts: [...option.efforts]
|
|
10706
|
+
} : {}
|
|
10707
|
+
};
|
|
10708
|
+
});
|
|
10709
|
+
return buildTuiModelPlan(harness, roles, source);
|
|
9449
10710
|
}
|
|
9450
10711
|
var defaultTuiOperations = {
|
|
9451
10712
|
status(harness, evidence) {
|
|
@@ -9501,6 +10762,9 @@ var defaultTuiOperations = {
|
|
|
9501
10762
|
modelPlan(harness, roles) {
|
|
9502
10763
|
return buildTuiModelPlan(harness, roles);
|
|
9503
10764
|
},
|
|
10765
|
+
restoreModelPlan(harness, options) {
|
|
10766
|
+
return buildRestoreModelPlan(harness, options);
|
|
10767
|
+
},
|
|
9504
10768
|
apply(plan) {
|
|
9505
10769
|
if (plan.harness === "opencode") return applyOpenCodePlan(plan);
|
|
9506
10770
|
if (plan.harness === "claude") return applyClaudeCodePlan(plan);
|
|
@@ -9566,3 +10830,9 @@ export {
|
|
|
9566
10830
|
getOpenCodeModelRoles,
|
|
9567
10831
|
defaultTuiOperations
|
|
9568
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
|
+
*/
|