thoth-agents 0.3.12 → 0.4.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/README.md +181 -474
- package/dist/agents/prompt-dialects.d.ts +4 -1
- package/dist/agents/prompt-sections.d.ts +2 -1
- package/dist/{chunk-5UBJB5QO.js → chunk-5EBXGMOK.js} +1 -1
- package/dist/chunk-62L3ORXN.js +30 -0
- package/dist/{chunk-DX4U3AD3.js → chunk-Q6SDM4KP.js} +114 -57
- package/dist/chunk-SECDI3TL.js +312 -0
- package/dist/{chunk-VPVBYBPU.js → chunk-WJ2YMM2E.js} +2298 -188
- package/dist/cli/index.js +184 -13
- package/dist/cli/install-completion.d.ts +13 -0
- package/dist/cli/install-ledger.d.ts +1 -1
- package/dist/cli/install.d.ts +11 -1
- package/dist/cli/operations/pi.d.ts +43 -0
- package/dist/cli/owned-skills.d.ts +17 -0
- package/dist/cli/pi-effort.d.ts +10 -0
- package/dist/cli/pi-install.d.ts +131 -0
- package/dist/cli/pi-migration.d.ts +12 -0
- package/dist/cli/pi-native-probe.d.ts +34 -0
- package/dist/cli/pi-package-receipt.d.ts +59 -0
- package/dist/cli/pi-paths.d.ts +20 -0
- package/dist/cli/pi-resources.d.ts +15 -0
- package/dist/cli/skills.d.ts +1 -1
- package/dist/cli/tui/index.js +12 -4
- package/dist/cli/tui/operations.d.ts +1 -0
- package/dist/cli/types.d.ts +4 -2
- package/dist/harness/adapters/pi.d.ts +8 -0
- package/dist/harness/pi-specialists.d.ts +6 -0
- package/dist/harness/types.d.ts +2 -2
- package/dist/harness/writers/pi-agent.d.ts +16 -0
- package/dist/index.js +6 -4
- package/dist/pi.d.ts +11 -0
- package/dist/pi.js +46 -0
- package/package.json +14 -3
- package/pi/.thoth-agents-assets.json +12 -0
- package/pi/agents/thoth-deep.md +81 -0
- package/pi/agents/thoth-designer.md +81 -0
- package/pi/agents/thoth-explorer.md +81 -0
- package/pi/agents/thoth-librarian.md +83 -0
- package/pi/agents/thoth-oracle.md +83 -0
- package/pi/agents/thoth-quick.md +81 -0
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/skills/thoth-archive/SKILL.md +1 -1
- package/plugin/skills/thoth-constitution/SKILL.md +1 -1
- package/plugin/skills/thoth-init/SKILL.md +1 -1
- package/plugin/skills/thoth-sdd/SKILL.md +1 -1
- package/skills/thoth-archive/SKILL.md +1 -1
- package/skills/thoth-constitution/SKILL.md +1 -1
- package/skills/thoth-init/SKILL.md +1 -1
- package/skills/thoth-sdd/SKILL.md +1 -1
|
@@ -1,20 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONTEXT7_MCP_URL,
|
|
3
|
+
GREP_APP_MCP_URL,
|
|
4
|
+
exa
|
|
5
|
+
} from "./chunk-62L3ORXN.js";
|
|
6
|
+
import {
|
|
7
|
+
PI_MANAGED_OWNER,
|
|
8
|
+
PI_ROOT_END,
|
|
9
|
+
PI_ROOT_START,
|
|
10
|
+
piAdapter,
|
|
11
|
+
renderConfiguredRolePrompt,
|
|
12
|
+
syncPiSpecialists
|
|
13
|
+
} from "./chunk-SECDI3TL.js";
|
|
1
14
|
import {
|
|
2
15
|
ALL_AGENT_NAMES,
|
|
3
16
|
CLAUDE_CODE_PROMPT_DIALECT,
|
|
4
17
|
CLAUDE_CODE_SUBAGENT_NAMESPACE,
|
|
5
18
|
CODEX_PROMPT_DIALECT,
|
|
6
19
|
CONFIRMED_OPENAI_SUBAGENT_PRESET,
|
|
7
|
-
CONTEXT7_MCP_URL,
|
|
8
20
|
DEFAULT_MODELS,
|
|
9
|
-
|
|
10
|
-
appendPromptSections,
|
|
21
|
+
PI_SPECIALIST_ROLES,
|
|
11
22
|
claudeCodeSubagentType,
|
|
12
|
-
composeAgentPrompt,
|
|
13
|
-
createModelFamilySection,
|
|
14
|
-
createRolePromptSections,
|
|
15
|
-
createStepBudgetSection,
|
|
16
23
|
ensureConfigDir,
|
|
17
|
-
exa,
|
|
18
24
|
findPackageRoot,
|
|
19
25
|
generateLiteConfig,
|
|
20
26
|
getAgentOverride,
|
|
@@ -25,17 +31,16 @@ import {
|
|
|
25
31
|
getExistingLiteConfigPath,
|
|
26
32
|
getOpenCodeManagedModelStatePath,
|
|
27
33
|
getPrimaryModelId,
|
|
34
|
+
isPiSpecialistRole,
|
|
28
35
|
isValidPackageVersion,
|
|
29
|
-
loadAgentPrompt,
|
|
30
36
|
parseConfig,
|
|
37
|
+
piSpecialistName,
|
|
31
38
|
renderAgentRoutingDescription,
|
|
32
|
-
renderPromptSection,
|
|
33
|
-
renderRolePrompt,
|
|
34
39
|
resolveExecutingPackageVersion,
|
|
35
40
|
updateOpenCodeMainConfig,
|
|
36
41
|
writeConfig,
|
|
37
42
|
writeLiteConfig
|
|
38
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-Q6SDM4KP.js";
|
|
39
44
|
|
|
40
45
|
// src/cli/codex-paths.ts
|
|
41
46
|
import { homedir } from "os";
|
|
@@ -94,32 +99,6 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
94
99
|
// src/harness/adapters/codex.ts
|
|
95
100
|
import { fileURLToPath } from "url";
|
|
96
101
|
|
|
97
|
-
// src/agents/configured-role-prompt.ts
|
|
98
|
-
function renderConfiguredRolePrompt({
|
|
99
|
-
role,
|
|
100
|
-
dialect,
|
|
101
|
-
config,
|
|
102
|
-
model
|
|
103
|
-
}) {
|
|
104
|
-
const promptOverrides = loadAgentPrompt(role, config?.preset);
|
|
105
|
-
const override = getAgentOverride(config, role);
|
|
106
|
-
const modelSection = createModelFamilySection(role, model);
|
|
107
|
-
const stepSection = createStepBudgetSection(override?.steps);
|
|
108
|
-
const basePrompt = renderRolePrompt(createRolePromptSections(role), dialect);
|
|
109
|
-
const prompt = composeAgentPrompt({
|
|
110
|
-
basePrompt,
|
|
111
|
-
customPrompt: promptOverrides.prompt,
|
|
112
|
-
customAppendPrompt: appendPromptSections(
|
|
113
|
-
modelSection ? renderPromptSection(modelSection, dialect) : void 0,
|
|
114
|
-
promptOverrides.appendPrompt
|
|
115
|
-
)
|
|
116
|
-
});
|
|
117
|
-
return appendPromptSections(
|
|
118
|
-
prompt,
|
|
119
|
-
stepSection ? renderPromptSection(stepSection, dialect) : void 0
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
102
|
// src/harness/core/memory-governance.ts
|
|
124
103
|
function memoryGovernanceDiagnostics(input) {
|
|
125
104
|
const diagnostics = [];
|
|
@@ -953,9 +932,9 @@ function stripProviderMcpValues(surfaceId, values) {
|
|
|
953
932
|
const { thoth_mem: _provider, ...mcpServers } = servers;
|
|
954
933
|
return { ...values, mcp_servers: mcpServers };
|
|
955
934
|
}
|
|
956
|
-
function fieldOrder(fields,
|
|
935
|
+
function fieldOrder(fields, keys2) {
|
|
957
936
|
const orderedRoots = fields.map((field) => field.split(".")[0]);
|
|
958
|
-
return [...
|
|
937
|
+
return [...keys2].sort((left, right) => {
|
|
959
938
|
const leftIndex = orderedRoots.indexOf(left);
|
|
960
939
|
const rightIndex = orderedRoots.indexOf(right);
|
|
961
940
|
if (leftIndex !== -1 || rightIndex !== -1) {
|
|
@@ -2482,11 +2461,12 @@ var INSTALL_LEDGER_SCHEMA_VERSION = 1;
|
|
|
2482
2461
|
var INSTALL_HARNESSES = /* @__PURE__ */ new Set([
|
|
2483
2462
|
"opencode",
|
|
2484
2463
|
"codex",
|
|
2485
|
-
"claude"
|
|
2464
|
+
"claude",
|
|
2465
|
+
"pi"
|
|
2486
2466
|
]);
|
|
2487
|
-
function hasOnlyKeys(value,
|
|
2467
|
+
function hasOnlyKeys(value, keys2) {
|
|
2488
2468
|
const actual = Object.keys(value);
|
|
2489
|
-
return actual.length ===
|
|
2469
|
+
return actual.length === keys2.length && actual.every((key) => keys2.includes(key));
|
|
2490
2470
|
}
|
|
2491
2471
|
function parseInstallLedger(serialized) {
|
|
2492
2472
|
let parsed;
|
|
@@ -2678,8 +2658,6 @@ function getThothMemSetupCommand(harness, dryRun, options = {}) {
|
|
|
2678
2658
|
"thoth-mem@latest",
|
|
2679
2659
|
"setup",
|
|
2680
2660
|
harness,
|
|
2681
|
-
"--scope",
|
|
2682
|
-
"global",
|
|
2683
2661
|
...dryRun ? ["--plan"] : [],
|
|
2684
2662
|
"--json"
|
|
2685
2663
|
],
|
|
@@ -2757,26 +2735,11 @@ function runThothMemSetup(options) {
|
|
|
2757
2735
|
function isConsistentProviderSuccess(result, harness) {
|
|
2758
2736
|
return result.success && result.evidenceValid && result.status === "complete" && result.exitCode === 0 && result.harness === harness;
|
|
2759
2737
|
}
|
|
2760
|
-
function
|
|
2738
|
+
function recordHarnessInstallCompletion(options) {
|
|
2761
2739
|
const ledgerPath = getInstallLedgerPath(options.ledgerOptions);
|
|
2762
|
-
const setup = options.runThothMemSetup ?? runThothMemSetup;
|
|
2763
|
-
const provider = setup({
|
|
2764
|
-
harness: options.harness,
|
|
2765
|
-
dryRun: options.dryRun,
|
|
2766
|
-
cwd: options.cwd
|
|
2767
|
-
});
|
|
2768
|
-
if (!isConsistentProviderSuccess(provider, options.harness)) {
|
|
2769
|
-
return {
|
|
2770
|
-
success: false,
|
|
2771
|
-
provider,
|
|
2772
|
-
ledger: { status: "not-attempted", path: ledgerPath },
|
|
2773
|
-
error: provider.error ?? "thoth-mem setup did not return consistent complete evidence."
|
|
2774
|
-
};
|
|
2775
|
-
}
|
|
2776
2740
|
if (options.dryRun) {
|
|
2777
2741
|
return {
|
|
2778
2742
|
success: true,
|
|
2779
|
-
provider,
|
|
2780
2743
|
ledger: { status: "planned", path: ledgerPath }
|
|
2781
2744
|
};
|
|
2782
2745
|
}
|
|
@@ -2789,7 +2752,6 @@ function finalizeHarnessInstall(options) {
|
|
|
2789
2752
|
if (!ledger.success) {
|
|
2790
2753
|
return {
|
|
2791
2754
|
success: false,
|
|
2792
|
-
provider,
|
|
2793
2755
|
ledger: {
|
|
2794
2756
|
status: "failed",
|
|
2795
2757
|
path: ledger.path,
|
|
@@ -2800,7 +2762,6 @@ function finalizeHarnessInstall(options) {
|
|
|
2800
2762
|
}
|
|
2801
2763
|
return {
|
|
2802
2764
|
success: true,
|
|
2803
|
-
provider,
|
|
2804
2765
|
ledger: {
|
|
2805
2766
|
status: "recorded",
|
|
2806
2767
|
path: ledger.path,
|
|
@@ -2809,6 +2770,34 @@ function finalizeHarnessInstall(options) {
|
|
|
2809
2770
|
}
|
|
2810
2771
|
};
|
|
2811
2772
|
}
|
|
2773
|
+
function finalizeHarnessInstall(options) {
|
|
2774
|
+
const ledgerPath = getInstallLedgerPath(options.ledgerOptions);
|
|
2775
|
+
const setup = options.runThothMemSetup ?? runThothMemSetup;
|
|
2776
|
+
const provider = setup({
|
|
2777
|
+
harness: options.harness,
|
|
2778
|
+
dryRun: options.dryRun,
|
|
2779
|
+
cwd: options.cwd
|
|
2780
|
+
});
|
|
2781
|
+
if (!isConsistentProviderSuccess(provider, options.harness)) {
|
|
2782
|
+
return {
|
|
2783
|
+
success: false,
|
|
2784
|
+
provider,
|
|
2785
|
+
ledger: { status: "not-attempted", path: ledgerPath },
|
|
2786
|
+
error: provider.error ?? "thoth-mem setup did not return consistent complete evidence."
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2789
|
+
const completion = recordHarnessInstallCompletion({
|
|
2790
|
+
harness: options.harness,
|
|
2791
|
+
version: options.version,
|
|
2792
|
+
dryRun: options.dryRun,
|
|
2793
|
+
recordCompletedInstall: options.recordCompletedInstall,
|
|
2794
|
+
ledgerOptions: options.ledgerOptions
|
|
2795
|
+
});
|
|
2796
|
+
return {
|
|
2797
|
+
provider,
|
|
2798
|
+
...completion
|
|
2799
|
+
};
|
|
2800
|
+
}
|
|
2812
2801
|
|
|
2813
2802
|
// src/cli/skills.ts
|
|
2814
2803
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
@@ -2818,7 +2807,8 @@ import { join as join3 } from "path";
|
|
|
2818
2807
|
var SKILLS_CLI_AGENT = {
|
|
2819
2808
|
opencode: "opencode",
|
|
2820
2809
|
codex: "codex",
|
|
2821
|
-
claude: "claude-code"
|
|
2810
|
+
claude: "claude-code",
|
|
2811
|
+
pi: "pi"
|
|
2822
2812
|
};
|
|
2823
2813
|
var GLOBAL_SKILL_ROOTS = {
|
|
2824
2814
|
opencode: [
|
|
@@ -2826,7 +2816,8 @@ var GLOBAL_SKILL_ROOTS = {
|
|
|
2826
2816
|
[".agents", "skills"]
|
|
2827
2817
|
],
|
|
2828
2818
|
codex: [[".agents", "skills"]],
|
|
2829
|
-
claude: [[".claude", "skills"]]
|
|
2819
|
+
claude: [[".claude", "skills"]],
|
|
2820
|
+
pi: [[".pi", "agent", "skills"]]
|
|
2830
2821
|
};
|
|
2831
2822
|
var REQUIRED_SKILLS = [
|
|
2832
2823
|
{
|
|
@@ -2883,7 +2874,8 @@ function getRequiredSkillInstallCommand(skill, harness, options = {}) {
|
|
|
2883
2874
|
"--global",
|
|
2884
2875
|
"--agent",
|
|
2885
2876
|
SKILLS_CLI_AGENT[harness],
|
|
2886
|
-
"--yes"
|
|
2877
|
+
"--yes",
|
|
2878
|
+
...harness === "pi" ? ["--copy"] : []
|
|
2887
2879
|
],
|
|
2888
2880
|
options
|
|
2889
2881
|
);
|
|
@@ -5124,8 +5116,10 @@ function resolveOpenCodeEffort(input) {
|
|
|
5124
5116
|
import {
|
|
5125
5117
|
cpSync,
|
|
5126
5118
|
existsSync as existsSync9,
|
|
5119
|
+
lstatSync as lstatSync2,
|
|
5127
5120
|
mkdirSync as mkdirSync4,
|
|
5128
5121
|
mkdtempSync,
|
|
5122
|
+
readFileSync as readFileSync8,
|
|
5129
5123
|
renameSync as renameSync3,
|
|
5130
5124
|
rmSync as rmSync3,
|
|
5131
5125
|
statSync
|
|
@@ -5144,6 +5138,68 @@ var THOTH_OWNED_SKILL_NAMES = [
|
|
|
5144
5138
|
];
|
|
5145
5139
|
|
|
5146
5140
|
// src/cli/owned-skills.ts
|
|
5141
|
+
function parseSkillFrontmatter(content) {
|
|
5142
|
+
const lines = content.split(/\r?\n/);
|
|
5143
|
+
if (lines[0] !== "---") return void 0;
|
|
5144
|
+
const closingDelimiter = lines.findIndex(
|
|
5145
|
+
(line, index) => index > 0 && line === "---"
|
|
5146
|
+
);
|
|
5147
|
+
if (closingDelimiter < 0) return void 0;
|
|
5148
|
+
const values = /* @__PURE__ */ new Map();
|
|
5149
|
+
for (const line of lines.slice(1, closingDelimiter)) {
|
|
5150
|
+
const field = /^(name|description):\s*(.*)$/.exec(line);
|
|
5151
|
+
if (!field) continue;
|
|
5152
|
+
const [, key, rawValue] = field;
|
|
5153
|
+
if (!key || rawValue === void 0 || values.has(key)) return void 0;
|
|
5154
|
+
const value = rawValue.trim();
|
|
5155
|
+
values.set(
|
|
5156
|
+
key,
|
|
5157
|
+
value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'") ? value.slice(1, -1) : value
|
|
5158
|
+
);
|
|
5159
|
+
}
|
|
5160
|
+
const name = values.get("name");
|
|
5161
|
+
const description = values.get("description");
|
|
5162
|
+
if (!name || !description || description.startsWith("#")) return void 0;
|
|
5163
|
+
return { name, description };
|
|
5164
|
+
}
|
|
5165
|
+
function inspectPiPackageSkill(entry) {
|
|
5166
|
+
if (!existsSync9(entry.sourcePath))
|
|
5167
|
+
return {
|
|
5168
|
+
name: entry.name,
|
|
5169
|
+
state: "missing",
|
|
5170
|
+
message: `Package-declared Pi skill directory is missing: ${entry.name}`
|
|
5171
|
+
};
|
|
5172
|
+
const root = lstatSync2(entry.sourcePath);
|
|
5173
|
+
if (!root.isDirectory() || root.isSymbolicLink())
|
|
5174
|
+
return {
|
|
5175
|
+
name: entry.name,
|
|
5176
|
+
state: "drift",
|
|
5177
|
+
message: `Package-declared Pi skill directory is not a regular directory: ${entry.name}`
|
|
5178
|
+
};
|
|
5179
|
+
const contractPath = join5(entry.sourcePath, "SKILL.md");
|
|
5180
|
+
if (!existsSync9(contractPath))
|
|
5181
|
+
return {
|
|
5182
|
+
name: entry.name,
|
|
5183
|
+
state: "missing",
|
|
5184
|
+
message: `Package-declared Pi skill contract is missing: ${entry.name}`
|
|
5185
|
+
};
|
|
5186
|
+
const contract = lstatSync2(contractPath);
|
|
5187
|
+
if (!contract.isFile() || contract.isSymbolicLink())
|
|
5188
|
+
return {
|
|
5189
|
+
name: entry.name,
|
|
5190
|
+
state: "drift",
|
|
5191
|
+
message: `Package-declared Pi skill contract is not a regular file: ${entry.name}`
|
|
5192
|
+
};
|
|
5193
|
+
const content = readFileSync8(contractPath, "utf8");
|
|
5194
|
+
const frontmatter = parseSkillFrontmatter(content);
|
|
5195
|
+
if (!frontmatter || frontmatter.name !== entry.name)
|
|
5196
|
+
return {
|
|
5197
|
+
name: entry.name,
|
|
5198
|
+
state: "drift",
|
|
5199
|
+
message: `Package-declared Pi skill contract is malformed: ${entry.name}`
|
|
5200
|
+
};
|
|
5201
|
+
return void 0;
|
|
5202
|
+
}
|
|
5147
5203
|
function resolveOwnedSkillPackageRoot(moduleUrl) {
|
|
5148
5204
|
const packageRoot = findPackageRoot(dirname5(fileURLToPath4(moduleUrl)));
|
|
5149
5205
|
if (!packageRoot) {
|
|
@@ -5165,6 +5221,14 @@ function getOpenCodeOwnedSkillEntries(options = {}) {
|
|
|
5165
5221
|
destinationPath: join5(homeDir, ".config", "opencode", "skills", name)
|
|
5166
5222
|
}));
|
|
5167
5223
|
}
|
|
5224
|
+
function getPiOwnedSkillEntries(options) {
|
|
5225
|
+
const packageRoot = options.packageRoot;
|
|
5226
|
+
return THOTH_OWNED_SKILL_NAMES.map((name) => ({
|
|
5227
|
+
name,
|
|
5228
|
+
sourcePath: join5(packageRoot, "skills", name),
|
|
5229
|
+
destinationPath: join5(packageRoot, "skills", name)
|
|
5230
|
+
}));
|
|
5231
|
+
}
|
|
5168
5232
|
function validateCanonicalSkill(entry) {
|
|
5169
5233
|
if (!existsSync9(entry.sourcePath) || !statSync(entry.sourcePath).isDirectory()) {
|
|
5170
5234
|
throw new Error(
|
|
@@ -5235,6 +5299,37 @@ function syncOpenCodeOwnedSkills(options = {}) {
|
|
|
5235
5299
|
};
|
|
5236
5300
|
}
|
|
5237
5301
|
}
|
|
5302
|
+
function inspectPiPackageSkills(options) {
|
|
5303
|
+
const skills = getPiOwnedSkillEntries(options);
|
|
5304
|
+
try {
|
|
5305
|
+
const issues = skills.flatMap((skill) => {
|
|
5306
|
+
const issue = inspectPiPackageSkill(skill);
|
|
5307
|
+
return issue ? [issue] : [];
|
|
5308
|
+
});
|
|
5309
|
+
if (issues.length > 0)
|
|
5310
|
+
return {
|
|
5311
|
+
success: false,
|
|
5312
|
+
state: "unavailable",
|
|
5313
|
+
skills,
|
|
5314
|
+
issues,
|
|
5315
|
+
error: issues.map(({ message }) => message).join("; ")
|
|
5316
|
+
};
|
|
5317
|
+
return {
|
|
5318
|
+
success: true,
|
|
5319
|
+
state: "available",
|
|
5320
|
+
skills,
|
|
5321
|
+
issues: []
|
|
5322
|
+
};
|
|
5323
|
+
} catch (error) {
|
|
5324
|
+
return {
|
|
5325
|
+
success: false,
|
|
5326
|
+
state: "unavailable",
|
|
5327
|
+
skills,
|
|
5328
|
+
issues: [],
|
|
5329
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5330
|
+
};
|
|
5331
|
+
}
|
|
5332
|
+
}
|
|
5238
5333
|
|
|
5239
5334
|
// src/cli/operations/opencode.ts
|
|
5240
5335
|
var PACKAGE_NAME = "thoth-agents";
|
|
@@ -5286,7 +5381,7 @@ function canonicalData(value, active) {
|
|
|
5286
5381
|
if (ownKeys.some((key) => typeof key === "symbol")) {
|
|
5287
5382
|
throw new NonCanonicalDataError("Symbol keys are not canonical.");
|
|
5288
5383
|
}
|
|
5289
|
-
const
|
|
5384
|
+
const keys2 = ownKeys;
|
|
5290
5385
|
active.add(value);
|
|
5291
5386
|
try {
|
|
5292
5387
|
if (array) {
|
|
@@ -5298,7 +5393,7 @@ function canonicalData(value, active) {
|
|
|
5298
5393
|
if (!Number.isSafeInteger(length) || length < 0) {
|
|
5299
5394
|
throw new NonCanonicalDataError("Array length is invalid.");
|
|
5300
5395
|
}
|
|
5301
|
-
const indexKeys =
|
|
5396
|
+
const indexKeys = keys2.filter((key) => key !== "length");
|
|
5302
5397
|
if (indexKeys.length !== length || indexKeys.some(
|
|
5303
5398
|
(key) => !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= length || String(Number(key)) !== key
|
|
5304
5399
|
)) {
|
|
@@ -5320,7 +5415,7 @@ function canonicalData(value, active) {
|
|
|
5320
5415
|
return `[${parts2.join(",")}]`;
|
|
5321
5416
|
}
|
|
5322
5417
|
const parts = [];
|
|
5323
|
-
for (const key of
|
|
5418
|
+
for (const key of keys2.sort()) {
|
|
5324
5419
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
5325
5420
|
if (!descriptor || !("value" in descriptor)) {
|
|
5326
5421
|
throw new NonCanonicalDataError("Accessors are not canonical.");
|
|
@@ -6859,143 +6954,2121 @@ function applyOpenCodePlan(plan) {
|
|
|
6859
6954
|
};
|
|
6860
6955
|
}
|
|
6861
6956
|
|
|
6862
|
-
// src/cli/operations/
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6957
|
+
// src/cli/operations/pi.ts
|
|
6958
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
6959
|
+
import { createHash as createHash5 } from "crypto";
|
|
6960
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
|
|
6961
|
+
import { basename as basename3, isAbsolute as isAbsolute4, join as join12 } from "path";
|
|
6962
|
+
|
|
6963
|
+
// src/cli/pi-effort.ts
|
|
6964
|
+
var PI_EFFORT_VALUES = [
|
|
6965
|
+
"off",
|
|
6966
|
+
"minimal",
|
|
6967
|
+
"low",
|
|
6968
|
+
"medium",
|
|
6969
|
+
"high",
|
|
6970
|
+
"xhigh"
|
|
6971
|
+
];
|
|
6972
|
+
var PI_EFFORTS = new Set(PI_EFFORT_VALUES);
|
|
6973
|
+
function resolvePiEffort(input) {
|
|
6974
|
+
if (!input.effort || input.effort.kind === "inherit") {
|
|
6975
|
+
return { ok: true, effort: void 0 };
|
|
6976
|
+
}
|
|
6977
|
+
const effort = input.effort.value;
|
|
6978
|
+
if (!PI_EFFORTS.has(effort)) {
|
|
6979
|
+
return {
|
|
6980
|
+
ok: false,
|
|
6981
|
+
message: `Pi does not support effort ${effort}; expected ${PI_EFFORT_VALUES.join(", ")}, or inherit.`
|
|
6982
|
+
};
|
|
6983
|
+
}
|
|
6984
|
+
if (input.availableEfforts !== void 0 && !input.availableEfforts.includes(effort)) {
|
|
6985
|
+
return {
|
|
6986
|
+
ok: false,
|
|
6987
|
+
message: `Effort ${effort} is not available for ${input.model} in the selected model catalog entry.`
|
|
6988
|
+
};
|
|
6989
|
+
}
|
|
6990
|
+
return { ok: true, effort };
|
|
6878
6991
|
}
|
|
6879
6992
|
|
|
6880
|
-
// src/cli/
|
|
6881
|
-
import {
|
|
6993
|
+
// src/cli/pi-install.ts
|
|
6994
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
6995
|
+
import { createHash as createHash4 } from "crypto";
|
|
6996
|
+
import {
|
|
6997
|
+
copyFileSync as copyFileSync5,
|
|
6998
|
+
existsSync as existsSync14,
|
|
6999
|
+
lstatSync as lstatSync3,
|
|
7000
|
+
mkdirSync as mkdirSync7,
|
|
7001
|
+
readdirSync as readdirSync3,
|
|
7002
|
+
readFileSync as readFileSync11,
|
|
7003
|
+
renameSync as renameSync6,
|
|
7004
|
+
rmSync as rmSync6,
|
|
7005
|
+
writeFileSync as writeFileSync7
|
|
7006
|
+
} from "fs";
|
|
7007
|
+
import { dirname as dirname9, isAbsolute as isAbsolute3, join as join11 } from "path";
|
|
7008
|
+
import { fileURLToPath as fileURLToPath5, pathToFileURL } from "url";
|
|
6882
7009
|
|
|
6883
|
-
// src/cli/
|
|
7010
|
+
// src/cli/pi-migration.ts
|
|
6884
7011
|
import {
|
|
7012
|
+
copyFileSync as copyFileSync4,
|
|
6885
7013
|
existsSync as existsSync11,
|
|
6886
7014
|
mkdirSync as mkdirSync5,
|
|
6887
|
-
|
|
7015
|
+
readdirSync as readdirSync2,
|
|
7016
|
+
readFileSync as readFileSync9,
|
|
6888
7017
|
renameSync as renameSync4,
|
|
6889
|
-
|
|
7018
|
+
statSync as statSync2,
|
|
6890
7019
|
writeFileSync as writeFileSync4
|
|
6891
7020
|
} from "fs";
|
|
6892
|
-
import {
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
etag: z2.string().optional(),
|
|
6907
|
-
fetchedAt: z2.string().datetime(),
|
|
6908
|
-
catalog: z2.object({ models: z2.array(cachedModelOptionSchema) }).passthrough()
|
|
6909
|
-
}).passthrough();
|
|
6910
|
-
function resolveModelsDevCachePath(options = {}) {
|
|
6911
|
-
const platform = options.platform ?? process.platform;
|
|
6912
|
-
const env = options.env ?? process.env;
|
|
6913
|
-
const home = options.homeDir ?? homedir5();
|
|
6914
|
-
if (platform === "win32") {
|
|
6915
|
-
const path = win32;
|
|
6916
|
-
const base2 = env.LOCALAPPDATA ?? env.APPDATA ?? path.join(home, "AppData", "Local");
|
|
6917
|
-
return path.join(base2, "thoth-agents", "models-dev-v1.json");
|
|
6918
|
-
}
|
|
6919
|
-
const base = env.XDG_CACHE_HOME ?? posix.join(home, ".cache");
|
|
6920
|
-
return posix.join(base, "thoth-agents", "models-dev-v1.json");
|
|
7021
|
+
import { dirname as dirname6, join as join7 } from "path";
|
|
7022
|
+
function files(root, current = root) {
|
|
7023
|
+
if (!existsSync11(root)) return [];
|
|
7024
|
+
return readdirSync2(current, { withFileTypes: true }).flatMap((entry) => {
|
|
7025
|
+
const path = join7(current, entry.name);
|
|
7026
|
+
return entry.isDirectory() ? files(root, path) : [path.slice(root.length + 1).replaceAll("\\", "/")];
|
|
7027
|
+
}).sort();
|
|
7028
|
+
}
|
|
7029
|
+
function identicalTrees(left, right) {
|
|
7030
|
+
const leftFiles = files(left);
|
|
7031
|
+
const rightFiles = files(right);
|
|
7032
|
+
return leftFiles.length === rightFiles.length && leftFiles.every(
|
|
7033
|
+
(name, index) => name === rightFiles[index] && readFileSync9(join7(left, name)).equals(readFileSync9(join7(right, name)))
|
|
7034
|
+
);
|
|
6921
7035
|
}
|
|
6922
|
-
function
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
}
|
|
7036
|
+
function removeRootBlock(content) {
|
|
7037
|
+
const start = content.indexOf(PI_ROOT_START);
|
|
7038
|
+
const end = content.indexOf(PI_ROOT_END, start + PI_ROOT_START.length);
|
|
7039
|
+
if (start < 0 && end < 0) return void 0;
|
|
7040
|
+
if (start < 0 || end < 0 || content.indexOf(PI_ROOT_START, start + 1) >= 0 || content.indexOf(PI_ROOT_END, end + 1) >= 0)
|
|
7041
|
+
throw new Error("Legacy Pi root block ownership is ambiguous.");
|
|
7042
|
+
const after = end + PI_ROOT_END.length;
|
|
7043
|
+
return `${content.slice(0, start)}${content.slice(after).replace(/^\r?\n/, "")}`;
|
|
6931
7044
|
}
|
|
6932
|
-
function
|
|
6933
|
-
const
|
|
6934
|
-
|
|
6935
|
-
|
|
7045
|
+
function migrateLegacyPiResources(options) {
|
|
7046
|
+
const changed = [];
|
|
7047
|
+
const manualActions = [];
|
|
7048
|
+
const appendPath = join7(options.piRoot, "APPEND_SYSTEM.md");
|
|
7049
|
+
let nextRoot;
|
|
7050
|
+
try {
|
|
7051
|
+
if (existsSync11(appendPath))
|
|
7052
|
+
nextRoot = removeRootBlock(readFileSync9(appendPath, "utf8"));
|
|
7053
|
+
} catch (error) {
|
|
7054
|
+
manualActions.push(error instanceof Error ? error.message : String(error));
|
|
7055
|
+
}
|
|
7056
|
+
const retire = [];
|
|
7057
|
+
for (const name of THOTH_OWNED_SKILL_NAMES) {
|
|
7058
|
+
const source = join7(options.piRoot, "skills", name);
|
|
7059
|
+
if (!existsSync11(source)) continue;
|
|
7060
|
+
const canonical = join7(options.packageRoot, "skills", name);
|
|
7061
|
+
const backup = `${source}.thoth-agents-legacy.bak`;
|
|
7062
|
+
if (!statSync2(source).isDirectory() || !identicalTrees(source, canonical) || existsSync11(backup)) {
|
|
7063
|
+
manualActions.push(
|
|
7064
|
+
`Legacy Pi skill ${name} is modified or has ambiguous backup ownership; remove it manually after review.`
|
|
7065
|
+
);
|
|
7066
|
+
continue;
|
|
7067
|
+
}
|
|
7068
|
+
retire.push({ source, backup });
|
|
6936
7069
|
}
|
|
6937
|
-
|
|
6938
|
-
|
|
7070
|
+
if (options.dryRun)
|
|
7071
|
+
return {
|
|
7072
|
+
success: true,
|
|
7073
|
+
changed: [
|
|
7074
|
+
...nextRoot === void 0 ? [] : [appendPath],
|
|
7075
|
+
...retire.map(({ source }) => source)
|
|
7076
|
+
],
|
|
7077
|
+
manualActions
|
|
7078
|
+
};
|
|
6939
7079
|
try {
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
`${
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
7080
|
+
if (nextRoot !== void 0) {
|
|
7081
|
+
mkdirSync5(dirname6(appendPath), { recursive: true });
|
|
7082
|
+
copyFileSync4(appendPath, `${appendPath}.thoth-agents-legacy.bak`);
|
|
7083
|
+
const temporary = `${appendPath}.tmp-${process.pid}`;
|
|
7084
|
+
writeFileSync4(temporary, nextRoot);
|
|
7085
|
+
renameSync4(temporary, appendPath);
|
|
7086
|
+
changed.push(appendPath);
|
|
7087
|
+
}
|
|
7088
|
+
for (const item of retire) {
|
|
7089
|
+
renameSync4(item.source, item.backup);
|
|
7090
|
+
changed.push(item.source);
|
|
7091
|
+
}
|
|
7092
|
+
return { success: true, changed, manualActions };
|
|
6946
7093
|
} catch (error) {
|
|
6947
|
-
|
|
6948
|
-
|
|
7094
|
+
try {
|
|
7095
|
+
for (const item of retire)
|
|
7096
|
+
if (existsSync11(item.backup) && !existsSync11(item.source))
|
|
7097
|
+
renameSync4(item.backup, item.source);
|
|
7098
|
+
if (existsSync11(`${appendPath}.thoth-agents-legacy.bak`))
|
|
7099
|
+
copyFileSync4(`${appendPath}.thoth-agents-legacy.bak`, appendPath);
|
|
7100
|
+
} catch {
|
|
7101
|
+
}
|
|
7102
|
+
return {
|
|
7103
|
+
success: false,
|
|
7104
|
+
changed,
|
|
7105
|
+
manualActions,
|
|
7106
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7107
|
+
};
|
|
6949
7108
|
}
|
|
6950
7109
|
}
|
|
6951
7110
|
|
|
6952
|
-
// src/cli/
|
|
6953
|
-
import {
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
}
|
|
6975
|
-
function runtimeModelId(provider, catalogId) {
|
|
6976
|
-
const prefix = `${provider}/`;
|
|
6977
|
-
return catalogId.startsWith(prefix) ? catalogId.slice(prefix.length) : catalogId;
|
|
6978
|
-
}
|
|
6979
|
-
function effortValues(options) {
|
|
6980
|
-
const values = /* @__PURE__ */ new Set();
|
|
6981
|
-
for (const option of options ?? []) {
|
|
6982
|
-
const parsed = effortOptionSchema.safeParse(option);
|
|
6983
|
-
if (!parsed.success) continue;
|
|
6984
|
-
for (const value of parsed.data.values) {
|
|
6985
|
-
const normalized = value.trim();
|
|
6986
|
-
if (normalized) values.add(normalized);
|
|
7111
|
+
// src/cli/pi-native-probe.ts
|
|
7112
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
7113
|
+
import { existsSync as existsSync12, mkdtempSync as mkdtempSync2, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
7114
|
+
import { tmpdir } from "os";
|
|
7115
|
+
import { dirname as dirname7, join as join8, resolve as resolve3 } from "path";
|
|
7116
|
+
function defaultExecutor(command, args, env) {
|
|
7117
|
+
let executable = command;
|
|
7118
|
+
let executableArgs = [...args];
|
|
7119
|
+
if (process.platform === "win32" && command === "pi") {
|
|
7120
|
+
const located = spawnSync5("where.exe", ["pi.cmd"], { encoding: "utf8" }).stdout?.split(/\r?\n/).find(Boolean);
|
|
7121
|
+
const cli = located ? join8(
|
|
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)) {
|
|
7131
|
+
executable = process.execPath;
|
|
7132
|
+
executableArgs = [cli, ...args];
|
|
6987
7133
|
}
|
|
6988
7134
|
}
|
|
6989
|
-
|
|
7135
|
+
const result = spawnSync5(executable, executableArgs, {
|
|
7136
|
+
encoding: "utf8",
|
|
7137
|
+
timeout: 3e4,
|
|
7138
|
+
env: { ...process.env, ...env }
|
|
7139
|
+
});
|
|
7140
|
+
return {
|
|
7141
|
+
exitCode: result.status,
|
|
7142
|
+
stdout: result.stdout ?? "",
|
|
7143
|
+
stderr: result.stderr ?? "",
|
|
7144
|
+
error: result.error
|
|
7145
|
+
};
|
|
6990
7146
|
}
|
|
6991
|
-
function
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
7147
|
+
function occurrences(text, marker) {
|
|
7148
|
+
return text.split(marker).length - 1;
|
|
7149
|
+
}
|
|
7150
|
+
function observePiNativeRoot(options) {
|
|
7151
|
+
const ownsIsolatedRoot = options.piHome === void 0;
|
|
7152
|
+
const isolatedRoot = options.piHome ?? mkdtempSync2(join8(tmpdir(), "thoth-pi-observer-"));
|
|
7153
|
+
const observerPath = join8(
|
|
7154
|
+
isolatedRoot,
|
|
7155
|
+
`observer-${process.pid}-${Date.now()}.mjs`
|
|
7156
|
+
);
|
|
7157
|
+
try {
|
|
7158
|
+
writeFileSync5(
|
|
7159
|
+
observerPath,
|
|
7160
|
+
`import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
7161
|
+
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
7162
|
+
import { createAssistantMessageEventStream } from '@earendil-works/pi-ai';
|
|
7163
|
+
const manifestSha256=${JSON.stringify(options.manifestSha256)};
|
|
7164
|
+
const extensionSha256=${JSON.stringify(options.extensionSha256)};
|
|
7165
|
+
const packageRoot=${JSON.stringify(options.packageRoot)};
|
|
7166
|
+
function discoveredSkills(systemPrompt) {
|
|
7167
|
+
const block=systemPrompt.match(/<available_skills>([\\s\\S]*?)<\\/available_skills>/)?.[1] ?? '';
|
|
7168
|
+
const entries=[...block.matchAll(/<skill>[\\s\\S]*?<name>([^<]+)<\\/name>[\\s\\S]*?<location>([^<]+)<\\/location>[\\s\\S]*?<\\/skill>/g)]
|
|
7169
|
+
.map((match)=>({name:match[1],location:match[2]}));
|
|
7170
|
+
if (!packageRoot) return entries;
|
|
7171
|
+
const skillRoot=resolve(packageRoot,'skills');
|
|
7172
|
+
return entries.filter(({location}) => {
|
|
7173
|
+
const child=relative(skillRoot,resolve(location));
|
|
7174
|
+
return child !== '' && !child.startsWith('..') && !isAbsolute(child);
|
|
7175
|
+
});
|
|
7176
|
+
}
|
|
7177
|
+
function specialistEvidence() {
|
|
7178
|
+
const root=join(process.env.PI_CODING_AGENT_DIR ?? '', 'agents');
|
|
7179
|
+
if (!existsSync(root)) return {materializedSpecialists:[],orchestratorChild:false};
|
|
7180
|
+
const managed=readdirSync(root)
|
|
7181
|
+
.filter((name)=>name.endsWith('.md'))
|
|
7182
|
+
.filter((name)=>/^managed-by:\\s*thoth-agents\\s*$/m.test(readFileSync(join(root,name),'utf8')))
|
|
7183
|
+
.map((name)=>name.slice(0,-3))
|
|
7184
|
+
.sort();
|
|
7185
|
+
return {materializedSpecialists:managed.filter((name)=>name!=='orchestrator'),orchestratorChild:managed.includes('orchestrator')};
|
|
7186
|
+
}
|
|
7187
|
+
export default function observer(pi) {
|
|
7188
|
+
let hookCount=0;
|
|
7189
|
+
let sessionStartCount=0;
|
|
7190
|
+
let sessionEvidence={materializedSpecialists:[],orchestratorChild:false};
|
|
7191
|
+
let skills=[];
|
|
7192
|
+
pi.on('session_start', () => {
|
|
7193
|
+
sessionStartCount += 1;
|
|
7194
|
+
sessionEvidence=specialistEvidence();
|
|
7195
|
+
});
|
|
7196
|
+
pi.on('before_provider_request', (event) => {
|
|
7197
|
+
hookCount += 1;
|
|
7198
|
+
process.stdout.write(JSON.stringify({systemPrompt:JSON.stringify(event.payload),manifestSha256,extensionSha256,hookCount,sessionStartCount,discoveredSkills:skills,...sessionEvidence})+'\\n');
|
|
7199
|
+
});
|
|
7200
|
+
pi.registerProvider('thoth-observer', {
|
|
7201
|
+
name:'Thoth Observer', baseUrl:'local://thoth-observer', apiKey:'credential-free', api:'thoth-observer',
|
|
7202
|
+
models:[{id:'probe',name:'Probe',api:'thoth-observer',reasoning:false,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:8192,maxTokens:16}],
|
|
7203
|
+
streamSimple(model, context, streamOptions) {
|
|
7204
|
+
const stream=createAssistantMessageEventStream();
|
|
7205
|
+
queueMicrotask(async () => {
|
|
7206
|
+
try {
|
|
7207
|
+
skills=discoveredSkills(context.systemPrompt);
|
|
7208
|
+
const raw={systemPrompt:context.systemPrompt,messages:context.messages};
|
|
7209
|
+
if (streamOptions?.onPayload) await streamOptions.onPayload(raw);
|
|
7210
|
+
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:'done',message});
|
|
7212
|
+
} catch (error) { stream.push({type:'error',error}); }
|
|
7213
|
+
});
|
|
7214
|
+
return stream;
|
|
7215
|
+
}
|
|
7216
|
+
});
|
|
7217
|
+
}
|
|
7218
|
+
`
|
|
7219
|
+
);
|
|
7220
|
+
const args = [
|
|
7221
|
+
"--mode",
|
|
7222
|
+
"json",
|
|
7223
|
+
"--no-session",
|
|
7224
|
+
"--no-approve",
|
|
7225
|
+
"--offline",
|
|
7226
|
+
"--no-extensions",
|
|
7227
|
+
"--extension",
|
|
7228
|
+
resolve3(options.extensionPath),
|
|
7229
|
+
"--extension",
|
|
7230
|
+
observerPath,
|
|
7231
|
+
"--provider",
|
|
7232
|
+
"thoth-observer",
|
|
7233
|
+
"--model",
|
|
7234
|
+
"thoth-observer/probe",
|
|
7235
|
+
"--print",
|
|
7236
|
+
options.probePrompt ?? "Return the single word probe."
|
|
7237
|
+
];
|
|
7238
|
+
const command = { command: options.piCommand ?? "pi", args };
|
|
7239
|
+
const result = (options.commandExecutor ?? defaultExecutor)(
|
|
7240
|
+
command.command,
|
|
7241
|
+
args,
|
|
7242
|
+
{ PI_CODING_AGENT_DIR: isolatedRoot }
|
|
7243
|
+
);
|
|
7244
|
+
if (result.exitCode === null || result.error)
|
|
7245
|
+
return {
|
|
7246
|
+
state: "unavailable",
|
|
7247
|
+
basis: [
|
|
7248
|
+
result.stderr || String(result.error ?? "Pi executable unavailable")
|
|
7249
|
+
],
|
|
7250
|
+
command
|
|
7251
|
+
};
|
|
7252
|
+
if (result.exitCode !== 0)
|
|
7253
|
+
return {
|
|
7254
|
+
state: "unobserved",
|
|
7255
|
+
basis: [`Pi probe exited ${result.exitCode}: ${result.stderr.trim()}`],
|
|
7256
|
+
command
|
|
7257
|
+
};
|
|
7258
|
+
const records = `${result.stdout}
|
|
7259
|
+
${result.stderr}`.split(/\r?\n/).flatMap((line) => {
|
|
7260
|
+
try {
|
|
7261
|
+
const value = JSON.parse(line);
|
|
7262
|
+
return typeof value.systemPrompt === "string" ? [value] : [];
|
|
7263
|
+
} catch {
|
|
7264
|
+
return [];
|
|
7265
|
+
}
|
|
7266
|
+
});
|
|
7267
|
+
const record = records.at(-1);
|
|
7268
|
+
const prompt = typeof record?.systemPrompt === "string" ? record.systemPrompt : "";
|
|
7269
|
+
const discoveredSkills = Array.isArray(record?.discoveredSkills) ? record.discoveredSkills.flatMap((entry) => {
|
|
7270
|
+
if (!entry || typeof entry !== "object" || typeof entry.name !== "string" || typeof entry.location !== "string")
|
|
7271
|
+
return [];
|
|
7272
|
+
return [{ name: entry.name, location: entry.location }];
|
|
7273
|
+
}) : void 0;
|
|
7274
|
+
const materializedSpecialists = Array.isArray(
|
|
7275
|
+
record?.materializedSpecialists
|
|
7276
|
+
) ? record.materializedSpecialists.filter(
|
|
7277
|
+
(name) => typeof name === "string"
|
|
7278
|
+
) : void 0;
|
|
7279
|
+
const orchestratorChild = typeof record?.orchestratorChild === "boolean" ? record.orchestratorChild : void 0;
|
|
7280
|
+
const sessionStartCount = typeof record?.sessionStartCount === "number" ? record.sessionStartCount : void 0;
|
|
7281
|
+
const valid = record?.manifestSha256 === options.manifestSha256 && record?.extensionSha256 === options.extensionSha256 && record?.hookCount === 1 && occurrences(prompt, PI_ROOT_START) === 1 && occurrences(prompt, PI_ROOT_END) === 1 && prompt.indexOf(PI_ROOT_START) < prompt.indexOf(PI_ROOT_END);
|
|
7282
|
+
return valid ? {
|
|
7283
|
+
state: "observed-at-install",
|
|
7284
|
+
basis: [
|
|
7285
|
+
"Real Pi final provider request contained exactly one receipt-bound root marker."
|
|
7286
|
+
],
|
|
7287
|
+
command,
|
|
7288
|
+
...discoveredSkills ? { discoveredSkills } : {},
|
|
7289
|
+
...materializedSpecialists ? { materializedSpecialists } : {},
|
|
7290
|
+
...orchestratorChild !== void 0 ? { orchestratorChild } : {},
|
|
7291
|
+
...sessionStartCount !== void 0 ? { sessionStartCount } : {}
|
|
7292
|
+
} : {
|
|
7293
|
+
state: "unobserved",
|
|
7294
|
+
basis: [
|
|
7295
|
+
"Pi probe did not return one complete digest-bound native root marker."
|
|
7296
|
+
],
|
|
7297
|
+
command
|
|
7298
|
+
};
|
|
7299
|
+
} finally {
|
|
7300
|
+
if (ownsIsolatedRoot)
|
|
7301
|
+
rmSync4(isolatedRoot, { recursive: true, force: true });
|
|
7302
|
+
else rmSync4(observerPath, { force: true });
|
|
7303
|
+
}
|
|
7304
|
+
}
|
|
7305
|
+
|
|
7306
|
+
// src/cli/pi-package-receipt.ts
|
|
7307
|
+
import {
|
|
7308
|
+
existsSync as existsSync13,
|
|
7309
|
+
mkdirSync as mkdirSync6,
|
|
7310
|
+
readFileSync as readFileSync10,
|
|
7311
|
+
renameSync as renameSync5,
|
|
7312
|
+
rmSync as rmSync5,
|
|
7313
|
+
writeFileSync as writeFileSync6
|
|
7314
|
+
} from "fs";
|
|
7315
|
+
import { homedir as homedir5 } from "os";
|
|
7316
|
+
import { dirname as dirname8, isAbsolute as isAbsolute2, join as join9, normalize, resolve as resolve4 } from "path";
|
|
7317
|
+
var keys = [
|
|
7318
|
+
"schemaVersion",
|
|
7319
|
+
"owner",
|
|
7320
|
+
"scope",
|
|
7321
|
+
"packageName",
|
|
7322
|
+
"source",
|
|
7323
|
+
"installSource",
|
|
7324
|
+
"version",
|
|
7325
|
+
"manifestSha256",
|
|
7326
|
+
"extensionSha256"
|
|
7327
|
+
];
|
|
7328
|
+
var semver = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
7329
|
+
var digest = /^[0-9a-f]{64}$/;
|
|
7330
|
+
function piPackagePathsEqual(left, right) {
|
|
7331
|
+
const normalizedLeft = normalize(resolve4(left));
|
|
7332
|
+
const normalizedRight = normalize(resolve4(right));
|
|
7333
|
+
return process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight;
|
|
7334
|
+
}
|
|
7335
|
+
function getPiPackageReceiptPath(options = {}) {
|
|
7336
|
+
const env = options.env ?? process.env;
|
|
7337
|
+
const root = options.configRoot ?? env.XDG_CONFIG_HOME?.trim() ?? join9(options.homeDir ?? homedir5(), ".config");
|
|
7338
|
+
return join9(root, "thoth-agents", "pi-package.json");
|
|
7339
|
+
}
|
|
7340
|
+
function parse(value) {
|
|
7341
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
7342
|
+
throw new Error("Receipt must be a JSON object.");
|
|
7343
|
+
const record = value;
|
|
7344
|
+
if (Object.keys(record).sort().join("|") !== [...keys].sort().join("|"))
|
|
7345
|
+
throw new Error("Receipt contains missing or additional keys.");
|
|
7346
|
+
if (record.schemaVersion !== 1 || record.owner !== "thoth-agents" || record.scope !== "user" || record.packageName !== "thoth-agents")
|
|
7347
|
+
throw new Error("Receipt identity is invalid.");
|
|
7348
|
+
if (typeof record.version !== "string" || !semver.test(record.version))
|
|
7349
|
+
throw new Error("Receipt version is invalid.");
|
|
7350
|
+
const npmSource = `npm:thoth-agents@${record.version}`;
|
|
7351
|
+
const npmIdentity = record.source === npmSource && record.installSource === npmSource;
|
|
7352
|
+
const localIdentity = typeof record.source === "string" && record.source.length > 0 && !isAbsolute2(record.source) && !/^[a-z][a-z+.-]*:/i.test(record.source) && typeof record.installSource === "string" && isAbsolute2(record.installSource);
|
|
7353
|
+
if (!npmIdentity && !localIdentity)
|
|
7354
|
+
throw new Error("Receipt source is not canonical.");
|
|
7355
|
+
if (typeof record.manifestSha256 !== "string" || !digest.test(record.manifestSha256) || typeof record.extensionSha256 !== "string" || !digest.test(record.extensionSha256))
|
|
7356
|
+
throw new Error("Receipt digests are invalid.");
|
|
7357
|
+
return record;
|
|
7358
|
+
}
|
|
7359
|
+
function readPiPackageReceipt(options = {}) {
|
|
7360
|
+
const path = getPiPackageReceiptPath(options);
|
|
7361
|
+
if (!existsSync13(path)) return { status: "missing", path };
|
|
7362
|
+
try {
|
|
7363
|
+
return {
|
|
7364
|
+
status: "valid",
|
|
7365
|
+
path,
|
|
7366
|
+
receipt: parse(JSON.parse(readFileSync10(path, "utf8")))
|
|
7367
|
+
};
|
|
7368
|
+
} catch (error) {
|
|
7369
|
+
return {
|
|
7370
|
+
status: "invalid",
|
|
7371
|
+
path,
|
|
7372
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7373
|
+
};
|
|
7374
|
+
}
|
|
7375
|
+
}
|
|
7376
|
+
function writePiPackageReceipt(receipt, options = {}) {
|
|
7377
|
+
const path = getPiPackageReceiptPath(options);
|
|
7378
|
+
try {
|
|
7379
|
+
const validated = parse(receipt);
|
|
7380
|
+
mkdirSync6(dirname8(path), { recursive: true });
|
|
7381
|
+
const temporary = `${path}.tmp-${process.pid}`;
|
|
7382
|
+
writeFileSync6(temporary, `${JSON.stringify(validated, null, 2)}
|
|
7383
|
+
`, {
|
|
7384
|
+
flag: "wx"
|
|
7385
|
+
});
|
|
7386
|
+
try {
|
|
7387
|
+
renameSync5(temporary, path);
|
|
7388
|
+
} catch (error) {
|
|
7389
|
+
rmSync5(temporary, { force: true });
|
|
7390
|
+
throw error;
|
|
7391
|
+
}
|
|
7392
|
+
return { success: true, path };
|
|
7393
|
+
} catch (error) {
|
|
7394
|
+
return {
|
|
7395
|
+
success: false,
|
|
7396
|
+
path,
|
|
7397
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7398
|
+
};
|
|
7399
|
+
}
|
|
7400
|
+
}
|
|
7401
|
+
function classifyPiPackageOwnership(input) {
|
|
7402
|
+
if (input.receipt.status === "invalid" || input.projectPackages.length > 0 || input.globalPackages.length > 1)
|
|
7403
|
+
return {
|
|
7404
|
+
state: "conflicting",
|
|
7405
|
+
reason: input.receipt.status === "invalid" ? input.receipt.error : "Ambiguous or project-local first-party source."
|
|
7406
|
+
};
|
|
7407
|
+
const configured = input.globalPackages[0];
|
|
7408
|
+
if (input.receipt.status === "missing")
|
|
7409
|
+
return { state: configured ? "configured-unowned" : "missing" };
|
|
7410
|
+
if (!configured) return { state: "owned-missing" };
|
|
7411
|
+
const configuredPath = configured.installedPath;
|
|
7412
|
+
const receipt = input.receipt.receipt;
|
|
7413
|
+
const identityMatches = (configured.packageName === void 0 || configured.packageName === receipt.packageName) && (configured.packageVersion === void 0 || configured.packageVersion === receipt.version);
|
|
7414
|
+
const isNpm = receipt.source === receipt.installSource;
|
|
7415
|
+
const pathMatches = isNpm || configuredPath !== void 0 && piPackagePathsEqual(configuredPath, receipt.installSource);
|
|
7416
|
+
return configured.source === receipt.source && pathMatches && identityMatches ? { state: "owned-current" } : {
|
|
7417
|
+
state: "conflicting",
|
|
7418
|
+
reason: "Configured source, resolved package identity, or path differs from the ownership receipt."
|
|
7419
|
+
};
|
|
7420
|
+
}
|
|
7421
|
+
|
|
7422
|
+
// src/cli/pi-paths.ts
|
|
7423
|
+
import { homedir as homedir6 } from "os";
|
|
7424
|
+
import { join as join10, resolve as resolve5 } from "path";
|
|
7425
|
+
function resolvePiPaths(options = {}) {
|
|
7426
|
+
const env = options.env ?? process.env;
|
|
7427
|
+
const homeDir = resolve5(
|
|
7428
|
+
options.homeDir ?? env.HOME ?? env.USERPROFILE ?? homedir6()
|
|
7429
|
+
);
|
|
7430
|
+
const defaultPiRoot = join10(homeDir, ".pi", "agent");
|
|
7431
|
+
const configured = env.PI_CODING_AGENT_DIR?.trim();
|
|
7432
|
+
const piRoot = configured ? resolve5(configured) : defaultPiRoot;
|
|
7433
|
+
const cwd = resolve5(options.cwd ?? process.cwd());
|
|
7434
|
+
const configRoot = env.XDG_CONFIG_HOME?.trim() ? resolve5(env.XDG_CONFIG_HOME) : join10(homeDir, ".config");
|
|
7435
|
+
return {
|
|
7436
|
+
homeDir,
|
|
7437
|
+
piRoot,
|
|
7438
|
+
defaultPiRoot,
|
|
7439
|
+
customPiRoot: piRoot !== defaultPiRoot,
|
|
7440
|
+
skillsDestinationCompatible: piRoot === defaultPiRoot,
|
|
7441
|
+
appendSystemPath: join10(piRoot, "APPEND_SYSTEM.md"),
|
|
7442
|
+
agentsRoot: join10(piRoot, "agents"),
|
|
7443
|
+
alternateAgentsRoot: join10(piRoot, "subagents"),
|
|
7444
|
+
ownedSkillsRoot: join10(piRoot, "skills"),
|
|
7445
|
+
mcpConfigPath: join10(configRoot, "mcp", "mcp.json"),
|
|
7446
|
+
projectAgentRoots: [
|
|
7447
|
+
join10(cwd, ".pi", "agents"),
|
|
7448
|
+
join10(cwd, ".pi", "subagents")
|
|
7449
|
+
],
|
|
7450
|
+
projectMcpPaths: [join10(cwd, ".mcp.json"), join10(cwd, ".pi", "mcp.json")]
|
|
7451
|
+
};
|
|
7452
|
+
}
|
|
7453
|
+
|
|
7454
|
+
// src/cli/pi-install.ts
|
|
7455
|
+
var PI_MINIMUM_VERSION = "0.84.4";
|
|
7456
|
+
var PI_NODE_MINIMUM = "22.19.0";
|
|
7457
|
+
var PI_COMMAND_TIMEOUT_MS = 12e4;
|
|
7458
|
+
var PI_PACKAGE_SPECS = [
|
|
7459
|
+
{
|
|
7460
|
+
id: "delegation",
|
|
7461
|
+
source: "npm:pi-subagents-j0k3r@1.5.9",
|
|
7462
|
+
packageName: "pi-subagents-j0k3r",
|
|
7463
|
+
version: "1.5.9"
|
|
7464
|
+
},
|
|
7465
|
+
{
|
|
7466
|
+
id: "context7",
|
|
7467
|
+
source: "npm:@upstash/context7-pi@0.1.2",
|
|
7468
|
+
packageName: "@upstash/context7-pi",
|
|
7469
|
+
version: "0.1.2"
|
|
7470
|
+
},
|
|
7471
|
+
{
|
|
7472
|
+
id: "web-access",
|
|
7473
|
+
source: "npm:pi-web-access@0.27.0",
|
|
7474
|
+
packageName: "pi-web-access",
|
|
7475
|
+
version: "0.27.0"
|
|
7476
|
+
},
|
|
7477
|
+
{
|
|
7478
|
+
id: "grep-adapter",
|
|
7479
|
+
source: "npm:pi-mcp-adapter@2.32.1",
|
|
7480
|
+
packageName: "pi-mcp-adapter",
|
|
7481
|
+
version: "2.32.1"
|
|
7482
|
+
},
|
|
7483
|
+
{
|
|
7484
|
+
id: "ask-user-question",
|
|
7485
|
+
source: "npm:@juicesharp/rpiv-ask-user-question@2.9.0",
|
|
7486
|
+
packageName: "@juicesharp/rpiv-ask-user-question",
|
|
7487
|
+
version: "2.9.0"
|
|
7488
|
+
},
|
|
7489
|
+
{
|
|
7490
|
+
id: "todo",
|
|
7491
|
+
source: "npm:@juicesharp/rpiv-todo@2.9.0",
|
|
7492
|
+
packageName: "@juicesharp/rpiv-todo",
|
|
7493
|
+
version: "2.9.0"
|
|
7494
|
+
}
|
|
7495
|
+
];
|
|
7496
|
+
var PI_GREP_MCP_ENTRY = {
|
|
7497
|
+
url: "https://mcp.grep.app",
|
|
7498
|
+
protocolVersion: "legacy",
|
|
7499
|
+
lifecycle: "lazy"
|
|
7500
|
+
};
|
|
7501
|
+
function getPiFirstPartySource(version) {
|
|
7502
|
+
return `npm:thoth-agents@${version}`;
|
|
7503
|
+
}
|
|
7504
|
+
function packageRootFor(options) {
|
|
7505
|
+
const root = options.packageRoot ?? findPackageRoot(dirname9(fileURLToPath5(import.meta.url)));
|
|
7506
|
+
if (!root)
|
|
7507
|
+
throw new Error(
|
|
7508
|
+
"Unable to locate the executing thoth-agents package root."
|
|
7509
|
+
);
|
|
7510
|
+
return root;
|
|
7511
|
+
}
|
|
7512
|
+
function packageVersionFor(root) {
|
|
7513
|
+
const value = JSON.parse(
|
|
7514
|
+
readFileSync11(join11(root, "package.json"), "utf8")
|
|
7515
|
+
);
|
|
7516
|
+
if (typeof value.version !== "string")
|
|
7517
|
+
throw new Error("Executing thoth-agents package version is invalid.");
|
|
7518
|
+
return value.version;
|
|
7519
|
+
}
|
|
7520
|
+
function sha2562(path) {
|
|
7521
|
+
return createHash4("sha256").update(readFileSync11(path)).digest("hex");
|
|
7522
|
+
}
|
|
7523
|
+
function verifyPiFirstPartyPackage(input) {
|
|
7524
|
+
try {
|
|
7525
|
+
const manifestPath = join11(input.packageRoot, "package.json");
|
|
7526
|
+
const extensionPath = join11(input.packageRoot, "dist", "pi.js");
|
|
7527
|
+
const assetsPath = join11(
|
|
7528
|
+
input.packageRoot,
|
|
7529
|
+
"pi",
|
|
7530
|
+
".thoth-agents-assets.json"
|
|
7531
|
+
);
|
|
7532
|
+
for (const path of [manifestPath, extensionPath, assetsPath])
|
|
7533
|
+
if (!existsSync14(path) || !lstatSync3(path).isFile() || lstatSync3(path).isSymbolicLink())
|
|
7534
|
+
throw new Error(
|
|
7535
|
+
`Required regular package asset is missing or symlinked: ${path}`
|
|
7536
|
+
);
|
|
7537
|
+
const manifest = JSON.parse(readFileSync11(manifestPath, "utf8"));
|
|
7538
|
+
if (manifest.name !== "thoth-agents" || manifest.version !== input.version || JSON.stringify(manifest.pi) !== JSON.stringify({ extensions: ["./dist/pi.js"], skills: ["./skills"] }))
|
|
7539
|
+
throw new Error(
|
|
7540
|
+
"Installed package identity or Pi manifest is inconsistent."
|
|
7541
|
+
);
|
|
7542
|
+
const assets = JSON.parse(readFileSync11(assetsPath, "utf8"));
|
|
7543
|
+
const expectedAgentPaths = PI_SPECIALIST_ROLES.map(
|
|
7544
|
+
(role) => `agents/${piSpecialistName(role)}.md`
|
|
7545
|
+
);
|
|
7546
|
+
if (assets.schemaVersion !== 1 || assets.owner !== "thoth-agents" || !isRecord6(assets.files) || Object.keys(assets.files).sort().join("|") !== [...expectedAgentPaths].sort().join("|"))
|
|
7547
|
+
throw new Error("Installed Pi specialist provenance is invalid.");
|
|
7548
|
+
for (const relativePath of expectedAgentPaths) {
|
|
7549
|
+
const path = join11(input.packageRoot, "pi", relativePath);
|
|
7550
|
+
if (!existsSync14(path) || !lstatSync3(path).isFile() || lstatSync3(path).isSymbolicLink() || sha2562(path) !== assets.files[relativePath])
|
|
7551
|
+
throw new Error(
|
|
7552
|
+
`Installed Pi specialist asset is missing, symlinked, or stale: ${relativePath}`
|
|
7553
|
+
);
|
|
7554
|
+
}
|
|
7555
|
+
for (const skill of THOTH_OWNED_SKILL_NAMES) {
|
|
7556
|
+
const path = join11(input.packageRoot, "skills", skill, "SKILL.md");
|
|
7557
|
+
if (!existsSync14(path) || !lstatSync3(path).isFile() || lstatSync3(path).isSymbolicLink())
|
|
7558
|
+
throw new Error(
|
|
7559
|
+
`Installed Pi manifest skill is missing or symlinked: ${skill}`
|
|
7560
|
+
);
|
|
7561
|
+
}
|
|
7562
|
+
const manifestSha256 = sha2562(manifestPath);
|
|
7563
|
+
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
|
+
const observation = observePiNativeRoot({
|
|
7572
|
+
extensionPath,
|
|
7573
|
+
manifestSha256,
|
|
7574
|
+
extensionSha256
|
|
7575
|
+
});
|
|
7576
|
+
if (observation.state !== "observed-at-install")
|
|
7577
|
+
throw new Error(observation.basis.join("; "));
|
|
7578
|
+
return {
|
|
7579
|
+
success: true,
|
|
7580
|
+
receipt: {
|
|
7581
|
+
schemaVersion: 1,
|
|
7582
|
+
owner: "thoth-agents",
|
|
7583
|
+
scope: "user",
|
|
7584
|
+
packageName: "thoth-agents",
|
|
7585
|
+
source: input.source,
|
|
7586
|
+
installSource: input.installSource,
|
|
7587
|
+
version: input.version,
|
|
7588
|
+
manifestSha256,
|
|
7589
|
+
extensionSha256
|
|
7590
|
+
}
|
|
7591
|
+
};
|
|
7592
|
+
} catch (error) {
|
|
7593
|
+
return {
|
|
7594
|
+
success: false,
|
|
7595
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7596
|
+
};
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
7599
|
+
function isRecord6(value) {
|
|
7600
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7601
|
+
}
|
|
7602
|
+
function readJsonObject(path) {
|
|
7603
|
+
if (!existsSync14(path)) return {};
|
|
7604
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
7605
|
+
if (!isRecord6(parsed)) throw new Error(`${path} must contain a JSON object.`);
|
|
7606
|
+
return parsed;
|
|
7607
|
+
}
|
|
7608
|
+
function mergePiGrepMcpConfig(current) {
|
|
7609
|
+
const rawServers = current.mcpServers;
|
|
7610
|
+
if (rawServers !== void 0 && !isRecord6(rawServers)) {
|
|
7611
|
+
throw new Error("Global MCP mcpServers must be a JSON object.");
|
|
7612
|
+
}
|
|
7613
|
+
const servers = isRecord6(rawServers) ? rawServers : {};
|
|
7614
|
+
const existing = servers.grep;
|
|
7615
|
+
if (existing !== void 0 && JSON.stringify(existing) !== JSON.stringify(PI_GREP_MCP_ENTRY)) {
|
|
7616
|
+
throw new Error(
|
|
7617
|
+
'Global MCP server "grep" is not owned by thoth-agents and conflicts with the required grep.app entry.'
|
|
7618
|
+
);
|
|
7619
|
+
}
|
|
7620
|
+
return {
|
|
7621
|
+
...current,
|
|
7622
|
+
mcpServers: { ...servers, grep: { ...PI_GREP_MCP_ENTRY } }
|
|
7623
|
+
};
|
|
7624
|
+
}
|
|
7625
|
+
function frontmatterValue(content, field) {
|
|
7626
|
+
const lines = content.split(/\r?\n/);
|
|
7627
|
+
if (lines[0]?.trim() !== "---") return void 0;
|
|
7628
|
+
const end = lines.findIndex(
|
|
7629
|
+
(line2, index) => index > 0 && line2.trim() === "---"
|
|
7630
|
+
);
|
|
7631
|
+
if (end === -1) return void 0;
|
|
7632
|
+
const prefix = `${field}:`;
|
|
7633
|
+
const line = lines.slice(1, end).find((candidate) => candidate.startsWith(prefix));
|
|
7634
|
+
if (!line) return void 0;
|
|
7635
|
+
const value = line.slice(prefix.length).trim();
|
|
7636
|
+
if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
|
|
7637
|
+
return value.slice(1, -1).trim();
|
|
7638
|
+
}
|
|
7639
|
+
return value || void 0;
|
|
7640
|
+
}
|
|
7641
|
+
function findAgentConflicts(paths) {
|
|
7642
|
+
const canonical = new Set(PI_SPECIALIST_ROLES.map(piSpecialistName));
|
|
7643
|
+
const conflicts = [];
|
|
7644
|
+
for (const root of [paths.agentsRoot, paths.alternateAgentsRoot]) {
|
|
7645
|
+
if (!existsSync14(root)) continue;
|
|
7646
|
+
for (const name of readdirSync3(root)) {
|
|
7647
|
+
if (!name.endsWith(".md")) continue;
|
|
7648
|
+
const path = join11(root, name);
|
|
7649
|
+
const content = readFileSync11(path, "utf8");
|
|
7650
|
+
const role = frontmatterValue(content, "name");
|
|
7651
|
+
const filenameRole = name.slice(0, -".md".length).toLowerCase();
|
|
7652
|
+
const canonicalRole = role && canonical.has(role) ? role : canonical.has(filenameRole) ? filenameRole : void 0;
|
|
7653
|
+
if (canonicalRole && frontmatterValue(content, "managed-by") !== PI_MANAGED_OWNER) {
|
|
7654
|
+
conflicts.push(
|
|
7655
|
+
`${path} defines canonical specialist ${canonicalRole} without thoth-agents ownership.`
|
|
7656
|
+
);
|
|
7657
|
+
}
|
|
7658
|
+
}
|
|
7659
|
+
}
|
|
7660
|
+
return conflicts;
|
|
7661
|
+
}
|
|
7662
|
+
function buildPiSetupPlan(options = {}) {
|
|
7663
|
+
const paths = resolvePiPaths(options);
|
|
7664
|
+
let packageRoot;
|
|
7665
|
+
let expectedVersion;
|
|
7666
|
+
try {
|
|
7667
|
+
packageRoot = packageRootFor(options);
|
|
7668
|
+
expectedVersion = options.expectedVersion ?? packageVersionFor(packageRoot);
|
|
7669
|
+
} catch {
|
|
7670
|
+
packageRoot = options.packageRoot ?? "";
|
|
7671
|
+
expectedVersion = options.expectedVersion ?? "";
|
|
7672
|
+
}
|
|
7673
|
+
const blockers = [];
|
|
7674
|
+
const diagnostics = [];
|
|
7675
|
+
if (!paths.skillsDestinationCompatible) {
|
|
7676
|
+
blockers.push(
|
|
7677
|
+
`PI_CODING_AGENT_DIR resolves to ${paths.piRoot}, but the required skills CLI targets ${paths.defaultPiRoot}. Remove the override or install the four external skills manually into ${paths.ownedSkillsRoot}.`
|
|
7678
|
+
);
|
|
7679
|
+
}
|
|
7680
|
+
blockers.push(...findAgentConflicts(paths));
|
|
7681
|
+
let mcpContent;
|
|
7682
|
+
try {
|
|
7683
|
+
mcpContent = `${JSON.stringify(
|
|
7684
|
+
mergePiGrepMcpConfig(readJsonObject(paths.mcpConfigPath)),
|
|
7685
|
+
null,
|
|
7686
|
+
2
|
|
7687
|
+
)}
|
|
7688
|
+
`;
|
|
7689
|
+
} catch (error) {
|
|
7690
|
+
blockers.push(error instanceof Error ? error.message : String(error));
|
|
7691
|
+
}
|
|
7692
|
+
for (const path of [...paths.projectAgentRoots, ...paths.projectMcpPaths]) {
|
|
7693
|
+
if (existsSync14(path))
|
|
7694
|
+
diagnostics.push(
|
|
7695
|
+
`Project-local Pi resource may shadow global managed state: ${path}`
|
|
7696
|
+
);
|
|
7697
|
+
}
|
|
7698
|
+
const rendered = piAdapter.render({
|
|
7699
|
+
projectRoot: options.cwd ?? process.cwd()
|
|
7700
|
+
});
|
|
7701
|
+
if (!expectedVersion || !packageRoot)
|
|
7702
|
+
blockers.push(
|
|
7703
|
+
"The executing thoth-agents package identity is unavailable."
|
|
7704
|
+
);
|
|
7705
|
+
const firstPartySource = options.firstPartySource ?? getPiFirstPartySource(expectedVersion);
|
|
7706
|
+
const items = [
|
|
7707
|
+
{
|
|
7708
|
+
kind: "preflight",
|
|
7709
|
+
description: "Verify Node.js >=22.19 and Pi >=0.84.4 before mutation",
|
|
7710
|
+
target: "node/pi runtime"
|
|
7711
|
+
},
|
|
7712
|
+
{
|
|
7713
|
+
kind: "package",
|
|
7714
|
+
description: `Install and verify first-party native Pi package ${firstPartySource}`,
|
|
7715
|
+
target: firstPartySource,
|
|
7716
|
+
command: {
|
|
7717
|
+
command: "pi",
|
|
7718
|
+
args: ["install", firstPartySource, "--no-approve"]
|
|
7719
|
+
}
|
|
7720
|
+
},
|
|
7721
|
+
...PI_PACKAGE_SPECS.map((pkg) => ({
|
|
7722
|
+
kind: "package",
|
|
7723
|
+
description: `Install and verify Pi package ${pkg.source}`,
|
|
7724
|
+
target: pkg.source,
|
|
7725
|
+
command: { command: "pi", args: ["install", pkg.source, "--no-approve"] }
|
|
7726
|
+
})),
|
|
7727
|
+
...mcpContent === void 0 ? [] : [
|
|
7728
|
+
{
|
|
7729
|
+
kind: "mcp",
|
|
7730
|
+
description: "Merge attributable global grep.app MCP entry",
|
|
7731
|
+
target: paths.mcpConfigPath,
|
|
7732
|
+
content: mcpContent
|
|
7733
|
+
}
|
|
7734
|
+
],
|
|
7735
|
+
...rendered.artifacts.filter((artifact) => artifact.kind === "agent-config").map((artifact) => ({
|
|
7736
|
+
kind: "agent",
|
|
7737
|
+
description: artifact.description ?? `Install ${artifact.path}`,
|
|
7738
|
+
target: join11(paths.piRoot, artifact.path),
|
|
7739
|
+
content: String(artifact.content)
|
|
7740
|
+
}))
|
|
7741
|
+
];
|
|
7742
|
+
return {
|
|
7743
|
+
dryRun: options.dryRun ?? false,
|
|
7744
|
+
ready: blockers.length === 0,
|
|
7745
|
+
paths,
|
|
7746
|
+
items,
|
|
7747
|
+
blockers,
|
|
7748
|
+
diagnostics,
|
|
7749
|
+
disclaimers: [
|
|
7750
|
+
"Pi extensions execute with the invoking user's system permissions; package pins and tool allowlists are not a security sandbox.",
|
|
7751
|
+
"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."
|
|
7753
|
+
],
|
|
7754
|
+
options: { ...options, expectedVersion, packageRoot, firstPartySource }
|
|
7755
|
+
};
|
|
7756
|
+
}
|
|
7757
|
+
function formatPiSetupPlan(plan) {
|
|
7758
|
+
return [
|
|
7759
|
+
"Pi setup plan:",
|
|
7760
|
+
...plan.items.map(
|
|
7761
|
+
(item) => item.command ? `- ${item.kind}: ${item.command.command} ${item.command.args.join(" ")}` : `- ${item.kind}: ${item.target}`
|
|
7762
|
+
),
|
|
7763
|
+
...plan.blockers.map((blocker) => `- BLOCKED: ${blocker}`)
|
|
7764
|
+
].join("\n");
|
|
7765
|
+
}
|
|
7766
|
+
function isVersionAtLeast(actual, expected) {
|
|
7767
|
+
const parts = (value) => value.replace(/^v/, "").split(".").slice(0, 3).map((part) => Number.parseInt(part, 10));
|
|
7768
|
+
const left = parts(actual);
|
|
7769
|
+
const right = parts(expected);
|
|
7770
|
+
if (left.some(Number.isNaN) || right.some(Number.isNaN)) return false;
|
|
7771
|
+
for (let index = 0; index < 3; index += 1) {
|
|
7772
|
+
const difference = (left[index] ?? 0) - (right[index] ?? 0);
|
|
7773
|
+
if (difference !== 0) return difference > 0;
|
|
7774
|
+
}
|
|
7775
|
+
return true;
|
|
7776
|
+
}
|
|
7777
|
+
function hasExactInstalledPiPackage(output, source) {
|
|
7778
|
+
return output.split(/\r?\n/).some((line) => line.trim() === source);
|
|
7779
|
+
}
|
|
7780
|
+
function parsePiPackageList(output) {
|
|
7781
|
+
const packages = [];
|
|
7782
|
+
let scope = "user";
|
|
7783
|
+
for (const line of output.split(/\r?\n/)) {
|
|
7784
|
+
const value = line.trim();
|
|
7785
|
+
if (!value || value === "No packages installed.") continue;
|
|
7786
|
+
if (value === "User packages:") {
|
|
7787
|
+
scope = "user";
|
|
7788
|
+
continue;
|
|
7789
|
+
}
|
|
7790
|
+
if (value === "Project packages:") {
|
|
7791
|
+
scope = "project";
|
|
7792
|
+
continue;
|
|
7793
|
+
}
|
|
7794
|
+
const previous = packages.at(-1);
|
|
7795
|
+
if (/^\s{4,}\S/.test(line) && previous && !previous.installedPath) {
|
|
7796
|
+
previous.installedPath = value;
|
|
7797
|
+
continue;
|
|
7798
|
+
}
|
|
7799
|
+
packages.push({ scope, source: value.replace(/ \(filtered\)$/, "") });
|
|
7800
|
+
}
|
|
7801
|
+
return packages;
|
|
7802
|
+
}
|
|
7803
|
+
function isThothPackageLocation(candidate, knownSource) {
|
|
7804
|
+
if (candidate.source === knownSource || /^npm:thoth-agents@/.test(candidate.source))
|
|
7805
|
+
return true;
|
|
7806
|
+
if (!candidate.installedPath) return false;
|
|
7807
|
+
try {
|
|
7808
|
+
const manifest = JSON.parse(
|
|
7809
|
+
readFileSync11(join11(candidate.installedPath, "package.json"), "utf8")
|
|
7810
|
+
);
|
|
7811
|
+
return manifest.name === "thoth-agents";
|
|
7812
|
+
} catch {
|
|
7813
|
+
return false;
|
|
7814
|
+
}
|
|
7815
|
+
}
|
|
7816
|
+
function configuredPackageIdentity(candidate) {
|
|
7817
|
+
if (!candidate.installedPath) return { packageName: "", packageVersion: "" };
|
|
7818
|
+
try {
|
|
7819
|
+
const manifest = JSON.parse(
|
|
7820
|
+
readFileSync11(join11(candidate.installedPath, "package.json"), "utf8")
|
|
7821
|
+
);
|
|
7822
|
+
return {
|
|
7823
|
+
packageName: typeof manifest.name === "string" ? manifest.name : "",
|
|
7824
|
+
packageVersion: typeof manifest.version === "string" ? manifest.version : ""
|
|
7825
|
+
};
|
|
7826
|
+
} catch {
|
|
7827
|
+
return { packageName: "", packageVersion: "" };
|
|
7828
|
+
}
|
|
7829
|
+
}
|
|
7830
|
+
function getPiFirstPartyPackages(packages, knownSource) {
|
|
7831
|
+
return packages.filter((candidate) => isThothPackageLocation(candidate, knownSource)).map((candidate) => ({
|
|
7832
|
+
...candidate,
|
|
7833
|
+
...configuredPackageIdentity(candidate)
|
|
7834
|
+
}));
|
|
7835
|
+
}
|
|
7836
|
+
function matchesInstallSource(candidate, installSource) {
|
|
7837
|
+
return isAbsolute3(installSource) ? candidate.installedPath !== void 0 && piPackagePathsEqual(candidate.installedPath, installSource) : candidate.source === installSource;
|
|
7838
|
+
}
|
|
7839
|
+
function defaultCommandExecutor4(command, args) {
|
|
7840
|
+
let executable = command;
|
|
7841
|
+
let executableArgs = [...args];
|
|
7842
|
+
if (process.platform === "win32" && command === "pi") {
|
|
7843
|
+
const located = spawnSync6("where.exe", ["pi.cmd"], { encoding: "utf8" }).stdout?.split(/\r?\n/).find(Boolean);
|
|
7844
|
+
const cli = located ? join11(
|
|
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)) {
|
|
7854
|
+
executable = process.execPath;
|
|
7855
|
+
executableArgs = [cli, ...args];
|
|
7856
|
+
}
|
|
7857
|
+
}
|
|
7858
|
+
const result = spawnSync6(executable, executableArgs, {
|
|
7859
|
+
encoding: "utf8",
|
|
7860
|
+
timeout: PI_COMMAND_TIMEOUT_MS
|
|
7861
|
+
});
|
|
7862
|
+
return {
|
|
7863
|
+
exitCode: result.status,
|
|
7864
|
+
stdout: result.stdout ?? "",
|
|
7865
|
+
stderr: result.stderr ?? "",
|
|
7866
|
+
error: result.error
|
|
7867
|
+
};
|
|
7868
|
+
}
|
|
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
|
+
function applyPiSetup(plan) {
|
|
7885
|
+
const diagnostics = [...plan.diagnostics];
|
|
7886
|
+
if (!plan.ready)
|
|
7887
|
+
return {
|
|
7888
|
+
success: false,
|
|
7889
|
+
changed: [],
|
|
7890
|
+
diagnostics,
|
|
7891
|
+
error: plan.blockers.join("\n"),
|
|
7892
|
+
failedStep: "preflight",
|
|
7893
|
+
installedPackages: []
|
|
7894
|
+
};
|
|
7895
|
+
if (plan.dryRun)
|
|
7896
|
+
return { success: true, changed: [], diagnostics, installedPackages: [] };
|
|
7897
|
+
const execute = plan.options.commandExecutor ?? defaultCommandExecutor4;
|
|
7898
|
+
const changed = [];
|
|
7899
|
+
const installedPackages = [];
|
|
7900
|
+
let receiptCommitted = false;
|
|
7901
|
+
let configuredPackageRoot;
|
|
7902
|
+
try {
|
|
7903
|
+
const node = execute("node", ["--version"]);
|
|
7904
|
+
if (node.exitCode !== 0 || !isVersionAtLeast(node.stdout.trim(), PI_NODE_MINIMUM))
|
|
7905
|
+
throw new Error(
|
|
7906
|
+
`Node.js >=${PI_NODE_MINIMUM} is required; observed ${node.stdout.trim() || node.stderr.trim() || "unavailable"}.`
|
|
7907
|
+
);
|
|
7908
|
+
const pi = execute("pi", ["--version"]);
|
|
7909
|
+
if (pi.exitCode !== 0 || !isVersionAtLeast(pi.stdout.trim(), PI_MINIMUM_VERSION))
|
|
7910
|
+
throw new Error(
|
|
7911
|
+
`Pi >=${PI_MINIMUM_VERSION} is required; observed ${pi.stdout.trim() || pi.stderr.trim() || "unavailable"}.`
|
|
7912
|
+
);
|
|
7913
|
+
const desiredSource = plan.options.firstPartySource ?? getPiFirstPartySource(plan.options.expectedVersion ?? "");
|
|
7914
|
+
const before = execute("pi", ["list", "--no-approve"]);
|
|
7915
|
+
if (before.exitCode !== 0)
|
|
7916
|
+
throw new Error(
|
|
7917
|
+
`Unable to inspect Pi package ownership before mutation: ${before.stderr}`
|
|
7918
|
+
);
|
|
7919
|
+
const receiptOptions = {
|
|
7920
|
+
homeDir: plan.paths.homeDir,
|
|
7921
|
+
env: plan.options.env,
|
|
7922
|
+
...plan.options.receiptOptions
|
|
7923
|
+
};
|
|
7924
|
+
const receipt = readPiPackageReceipt(receiptOptions);
|
|
7925
|
+
const configuredBefore = parsePiPackageList(before.stdout);
|
|
7926
|
+
const knownReceiptSource = receipt.status === "valid" ? receipt.receipt.source : void 0;
|
|
7927
|
+
const firstPartyBefore = getPiFirstPartyPackages(
|
|
7928
|
+
configuredBefore,
|
|
7929
|
+
knownReceiptSource
|
|
7930
|
+
);
|
|
7931
|
+
const globalPackages = firstPartyBefore.filter(
|
|
7932
|
+
({ scope }) => scope === "user"
|
|
7933
|
+
);
|
|
7934
|
+
const projectPackages = firstPartyBefore.filter(
|
|
7935
|
+
({ scope }) => scope === "project"
|
|
7936
|
+
);
|
|
7937
|
+
const ownership = classifyPiPackageOwnership({
|
|
7938
|
+
receipt,
|
|
7939
|
+
globalPackages,
|
|
7940
|
+
projectPackages
|
|
7941
|
+
});
|
|
7942
|
+
if (ownership.state === "configured-unowned" || ownership.state === "conflicting")
|
|
7943
|
+
throw new Error(
|
|
7944
|
+
`First-party Pi package ownership conflict: ${ownership.reason ?? ownership.state}`
|
|
7945
|
+
);
|
|
7946
|
+
const installed = execute("pi", ["install", desiredSource, "--no-approve"]);
|
|
7947
|
+
if (installed.exitCode !== 0)
|
|
7948
|
+
throw new Error(
|
|
7949
|
+
`Failed to install ${desiredSource}: ${installed.stderr.trim() || "unknown Pi error"}`
|
|
7950
|
+
);
|
|
7951
|
+
installedPackages.push(desiredSource);
|
|
7952
|
+
try {
|
|
7953
|
+
const listed = execute("pi", ["list", "--no-approve"]);
|
|
7954
|
+
const configuredAfter = parsePiPackageList(listed.stdout);
|
|
7955
|
+
const firstPartyAfter = getPiFirstPartyPackages(
|
|
7956
|
+
configuredAfter.filter(
|
|
7957
|
+
(candidate) => isThothPackageLocation(candidate, knownReceiptSource) || matchesInstallSource(candidate, desiredSource)
|
|
7958
|
+
),
|
|
7959
|
+
knownReceiptSource
|
|
7960
|
+
);
|
|
7961
|
+
const projectCandidates = firstPartyAfter.filter(
|
|
7962
|
+
({ scope }) => scope === "project"
|
|
7963
|
+
);
|
|
7964
|
+
const globalCandidates = firstPartyAfter.filter(
|
|
7965
|
+
({ scope }) => scope === "user"
|
|
7966
|
+
);
|
|
7967
|
+
const candidates = globalCandidates.filter(
|
|
7968
|
+
(candidate) => matchesInstallSource(candidate, desiredSource)
|
|
7969
|
+
);
|
|
7970
|
+
if (listed.exitCode !== 0 || candidates.length !== 1 || globalCandidates.length !== 1 || projectCandidates.length !== 0)
|
|
7971
|
+
throw new Error(
|
|
7972
|
+
`Pi did not verify the exact installed package source ${desiredSource}.`
|
|
7973
|
+
);
|
|
7974
|
+
const resolvedPackageRoot = candidates[0]?.installedPath;
|
|
7975
|
+
if (!resolvedPackageRoot || !isAbsolute3(resolvedPackageRoot) || !existsSync14(resolvedPackageRoot) || !lstatSync3(resolvedPackageRoot).isDirectory() || lstatSync3(resolvedPackageRoot).isSymbolicLink())
|
|
7976
|
+
throw new Error(
|
|
7977
|
+
`Pi did not report one absolute regular installed directory for ${desiredSource}.`
|
|
7978
|
+
);
|
|
7979
|
+
const packageRoot = resolvedPackageRoot;
|
|
7980
|
+
configuredPackageRoot = packageRoot;
|
|
7981
|
+
const verified = (plan.options.verifyFirstParty ?? verifyPiFirstPartyPackage)({
|
|
7982
|
+
source: candidates[0]?.source ?? desiredSource,
|
|
7983
|
+
installSource: desiredSource,
|
|
7984
|
+
version: plan.options.expectedVersion ?? packageVersionFor(packageRoot),
|
|
7985
|
+
packageRoot
|
|
7986
|
+
});
|
|
7987
|
+
if (!verified.success) throw new Error(verified.error);
|
|
7988
|
+
const committed = writePiPackageReceipt(verified.receipt, receiptOptions);
|
|
7989
|
+
if (!committed.success)
|
|
7990
|
+
throw new Error(
|
|
7991
|
+
`Could not commit Pi package receipt: ${committed.error}`
|
|
7992
|
+
);
|
|
7993
|
+
receiptCommitted = true;
|
|
7994
|
+
changed.push(committed.path);
|
|
7995
|
+
} catch (originalError) {
|
|
7996
|
+
const restoreSource = ownership.state === "owned-current" && receipt.status === "valid" ? receipt.receipt.installSource : void 0;
|
|
7997
|
+
const rollback = restoreSource ? execute("pi", ["install", restoreSource, "--no-approve"]) : execute("pi", ["remove", desiredSource, "--no-approve"]);
|
|
7998
|
+
const afterRollback = execute("pi", ["list", "--no-approve"]);
|
|
7999
|
+
const rollbackPackages = parsePiPackageList(afterRollback.stdout);
|
|
8000
|
+
const restored = (() => {
|
|
8001
|
+
if (rollback.exitCode !== 0 || afterRollback.exitCode !== 0)
|
|
8002
|
+
return false;
|
|
8003
|
+
if (restoreSource && receipt.status === "valid") {
|
|
8004
|
+
const priorPackages = getPiFirstPartyPackages(
|
|
8005
|
+
rollbackPackages,
|
|
8006
|
+
receipt.receipt.source
|
|
8007
|
+
);
|
|
8008
|
+
return classifyPiPackageOwnership({
|
|
8009
|
+
receipt,
|
|
8010
|
+
globalPackages: priorPackages.filter(
|
|
8011
|
+
({ scope }) => scope === "user"
|
|
8012
|
+
),
|
|
8013
|
+
projectPackages: priorPackages.filter(
|
|
8014
|
+
({ scope }) => scope === "project"
|
|
8015
|
+
)
|
|
8016
|
+
}).state === "owned-current";
|
|
8017
|
+
}
|
|
8018
|
+
return !rollbackPackages.some(
|
|
8019
|
+
(candidate) => matchesInstallSource(candidate, desiredSource)
|
|
8020
|
+
);
|
|
8021
|
+
})();
|
|
8022
|
+
if (!restored)
|
|
8023
|
+
return {
|
|
8024
|
+
success: false,
|
|
8025
|
+
changed,
|
|
8026
|
+
diagnostics,
|
|
8027
|
+
error: `${originalError instanceof Error ? originalError.message : String(originalError)}; rollback failed: ${rollback.stderr || afterRollback.stderr}`,
|
|
8028
|
+
failedStep: "package",
|
|
8029
|
+
installedPackages,
|
|
8030
|
+
rollbackFailed: true,
|
|
8031
|
+
manualRecovery: restoreSource ? `pi install ${restoreSource} --no-approve` : `pi remove ${desiredSource} --no-approve`
|
|
8032
|
+
};
|
|
8033
|
+
return {
|
|
8034
|
+
success: false,
|
|
8035
|
+
changed,
|
|
8036
|
+
diagnostics,
|
|
8037
|
+
error: originalError instanceof Error ? originalError.message : String(originalError),
|
|
8038
|
+
failedStep: "package",
|
|
8039
|
+
installedPackages,
|
|
8040
|
+
receiptCommitted: false
|
|
8041
|
+
};
|
|
8042
|
+
}
|
|
8043
|
+
if (!configuredPackageRoot)
|
|
8044
|
+
throw new Error("Verified Pi package root is unavailable.");
|
|
8045
|
+
const migration = migrateLegacyPiResources({
|
|
8046
|
+
packageRoot: configuredPackageRoot,
|
|
8047
|
+
piRoot: plan.paths.piRoot
|
|
8048
|
+
});
|
|
8049
|
+
diagnostics.push(...migration.manualActions);
|
|
8050
|
+
if (!migration.success)
|
|
8051
|
+
throw new Error(migration.error ?? "Pi legacy migration failed.");
|
|
8052
|
+
changed.push(...migration.changed);
|
|
8053
|
+
for (const pkg of PI_PACKAGE_SPECS) {
|
|
8054
|
+
const result = execute("pi", ["install", pkg.source, "--no-approve"]);
|
|
8055
|
+
if (result.exitCode !== 0)
|
|
8056
|
+
throw new Error(
|
|
8057
|
+
`Failed to install ${pkg.source}: ${result.stderr.trim() || "unknown Pi error"}`
|
|
8058
|
+
);
|
|
8059
|
+
const listed = execute("pi", ["list", "--no-approve"]);
|
|
8060
|
+
if (listed.exitCode !== 0 || !hasExactInstalledPiPackage(listed.stdout, pkg.source)) {
|
|
8061
|
+
throw new Error(
|
|
8062
|
+
`Pi did not verify the exact installed package source ${pkg.source}.`
|
|
8063
|
+
);
|
|
8064
|
+
}
|
|
8065
|
+
installedPackages.push(pkg.source);
|
|
8066
|
+
}
|
|
8067
|
+
for (const item of plan.items.filter(
|
|
8068
|
+
(candidate) => candidate.content !== void 0 && candidate.kind === "mcp"
|
|
8069
|
+
)) {
|
|
8070
|
+
if (writePiManagedText(item.target, item.content ?? ""))
|
|
8071
|
+
changed.push(item.target);
|
|
8072
|
+
}
|
|
8073
|
+
const resources = syncPiSpecialists({
|
|
8074
|
+
packageRoot: configuredPackageRoot,
|
|
8075
|
+
piRoot: plan.paths.piRoot,
|
|
8076
|
+
projectRoots: plan.paths.projectAgentRoots
|
|
8077
|
+
});
|
|
8078
|
+
diagnostics.push(...resources.diagnostics);
|
|
8079
|
+
if (!resources.success)
|
|
8080
|
+
throw new Error(
|
|
8081
|
+
resources.error ?? "Pi specialist synchronization failed."
|
|
8082
|
+
);
|
|
8083
|
+
changed.push(...resources.changed);
|
|
8084
|
+
return {
|
|
8085
|
+
success: true,
|
|
8086
|
+
changed,
|
|
8087
|
+
diagnostics,
|
|
8088
|
+
installedPackages,
|
|
8089
|
+
receiptCommitted,
|
|
8090
|
+
configuredPackageRoot
|
|
8091
|
+
};
|
|
8092
|
+
} catch (error) {
|
|
8093
|
+
return {
|
|
8094
|
+
success: false,
|
|
8095
|
+
changed,
|
|
8096
|
+
diagnostics,
|
|
8097
|
+
error: error instanceof Error ? error.message : String(error),
|
|
8098
|
+
failedStep: installedPackages.length < plan.items.filter((item) => item.kind === "package").length ? "package" : "managed-surface",
|
|
8099
|
+
installedPackages,
|
|
8100
|
+
receiptCommitted
|
|
8101
|
+
};
|
|
8102
|
+
}
|
|
8103
|
+
}
|
|
8104
|
+
|
|
8105
|
+
// src/cli/operations/pi.ts
|
|
8106
|
+
var PI_DISPLAY_NAME = "Pi";
|
|
8107
|
+
var MODEL_ROOT_LIMITATION = "Pi owns the ambient root session model; only thoth-agents-owned specialist definitions may be changed.";
|
|
8108
|
+
var piActions = [
|
|
8109
|
+
{
|
|
8110
|
+
id: "pi-status",
|
|
8111
|
+
kind: "status",
|
|
8112
|
+
label: "Status",
|
|
8113
|
+
description: "Inspect Pi packages, resources, research providers, and CLI ledger",
|
|
8114
|
+
dryRun: false,
|
|
8115
|
+
requiresConfirmation: false,
|
|
8116
|
+
supported: true
|
|
8117
|
+
},
|
|
8118
|
+
{
|
|
8119
|
+
id: "pi-list",
|
|
8120
|
+
kind: "list",
|
|
8121
|
+
label: "List",
|
|
8122
|
+
description: "List Pi-managed targets and truthful capability limits",
|
|
8123
|
+
dryRun: false,
|
|
8124
|
+
requiresConfirmation: false,
|
|
8125
|
+
supported: true
|
|
8126
|
+
},
|
|
8127
|
+
{
|
|
8128
|
+
id: "pi-install",
|
|
8129
|
+
kind: "install",
|
|
8130
|
+
label: "Install",
|
|
8131
|
+
description: "Preview complete ordered Pi setup",
|
|
8132
|
+
dryRun: true,
|
|
8133
|
+
requiresConfirmation: true,
|
|
8134
|
+
supported: true
|
|
8135
|
+
},
|
|
8136
|
+
{
|
|
8137
|
+
id: "pi-update",
|
|
8138
|
+
kind: "update",
|
|
8139
|
+
label: "Update",
|
|
8140
|
+
description: "Preview an installation-equivalent Pi refresh",
|
|
8141
|
+
dryRun: true,
|
|
8142
|
+
requiresConfirmation: true,
|
|
8143
|
+
supported: true
|
|
8144
|
+
},
|
|
8145
|
+
{
|
|
8146
|
+
id: "pi-sync",
|
|
8147
|
+
kind: "sync",
|
|
8148
|
+
label: "Sync",
|
|
8149
|
+
description: "Preview attributable root, specialist, skill, and grep configuration sync",
|
|
8150
|
+
dryRun: true,
|
|
8151
|
+
requiresConfirmation: true,
|
|
8152
|
+
supported: true
|
|
8153
|
+
},
|
|
8154
|
+
{
|
|
8155
|
+
id: "pi-model-config",
|
|
8156
|
+
kind: "model-config",
|
|
8157
|
+
label: "Model",
|
|
8158
|
+
description: "Configure attributable Pi specialist model and effort fields",
|
|
8159
|
+
dryRun: true,
|
|
8160
|
+
requiresConfirmation: true,
|
|
8161
|
+
supported: true,
|
|
8162
|
+
disabledReason: MODEL_ROOT_LIMITATION
|
|
8163
|
+
}
|
|
8164
|
+
];
|
|
8165
|
+
var piOperationAdapter = {
|
|
8166
|
+
id: "pi",
|
|
8167
|
+
displayName: PI_DISPLAY_NAME,
|
|
8168
|
+
available: true,
|
|
8169
|
+
description: "Pi-native packages, ambient root, six specialists, skills, and hybrid research stack.",
|
|
8170
|
+
actions: piActions
|
|
8171
|
+
};
|
|
8172
|
+
function disclaimers() {
|
|
8173
|
+
return [
|
|
8174
|
+
{
|
|
8175
|
+
code: "pi-no-os-sandbox",
|
|
8176
|
+
message: "Pi extensions execute with the invoking user's system permissions; tool allowlists are not an OS or credential sandbox."
|
|
8177
|
+
},
|
|
8178
|
+
{
|
|
8179
|
+
code: "pi-runtime-owned",
|
|
8180
|
+
message: "Pi and pi-subagents own execution, concurrency, task/history storage, trust, and lifecycle."
|
|
8181
|
+
},
|
|
8182
|
+
{
|
|
8183
|
+
code: "pi-research-independent",
|
|
8184
|
+
message: "Context7, web access, and grep.app availability is reported independently; provider configuration and credentials remain operator-owned."
|
|
8185
|
+
},
|
|
8186
|
+
{ code: "pi-root-model-owned", message: MODEL_ROOT_LIMITATION }
|
|
8187
|
+
];
|
|
8188
|
+
}
|
|
8189
|
+
function warning3(message, code, severity = "important") {
|
|
8190
|
+
return { severity, message, code };
|
|
8191
|
+
}
|
|
8192
|
+
var RESEARCH_PROVIDER_LABEL = {
|
|
8193
|
+
context7: "Context7",
|
|
8194
|
+
"web-access": "Web access",
|
|
8195
|
+
grep: "grep.app"
|
|
8196
|
+
};
|
|
8197
|
+
function researchRuntimeState(provider, targets, evidence) {
|
|
8198
|
+
const observed = evidence.research?.[provider];
|
|
8199
|
+
if (observed) return observed;
|
|
8200
|
+
const packageId = provider === "grep" ? "grep-adapter" : provider;
|
|
8201
|
+
const packageSource = PI_PACKAGE_SPECS.find(
|
|
8202
|
+
({ id }) => id === packageId
|
|
8203
|
+
)?.source;
|
|
8204
|
+
const packageReady = targets.some(
|
|
8205
|
+
({ path, state }) => path === packageSource && state === "installed"
|
|
8206
|
+
);
|
|
8207
|
+
const configReady = provider !== "grep" || targets.some(
|
|
8208
|
+
({ kind, label, state }) => kind === "config" && label?.includes("grep.app") && state === "installed"
|
|
8209
|
+
);
|
|
8210
|
+
if (!packageReady || !configReady)
|
|
8211
|
+
return {
|
|
8212
|
+
state: "drifted",
|
|
8213
|
+
basis: [
|
|
8214
|
+
"managed package or configuration preconditions are not satisfied"
|
|
8215
|
+
]
|
|
8216
|
+
};
|
|
8217
|
+
return provider === "web-access" ? {
|
|
8218
|
+
state: "unverified",
|
|
8219
|
+
basis: [
|
|
8220
|
+
"exact package is installed; no live provider request was observed"
|
|
8221
|
+
]
|
|
8222
|
+
} : {
|
|
8223
|
+
state: "ready",
|
|
8224
|
+
basis: [
|
|
8225
|
+
"managed package and configuration preconditions are satisfied; no live probe was requested"
|
|
8226
|
+
]
|
|
8227
|
+
};
|
|
8228
|
+
}
|
|
8229
|
+
function runtimeTarget(provider, evidence) {
|
|
8230
|
+
const managedState = evidence.state === "ready" ? "installed" : evidence.state === "credential-required" ? "missing" : evidence.state === "drifted" ? "drift" : "unknown";
|
|
8231
|
+
return {
|
|
8232
|
+
kind: "surface",
|
|
8233
|
+
label: `${RESEARCH_PROVIDER_LABEL[provider]} runtime availability`,
|
|
8234
|
+
state: managedState,
|
|
8235
|
+
expected: "ready",
|
|
8236
|
+
observed: evidence.state,
|
|
8237
|
+
description: evidence.basis.join("; ")
|
|
8238
|
+
};
|
|
8239
|
+
}
|
|
8240
|
+
function runtimeDiagnostic(provider, evidence) {
|
|
8241
|
+
if (evidence.state === "ready") return void 0;
|
|
8242
|
+
const code = `pi-${provider}-runtime-${evidence.state}`;
|
|
8243
|
+
const severity = evidence.state === "failed" ? "critical" : evidence.state === "credential-required" || evidence.state === "unverified" ? "minor" : "important";
|
|
8244
|
+
return warning3(
|
|
8245
|
+
`${RESEARCH_PROVIDER_LABEL[provider]} runtime is ${evidence.state}: ${evidence.basis.join("; ")}.`,
|
|
8246
|
+
code,
|
|
8247
|
+
severity
|
|
8248
|
+
);
|
|
8249
|
+
}
|
|
8250
|
+
function statusFromPlan(plan, context2, evidence = {}) {
|
|
8251
|
+
const targets = plan.items.filter((item) => item.kind !== "preflight").map((item) => ({
|
|
8252
|
+
kind: item.kind === "package" ? "package" : item.kind === "agent" ? "file" : item.kind === "mcp" ? "config" : "file",
|
|
8253
|
+
path: item.target,
|
|
8254
|
+
label: item.description,
|
|
8255
|
+
state: item.kind === "package" ? "unknown" : existsSync15(item.target) ? item.content === void 0 || readFileSync12(item.target, "utf8") === item.content ? "installed" : "drift" : "missing",
|
|
8256
|
+
expected: item.kind === "package" ? item.target : "attributable managed content",
|
|
8257
|
+
description: item.kind === "package" ? "Exact installed-package evidence; this does not prove live tool availability." : void 0
|
|
8258
|
+
}));
|
|
8259
|
+
const execute = context2.piCommandExecutor ?? ((command, args) => {
|
|
8260
|
+
const result = spawnSync7(command, [...args], {
|
|
8261
|
+
encoding: "utf8",
|
|
8262
|
+
timeout: 5e3,
|
|
8263
|
+
env: {
|
|
8264
|
+
...process.env,
|
|
8265
|
+
...context2.env,
|
|
8266
|
+
...context2.homeDir ? { HOME: context2.homeDir, USERPROFILE: context2.homeDir } : {},
|
|
8267
|
+
PI_CODING_AGENT_DIR: plan.paths.piRoot
|
|
8268
|
+
}
|
|
8269
|
+
});
|
|
8270
|
+
return {
|
|
8271
|
+
exitCode: result.status,
|
|
8272
|
+
stdout: result.stdout ?? "",
|
|
8273
|
+
stderr: result.stderr ?? "",
|
|
8274
|
+
error: result.error
|
|
8275
|
+
};
|
|
8276
|
+
});
|
|
8277
|
+
const node = execute("node", ["--version"]);
|
|
8278
|
+
const pi = execute("pi", ["--version"]);
|
|
8279
|
+
const packages = execute("pi", ["list", "--no-approve"]);
|
|
8280
|
+
targets.unshift(
|
|
8281
|
+
{
|
|
8282
|
+
kind: "surface",
|
|
8283
|
+
label: "Node.js runtime",
|
|
8284
|
+
state: node.exitCode !== 0 ? "missing" : isVersionAtLeast(node.stdout.trim(), PI_NODE_MINIMUM) ? "installed" : "drift",
|
|
8285
|
+
expected: `>=${PI_NODE_MINIMUM}`,
|
|
8286
|
+
observed: node.stdout.trim() || node.stderr.trim() || "unavailable"
|
|
8287
|
+
},
|
|
8288
|
+
{
|
|
8289
|
+
kind: "package",
|
|
8290
|
+
label: "Pi runtime",
|
|
8291
|
+
state: pi.exitCode !== 0 ? "missing" : isVersionAtLeast(pi.stdout.trim(), PI_MINIMUM_VERSION) ? "installed" : "drift",
|
|
8292
|
+
expected: `>=${PI_MINIMUM_VERSION}`,
|
|
8293
|
+
observed: pi.stdout.trim() || pi.stderr.trim() || "unavailable"
|
|
8294
|
+
}
|
|
8295
|
+
);
|
|
8296
|
+
for (const target of targets.filter(
|
|
8297
|
+
(candidate) => candidate.kind === "package" && candidate.path?.startsWith("npm:")
|
|
8298
|
+
)) {
|
|
8299
|
+
const packageName = target.path?.replace(/^npm:/, "").replace(/@[^@]+$/, "");
|
|
8300
|
+
target.state = packages.exitCode !== 0 ? "unknown" : hasExactInstalledPiPackage(packages.stdout, target.path ?? "") ? "installed" : packageName && packages.stdout.includes(packageName) ? "drift" : "missing";
|
|
8301
|
+
target.observed = packages.exitCode === 0 ? target.state : packages.stderr.trim() || "pi list unavailable";
|
|
8302
|
+
}
|
|
8303
|
+
const receiptOptions = context2.installLedgerOptions ?? {
|
|
8304
|
+
env: context2.env,
|
|
8305
|
+
homeDir: context2.homeDir
|
|
8306
|
+
};
|
|
8307
|
+
const receipt = readPiPackageReceipt(receiptOptions);
|
|
8308
|
+
const configuredPackages = packages.exitCode === 0 ? parsePiPackageList(packages.stdout) : [];
|
|
8309
|
+
const firstPartyPackages = getPiFirstPartyPackages(
|
|
8310
|
+
configuredPackages,
|
|
8311
|
+
receipt.status === "valid" ? receipt.receipt.source : void 0
|
|
8312
|
+
);
|
|
8313
|
+
const ownership = classifyPiPackageOwnership({
|
|
8314
|
+
receipt,
|
|
8315
|
+
globalPackages: firstPartyPackages.filter(({ scope }) => scope === "user"),
|
|
8316
|
+
projectPackages: firstPartyPackages.filter(
|
|
8317
|
+
({ scope }) => scope === "project"
|
|
8318
|
+
)
|
|
8319
|
+
});
|
|
8320
|
+
targets.push({
|
|
8321
|
+
kind: "file",
|
|
8322
|
+
path: getPiPackageReceiptPath(receiptOptions),
|
|
8323
|
+
label: "First-party Pi ownership receipt",
|
|
8324
|
+
state: receipt.status === "valid" ? "installed" : receipt.status === "missing" ? "missing" : "drift",
|
|
8325
|
+
observed: receipt.status
|
|
8326
|
+
});
|
|
8327
|
+
targets.push({
|
|
8328
|
+
kind: "surface",
|
|
8329
|
+
label: "First-party Pi package ownership",
|
|
8330
|
+
state: ownership.state === "owned-current" ? "installed" : ownership.state === "missing" || ownership.state === "owned-missing" ? "missing" : "drift",
|
|
8331
|
+
expected: "owned-current",
|
|
8332
|
+
observed: ownership.state,
|
|
8333
|
+
description: ownership.reason
|
|
8334
|
+
});
|
|
8335
|
+
let validatedPackageRoot;
|
|
8336
|
+
if (receipt.status === "valid") {
|
|
8337
|
+
const configuredCandidates = firstPartyPackages.filter(
|
|
8338
|
+
({ scope, source }) => scope === "user" && source === receipt.receipt.source
|
|
8339
|
+
);
|
|
8340
|
+
const configuredPackage = configuredCandidates[0];
|
|
8341
|
+
const configuredPath = configuredPackage?.installedPath;
|
|
8342
|
+
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 && isAbsolute4(configuredPath) && localPathMatches;
|
|
8344
|
+
if (configured) validatedPackageRoot = configuredPath;
|
|
8345
|
+
targets.push({
|
|
8346
|
+
kind: "surface",
|
|
8347
|
+
path: configuredPath,
|
|
8348
|
+
label: "First-party Pi configured source",
|
|
8349
|
+
state: configured ? "installed" : configuredCandidates.length === 0 ? "missing" : "drift",
|
|
8350
|
+
expected: `${receipt.receipt.source} -> ${receipt.receipt.installSource}`,
|
|
8351
|
+
observed: configuredPackage ? `${configuredPackage.source} -> ${configuredPath ?? "unavailable"}` : "unavailable"
|
|
8352
|
+
});
|
|
8353
|
+
const extensionPath = configuredPath ? join12(configuredPath, "dist", "pi.js") : void 0;
|
|
8354
|
+
const manifestPath = configuredPath ? join12(configuredPath, "package.json") : void 0;
|
|
8355
|
+
let receiptMatches = false;
|
|
8356
|
+
if (extensionPath && manifestPath && existsSync15(extensionPath) && existsSync15(manifestPath))
|
|
8357
|
+
receiptMatches = createHash5("sha256").update(readFileSync12(extensionPath)).digest("hex") === receipt.receipt.extensionSha256 && createHash5("sha256").update(readFileSync12(manifestPath)).digest("hex") === receipt.receipt.manifestSha256;
|
|
8358
|
+
targets.push({
|
|
8359
|
+
kind: "surface",
|
|
8360
|
+
path: extensionPath,
|
|
8361
|
+
label: "Native Pi extension loadability",
|
|
8362
|
+
state: receiptMatches ? "installed" : extensionPath ? "drift" : "unknown",
|
|
8363
|
+
observed: receiptMatches ? "loadable" : "unavailable"
|
|
8364
|
+
});
|
|
8365
|
+
targets.push({
|
|
8366
|
+
kind: "surface",
|
|
8367
|
+
label: "Native root observation",
|
|
8368
|
+
state: configured && receiptMatches ? "installed" : "drift",
|
|
8369
|
+
expected: "observed-at-install",
|
|
8370
|
+
observed: configured && receiptMatches ? "observed-at-install (receipt-bound)" : "unobserved"
|
|
8371
|
+
});
|
|
8372
|
+
}
|
|
8373
|
+
const ownedSkills = validatedPackageRoot ? (() => {
|
|
8374
|
+
const inspection = inspectPiPackageSkills({
|
|
8375
|
+
packageRoot: validatedPackageRoot
|
|
8376
|
+
});
|
|
8377
|
+
return inspection.skills.map((skill) => {
|
|
8378
|
+
const issue = inspection.issues.find(({ name }) => name === skill.name) ?? (!inspection.success ? {
|
|
8379
|
+
state: "drift",
|
|
8380
|
+
message: inspection.error ?? "Package-declared Pi skill inspection is unavailable."
|
|
8381
|
+
} : void 0);
|
|
8382
|
+
return {
|
|
8383
|
+
kind: "skill",
|
|
8384
|
+
path: join12(skill.destinationPath, "SKILL.md"),
|
|
8385
|
+
label: `Pi package-declared skill: ${skill.name}`,
|
|
8386
|
+
state: issue?.state ?? "installed",
|
|
8387
|
+
...issue ? { observed: issue.message } : {}
|
|
8388
|
+
};
|
|
8389
|
+
});
|
|
8390
|
+
})() : THOTH_OWNED_SKILL_NAMES.map((name) => ({
|
|
8391
|
+
kind: "skill",
|
|
8392
|
+
label: `Pi package-declared skill: ${name}`,
|
|
8393
|
+
state: "unknown",
|
|
8394
|
+
observed: "package ownership unavailable"
|
|
8395
|
+
}));
|
|
8396
|
+
targets.push(...ownedSkills);
|
|
8397
|
+
targets.push(
|
|
8398
|
+
getCliManagedInstallVersionTarget("pi", {
|
|
8399
|
+
env: context2.env,
|
|
8400
|
+
homeDir: context2.homeDir
|
|
8401
|
+
})
|
|
8402
|
+
);
|
|
8403
|
+
const fileStates = targets.map((target) => target.state);
|
|
8404
|
+
const state = !plan.ready ? "unknown" : fileStates.includes("missing") ? "missing" : fileStates.includes("drift") ? "drift" : fileStates.includes("unknown") ? "unknown" : "installed";
|
|
8405
|
+
const runtimeEvidence = ["context7", "web-access", "grep"].map(
|
|
8406
|
+
(provider) => ({
|
|
8407
|
+
provider,
|
|
8408
|
+
evidence: researchRuntimeState(provider, targets, evidence)
|
|
8409
|
+
})
|
|
8410
|
+
);
|
|
8411
|
+
targets.push(
|
|
8412
|
+
...runtimeEvidence.map(
|
|
8413
|
+
({ provider, evidence: runtime }) => runtimeTarget(provider, runtime)
|
|
8414
|
+
)
|
|
8415
|
+
);
|
|
8416
|
+
const diagnostics = [
|
|
8417
|
+
...plan.blockers.map(
|
|
8418
|
+
(message) => warning3(message, "pi-preflight-blocked", "critical")
|
|
8419
|
+
),
|
|
8420
|
+
...plan.diagnostics.map(
|
|
8421
|
+
(message) => warning3(message, "pi-resource-shadowing")
|
|
8422
|
+
),
|
|
8423
|
+
...ownership.state === "configured-unowned" || ownership.state === "conflicting" ? [
|
|
8424
|
+
warning3(
|
|
8425
|
+
ownership.reason ?? `First-party Pi package ownership is ${ownership.state}; remove it manually or restore a matching receipt before Update.`,
|
|
8426
|
+
`pi-first-party-${ownership.state}`,
|
|
8427
|
+
"critical"
|
|
8428
|
+
)
|
|
8429
|
+
] : ownership.state === "owned-missing" ? [
|
|
8430
|
+
warning3(
|
|
8431
|
+
"The receipt-owned first-party Pi package is not configured; Install or Update may repair it.",
|
|
8432
|
+
"pi-first-party-owned-missing"
|
|
8433
|
+
)
|
|
8434
|
+
] : [],
|
|
8435
|
+
...runtimeEvidence.map(
|
|
8436
|
+
({ provider, evidence: runtime }) => runtimeDiagnostic(provider, runtime)
|
|
8437
|
+
).filter((item) => item !== void 0)
|
|
8438
|
+
];
|
|
8439
|
+
return {
|
|
8440
|
+
harness: "pi",
|
|
8441
|
+
displayName: PI_DISPLAY_NAME,
|
|
8442
|
+
state,
|
|
8443
|
+
summary: state === "installed" ? "Pi managed resources are present; native package and remote-provider health remain separately owned." : "Pi setup is missing, drifted, shadowed, or blocked; rerun the complete install after resolving diagnostics.",
|
|
8444
|
+
targets,
|
|
8445
|
+
diagnostics,
|
|
8446
|
+
actions: piActions,
|
|
8447
|
+
disclaimers: disclaimers()
|
|
8448
|
+
};
|
|
8449
|
+
}
|
|
8450
|
+
function contextPlan(context2, dryRun = true) {
|
|
8451
|
+
const build = context2.buildPiSetupPlan ?? buildPiSetupPlan;
|
|
8452
|
+
const version = resolveExecutingPackageVersion();
|
|
8453
|
+
return build({
|
|
8454
|
+
dryRun,
|
|
8455
|
+
cwd: context2.cwd,
|
|
8456
|
+
env: context2.env,
|
|
8457
|
+
homeDir: context2.homeDir,
|
|
8458
|
+
commandExecutor: context2.piCommandExecutor,
|
|
8459
|
+
packageRoot: context2.packageRoot ?? (version.ok ? version.packageRoot : void 0),
|
|
8460
|
+
expectedVersion: version.ok ? version.version : void 0,
|
|
8461
|
+
receiptOptions: context2.installLedgerOptions
|
|
8462
|
+
});
|
|
8463
|
+
}
|
|
8464
|
+
function getPiStatus(context2 = { cwd: process.cwd() }, evidence = {}) {
|
|
8465
|
+
const status = statusFromPlan(contextPlan(context2), context2, evidence);
|
|
8466
|
+
return {
|
|
8467
|
+
...status,
|
|
8468
|
+
providerCapability: classifyProviderCapabilityEvidence(evidence)
|
|
8469
|
+
};
|
|
8470
|
+
}
|
|
8471
|
+
var planSources = /* @__PURE__ */ new WeakMap();
|
|
8472
|
+
function configuredPackageRootFromStatus(status) {
|
|
8473
|
+
const ownership = status.targets.find(
|
|
8474
|
+
({ label }) => label === "First-party Pi package ownership"
|
|
8475
|
+
);
|
|
8476
|
+
const configured = status.targets.find(
|
|
8477
|
+
({ label }) => label === "First-party Pi configured source"
|
|
8478
|
+
);
|
|
8479
|
+
return ownership?.observed === "owned-current" && configured?.state === "installed" && configured.path && isAbsolute4(configured.path) ? configured.path : void 0;
|
|
8480
|
+
}
|
|
8481
|
+
function piPlan(action, context2) {
|
|
8482
|
+
const setup = contextPlan(context2);
|
|
8483
|
+
const status = statusFromPlan(setup, context2);
|
|
8484
|
+
const configuredPackageRoot = configuredPackageRootFromStatus(status);
|
|
8485
|
+
const version = resolveExecutingPackageVersion();
|
|
8486
|
+
const complete = action !== "sync";
|
|
8487
|
+
const items = setup.items.filter(
|
|
8488
|
+
(item) => complete || item.kind === "root" || item.kind === "agent" || item.kind === "mcp"
|
|
8489
|
+
).map((item) => ({
|
|
8490
|
+
title: item.description,
|
|
8491
|
+
target: {
|
|
8492
|
+
kind: item.kind === "package" ? "package" : item.kind === "mcp" ? "config" : "file",
|
|
8493
|
+
path: item.target,
|
|
8494
|
+
label: item.description
|
|
8495
|
+
},
|
|
8496
|
+
preview: item.command ? `${item.command.command} ${item.command.args.join(" ")}` : `write attributable ${item.kind} surface`,
|
|
8497
|
+
backup: {
|
|
8498
|
+
required: ["root", "agent", "mcp"].includes(item.kind),
|
|
8499
|
+
strategy: "managed-backup-file"
|
|
8500
|
+
}
|
|
8501
|
+
}));
|
|
8502
|
+
items.push({
|
|
8503
|
+
title: "Inspect five package-declared Pi skills",
|
|
8504
|
+
target: { kind: "skill", label: "Pi package-declared skills" },
|
|
8505
|
+
preview: configuredPackageRoot ? getPiOwnedSkillEntries({ packageRoot: configuredPackageRoot }).map((entry) => join12(entry.destinationPath, "SKILL.md")).join(", ") : "unavailable until the first-party Pi package is receipt-validated"
|
|
8506
|
+
});
|
|
8507
|
+
if (complete) {
|
|
8508
|
+
items.push(
|
|
8509
|
+
{
|
|
8510
|
+
title: "Install four required external skills for Pi",
|
|
8511
|
+
target: { kind: "skill", label: "Pi required external skills" },
|
|
8512
|
+
preview: JSON.stringify(
|
|
8513
|
+
REQUIRED_SKILLS.map(
|
|
8514
|
+
(skill) => getRequiredSkillInstallCommand(skill, "pi")
|
|
8515
|
+
),
|
|
8516
|
+
null,
|
|
8517
|
+
2
|
|
8518
|
+
)
|
|
8519
|
+
},
|
|
8520
|
+
{
|
|
8521
|
+
title: "Run provider-owned thoth-mem setup pi",
|
|
8522
|
+
target: { kind: "surface", label: "Provider setup" },
|
|
8523
|
+
preview: (() => {
|
|
8524
|
+
const cmd = getThothMemSetupCommand("pi", true);
|
|
8525
|
+
return `${cmd.command} ${cmd.args.join(" ")}`;
|
|
8526
|
+
})()
|
|
8527
|
+
},
|
|
8528
|
+
{
|
|
8529
|
+
title: "Record completed Pi CLI install",
|
|
8530
|
+
target: {
|
|
8531
|
+
kind: "file",
|
|
8532
|
+
path: getInstallLedgerPath(
|
|
8533
|
+
context2.installLedgerOptions ?? {
|
|
8534
|
+
env: context2.env,
|
|
8535
|
+
homeDir: context2.homeDir
|
|
8536
|
+
}
|
|
8537
|
+
),
|
|
8538
|
+
label: "CLI-managed install version"
|
|
8539
|
+
},
|
|
8540
|
+
preview: version.ok ? JSON.stringify({ harness: "pi", version: version.version }) : "unavailable"
|
|
8541
|
+
}
|
|
8542
|
+
);
|
|
8543
|
+
}
|
|
8544
|
+
const ownershipState = status.targets.find(
|
|
8545
|
+
({ label }) => label === "First-party Pi package ownership"
|
|
8546
|
+
)?.observed;
|
|
8547
|
+
const ownershipBlocked = ownershipState === "configured-unowned" || ownershipState === "conflicting";
|
|
8548
|
+
const ownershipBlockers = ownershipBlocked ? [
|
|
8549
|
+
{
|
|
8550
|
+
kind: "surface",
|
|
8551
|
+
label: "First-party Pi package ownership blocker",
|
|
8552
|
+
state: "drift",
|
|
8553
|
+
observed: `${ownershipState}; remove the configured first-party package manually or restore its matching receipt before applying ${action}.`
|
|
8554
|
+
}
|
|
8555
|
+
] : [];
|
|
8556
|
+
const syncSkillsUnavailable = action === "sync" && (!configuredPackageRoot || status.targets.some(
|
|
8557
|
+
({ label, state }) => label?.startsWith("Pi package-declared skill:") && state !== "installed"
|
|
8558
|
+
));
|
|
8559
|
+
const syncSkillBlockers = syncSkillsUnavailable ? [
|
|
8560
|
+
{
|
|
8561
|
+
kind: "skill",
|
|
8562
|
+
label: "Pi package-declared skill evidence blocker",
|
|
8563
|
+
state: "unknown",
|
|
8564
|
+
observed: "A receipt-validated configured Pi package root with all five manifest-owned skills is required before Sync."
|
|
8565
|
+
}
|
|
8566
|
+
] : [];
|
|
8567
|
+
const plan = {
|
|
8568
|
+
id: `pi-${action}-preview`,
|
|
8569
|
+
harness: "pi",
|
|
8570
|
+
action,
|
|
8571
|
+
title: `${action === "install" ? "Install" : action === "update" ? "Update" : "Sync"} Pi`,
|
|
8572
|
+
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
|
+
dryRun: true,
|
|
8574
|
+
canApply: setup.ready && (!complete || version.ok) && ownershipBlockers.length === 0 && syncSkillBlockers.length === 0,
|
|
8575
|
+
targets: status.targets,
|
|
8576
|
+
blockerTargets: [
|
|
8577
|
+
...setup.blockers.map((message) => ({
|
|
8578
|
+
kind: "surface",
|
|
8579
|
+
label: "Pi preflight blocker",
|
|
8580
|
+
state: "unknown",
|
|
8581
|
+
observed: message
|
|
8582
|
+
})),
|
|
8583
|
+
...ownershipBlockers,
|
|
8584
|
+
...syncSkillBlockers
|
|
8585
|
+
],
|
|
8586
|
+
surfaces: setup.items.filter((item) => item.kind !== "preflight").map((item) => ({
|
|
8587
|
+
id: `${item.kind}:${item.target}`,
|
|
8588
|
+
label: item.description,
|
|
8589
|
+
path: item.target
|
|
8590
|
+
})),
|
|
8591
|
+
backup: {
|
|
8592
|
+
required: true,
|
|
8593
|
+
strategy: "managed-backup-file",
|
|
8594
|
+
description: "Attributable shared files retain sibling .bak files when replaced; Pi package state remains manager-owned."
|
|
8595
|
+
},
|
|
8596
|
+
items,
|
|
8597
|
+
warnings: status.diagnostics,
|
|
8598
|
+
disclaimers: disclaimers()
|
|
8599
|
+
};
|
|
8600
|
+
if (!version.ok && complete)
|
|
8601
|
+
plan.warnings.push(
|
|
8602
|
+
warning3(
|
|
8603
|
+
version.error.message,
|
|
8604
|
+
"pi-package-version-unresolved",
|
|
8605
|
+
"critical"
|
|
8606
|
+
)
|
|
8607
|
+
);
|
|
8608
|
+
planSources.set(plan, {
|
|
8609
|
+
setup,
|
|
8610
|
+
context: context2,
|
|
8611
|
+
...version.ok ? { version: version.version } : {},
|
|
8612
|
+
...configuredPackageRoot ? { configuredPackageRoot } : {}
|
|
8613
|
+
});
|
|
8614
|
+
return plan;
|
|
8615
|
+
}
|
|
8616
|
+
function buildPiInstallPlan(context2 = { cwd: process.cwd() }) {
|
|
8617
|
+
return piPlan("install", context2);
|
|
8618
|
+
}
|
|
8619
|
+
function buildPiUpdatePlan(context2 = { cwd: process.cwd() }) {
|
|
8620
|
+
return piPlan("update", context2);
|
|
8621
|
+
}
|
|
8622
|
+
function buildPiSyncPlan(context2 = { cwd: process.cwd() }) {
|
|
8623
|
+
return piPlan("sync", context2);
|
|
8624
|
+
}
|
|
8625
|
+
function defaultPiModelRoles(context2 = { cwd: process.cwd() }) {
|
|
8626
|
+
const plan = contextPlan(context2);
|
|
8627
|
+
return getAgentPackContract().roles.filter(
|
|
8628
|
+
(role) => isPiSpecialistRole(role.name)
|
|
8629
|
+
).map((role) => {
|
|
8630
|
+
const path = plan.items.find(
|
|
8631
|
+
(item) => item.kind === "agent" && basename3(item.target) === `${piSpecialistName(role.name)}.md`
|
|
8632
|
+
)?.target;
|
|
8633
|
+
const content = path && existsSync15(path) ? readFileSync12(path, "utf8") : "";
|
|
8634
|
+
const model = /^model:\s*["']?([^"'\r\n]+)/m.exec(content)?.[1]?.trim();
|
|
8635
|
+
return {
|
|
8636
|
+
role: role.name,
|
|
8637
|
+
model: model && model !== "default" ? model : "inherit",
|
|
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
|
+
})()
|
|
8642
|
+
};
|
|
8643
|
+
});
|
|
8644
|
+
}
|
|
8645
|
+
function buildPiModelPlan(input, context2 = { cwd: process.cwd() }) {
|
|
8646
|
+
const setup = contextPlan(context2);
|
|
8647
|
+
const specialistNames = new Set(
|
|
8648
|
+
getAgentPackContract().roles.filter((role) => role.name !== "orchestrator").map((role) => role.name)
|
|
8649
|
+
);
|
|
8650
|
+
const invalid = input.harness !== "pi" || input.roles.some((role) => !specialistNames.has(role.role));
|
|
8651
|
+
const effortErrors = input.roles.map((role) => ({ role: role.role, resolution: resolvePiEffort(role) })).filter(({ resolution }) => !resolution.ok);
|
|
8652
|
+
const items = input.roles.map((role) => {
|
|
8653
|
+
const specialist = isPiSpecialistRole(role.role) ? piSpecialistName(role.role) : void 0;
|
|
8654
|
+
return {
|
|
8655
|
+
title: `Configure Pi specialist ${role.role}`,
|
|
8656
|
+
target: {
|
|
8657
|
+
kind: "file",
|
|
8658
|
+
path: specialist ? setup.items.find(
|
|
8659
|
+
(item) => item.kind === "agent" && basename3(item.target) === `${specialist}.md`
|
|
8660
|
+
)?.target : void 0,
|
|
8661
|
+
label: `Pi ${role.role} specialist`
|
|
8662
|
+
},
|
|
8663
|
+
preview: JSON.stringify({
|
|
8664
|
+
model: role.model,
|
|
8665
|
+
effort: role.effort ?? null
|
|
8666
|
+
})
|
|
8667
|
+
};
|
|
8668
|
+
});
|
|
8669
|
+
const plan = {
|
|
8670
|
+
id: "pi-model-config-preview",
|
|
8671
|
+
harness: "pi",
|
|
8672
|
+
action: "model-config",
|
|
8673
|
+
title: "Configure Pi specialist models",
|
|
8674
|
+
summary: MODEL_ROOT_LIMITATION,
|
|
8675
|
+
dryRun: true,
|
|
8676
|
+
canApply: setup.ready && !invalid && effortErrors.length === 0 && items.every((item) => Boolean(item.target.path)),
|
|
8677
|
+
targets: items.map((item) => item.target),
|
|
8678
|
+
surfaces: [],
|
|
8679
|
+
backup: { required: true, strategy: "managed-backup-file" },
|
|
8680
|
+
items,
|
|
8681
|
+
warnings: [
|
|
8682
|
+
...invalid ? [
|
|
8683
|
+
warning3(
|
|
8684
|
+
"Pi model configuration accepts only the six owned specialists and never the ambient orchestrator.",
|
|
8685
|
+
"pi-model-role-unsupported",
|
|
8686
|
+
"critical"
|
|
8687
|
+
)
|
|
8688
|
+
] : [],
|
|
8689
|
+
...effortErrors.map(
|
|
8690
|
+
({ role, resolution }) => warning3(
|
|
8691
|
+
`Pi specialist ${role}: ${resolution.ok ? "" : resolution.message}`,
|
|
8692
|
+
"pi-model-effort-unsupported",
|
|
8693
|
+
"critical"
|
|
8694
|
+
)
|
|
8695
|
+
)
|
|
8696
|
+
],
|
|
8697
|
+
disclaimers: disclaimers()
|
|
8698
|
+
};
|
|
8699
|
+
planSources.set(plan, { setup, context: context2, model: input });
|
|
8700
|
+
return plan;
|
|
8701
|
+
}
|
|
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
|
+
function applyPiPlan(plan) {
|
|
8720
|
+
const source = planSources.get(plan);
|
|
8721
|
+
const fileTargets = (paths) => paths.map((path) => ({ kind: "file", path, state: "installed" }));
|
|
8722
|
+
const reject = (message, changedTargets = []) => ({
|
|
8723
|
+
harness: "pi",
|
|
8724
|
+
action: plan.action,
|
|
8725
|
+
applied: false,
|
|
8726
|
+
summary: message,
|
|
8727
|
+
changedTargets,
|
|
8728
|
+
backups: [],
|
|
8729
|
+
warnings: [
|
|
8730
|
+
...plan.warnings,
|
|
8731
|
+
warning3(message, "pi-apply-rejected", "critical")
|
|
8732
|
+
],
|
|
8733
|
+
disclaimers: disclaimers()
|
|
8734
|
+
});
|
|
8735
|
+
if (plan.harness !== "pi" || !plan.canApply || !source)
|
|
8736
|
+
return reject(
|
|
8737
|
+
"Pi operation plan is not applicable or was not built in this process."
|
|
8738
|
+
);
|
|
8739
|
+
if (plan.action === "model-config" && source.model) {
|
|
8740
|
+
const changedTargets = [];
|
|
8741
|
+
for (const role of source.model.roles) {
|
|
8742
|
+
const target = plan.items.find((item) => item.title.endsWith(role.role))?.target.path;
|
|
8743
|
+
if (!target || !existsSync15(target))
|
|
8744
|
+
return reject(
|
|
8745
|
+
`Owned Pi specialist definition is missing: ${role.role}.`
|
|
8746
|
+
);
|
|
8747
|
+
let content = readFileSync12(target, "utf8");
|
|
8748
|
+
content = replaceFrontmatterField(content, "model", role.model);
|
|
8749
|
+
content = replaceFrontmatterField(
|
|
8750
|
+
content,
|
|
8751
|
+
"effort",
|
|
8752
|
+
role.effort?.kind === "effort" ? role.effort.value : void 0
|
|
8753
|
+
);
|
|
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
|
+
return {
|
|
8763
|
+
harness: "pi",
|
|
8764
|
+
action: plan.action,
|
|
8765
|
+
applied: true,
|
|
8766
|
+
summary: "Applied Pi specialist model configuration; ambient root model remains Pi-owned.",
|
|
8767
|
+
changedTargets,
|
|
8768
|
+
backups: [],
|
|
8769
|
+
warnings: [],
|
|
8770
|
+
disclaimers: disclaimers()
|
|
8771
|
+
};
|
|
8772
|
+
}
|
|
8773
|
+
if (plan.action === "sync") {
|
|
8774
|
+
const fresh = (source.context.buildPiSetupPlan ?? buildPiSetupPlan)({
|
|
8775
|
+
...source.setup.options,
|
|
8776
|
+
dryRun: false
|
|
8777
|
+
});
|
|
8778
|
+
if (!fresh.ready) return reject(fresh.blockers.join("\n"));
|
|
8779
|
+
const liveStatus = statusFromPlan(
|
|
8780
|
+
contextPlan(source.context),
|
|
8781
|
+
source.context
|
|
8782
|
+
);
|
|
8783
|
+
const packageRoot = configuredPackageRootFromStatus(liveStatus);
|
|
8784
|
+
if (!packageRoot)
|
|
8785
|
+
return reject(
|
|
8786
|
+
"Receipt-validated configured Pi package root is unavailable."
|
|
8787
|
+
);
|
|
8788
|
+
const packageSkills = (source.context.inspectPiPackageSkills ?? inspectPiPackageSkills)({ packageRoot });
|
|
8789
|
+
if (!packageSkills.success)
|
|
8790
|
+
return reject(
|
|
8791
|
+
packageSkills.error ?? "Pi package-declared skills are unavailable."
|
|
8792
|
+
);
|
|
8793
|
+
const changed = [];
|
|
8794
|
+
try {
|
|
8795
|
+
for (const item of fresh.items.filter(
|
|
8796
|
+
(item2) => item2.kind === "mcp" && item2.content !== void 0
|
|
8797
|
+
))
|
|
8798
|
+
if (writePiManagedText(item.target, item.content ?? ""))
|
|
8799
|
+
changed.push(item.target);
|
|
8800
|
+
} catch (error) {
|
|
8801
|
+
return reject(
|
|
8802
|
+
error instanceof Error ? error.message : String(error),
|
|
8803
|
+
fileTargets(changed)
|
|
8804
|
+
);
|
|
8805
|
+
}
|
|
8806
|
+
const resources = syncPiSpecialists({
|
|
8807
|
+
packageRoot,
|
|
8808
|
+
piRoot: fresh.paths.piRoot,
|
|
8809
|
+
projectRoots: fresh.paths.projectAgentRoots
|
|
8810
|
+
});
|
|
8811
|
+
changed.push(...resources.changed);
|
|
8812
|
+
if (!resources.success)
|
|
8813
|
+
return reject(
|
|
8814
|
+
resources.error ?? "Pi specialist sync failed.",
|
|
8815
|
+
fileTargets(changed)
|
|
8816
|
+
);
|
|
8817
|
+
const migration = migrateLegacyPiResources({
|
|
8818
|
+
packageRoot,
|
|
8819
|
+
piRoot: fresh.paths.piRoot
|
|
8820
|
+
});
|
|
8821
|
+
changed.push(...migration.changed);
|
|
8822
|
+
if (!migration.success)
|
|
8823
|
+
return reject(
|
|
8824
|
+
migration.error ?? "Pi legacy migration failed.",
|
|
8825
|
+
fileTargets(changed)
|
|
8826
|
+
);
|
|
8827
|
+
return {
|
|
8828
|
+
harness: "pi",
|
|
8829
|
+
action: plan.action,
|
|
8830
|
+
applied: true,
|
|
8831
|
+
summary: "Applied attributable Pi managed-surface sync without changing packages, external skills, provider state, or ledger.",
|
|
8832
|
+
changedTargets: fileTargets(changed),
|
|
8833
|
+
diagnosticTargets: packageSkills.skills.map((skill) => ({
|
|
8834
|
+
kind: "skill",
|
|
8835
|
+
path: join12(skill.destinationPath, "SKILL.md"),
|
|
8836
|
+
label: `Pi package-declared skill: ${skill.name}`,
|
|
8837
|
+
state: "installed"
|
|
8838
|
+
})),
|
|
8839
|
+
backups: [],
|
|
8840
|
+
warnings: [],
|
|
8841
|
+
disclaimers: disclaimers()
|
|
8842
|
+
};
|
|
8843
|
+
}
|
|
8844
|
+
if (plan.action === "install" || plan.action === "update") {
|
|
8845
|
+
const fresh = (source.context.buildPiSetupPlan ?? buildPiSetupPlan)({
|
|
8846
|
+
...source.setup.options,
|
|
8847
|
+
dryRun: false,
|
|
8848
|
+
commandExecutor: source.context.piCommandExecutor
|
|
8849
|
+
});
|
|
8850
|
+
const applied = (source.context.applyPiSetup ?? applyPiSetup)(fresh);
|
|
8851
|
+
const setupTargets = fileTargets(applied.changed);
|
|
8852
|
+
if (!applied.success)
|
|
8853
|
+
return reject(
|
|
8854
|
+
applied.error ?? "Pi package or managed-surface setup failed.",
|
|
8855
|
+
setupTargets
|
|
8856
|
+
);
|
|
8857
|
+
if (!applied.configuredPackageRoot)
|
|
8858
|
+
return reject(
|
|
8859
|
+
"Verified configured Pi package root is unavailable.",
|
|
8860
|
+
setupTargets
|
|
8861
|
+
);
|
|
8862
|
+
const packageSkills = (source.context.inspectPiPackageSkills ?? inspectPiPackageSkills)({ packageRoot: applied.configuredPackageRoot });
|
|
8863
|
+
if (!packageSkills.success)
|
|
8864
|
+
return reject(
|
|
8865
|
+
packageSkills.error ?? "Pi package-declared skills are unavailable.",
|
|
8866
|
+
setupTargets
|
|
8867
|
+
);
|
|
8868
|
+
const installSkill = source.context.installRequiredSkill ?? installRequiredSkill;
|
|
8869
|
+
const skillTargets = [];
|
|
8870
|
+
for (const skill of REQUIRED_SKILLS) {
|
|
8871
|
+
const installed = installSkill(skill, "pi", {
|
|
8872
|
+
homeDir: source.context.homeDir
|
|
8873
|
+
});
|
|
8874
|
+
if (installed.status === "failed")
|
|
8875
|
+
return reject(`Failed to install required Pi skill: ${skill.name}.`, [
|
|
8876
|
+
...setupTargets,
|
|
8877
|
+
...skillTargets
|
|
8878
|
+
]);
|
|
8879
|
+
skillTargets.push({
|
|
8880
|
+
kind: "skill",
|
|
8881
|
+
path: installed.skillPath,
|
|
8882
|
+
label: `Pi required skill: ${skill.name}`,
|
|
8883
|
+
state: "installed"
|
|
8884
|
+
});
|
|
8885
|
+
}
|
|
8886
|
+
if (!source.version)
|
|
8887
|
+
return reject("Executing thoth-agents package version is unavailable.");
|
|
8888
|
+
const finalize = source.context.finalizeHarnessInstall ?? finalizeHarnessInstall;
|
|
8889
|
+
const completion = finalize({
|
|
8890
|
+
harness: "pi",
|
|
8891
|
+
version: source.version,
|
|
8892
|
+
dryRun: false,
|
|
8893
|
+
cwd: source.context.cwd,
|
|
8894
|
+
runThothMemSetup: source.context.runThothMemSetup,
|
|
8895
|
+
ledgerOptions: source.context.installLedgerOptions ?? {
|
|
8896
|
+
env: source.context.env,
|
|
8897
|
+
homeDir: source.context.homeDir
|
|
8898
|
+
}
|
|
8899
|
+
});
|
|
8900
|
+
if (!completion.success)
|
|
8901
|
+
return reject(
|
|
8902
|
+
completion.error ?? "Pi provider setup or ledger commit failed.",
|
|
8903
|
+
[...setupTargets, ...skillTargets]
|
|
8904
|
+
);
|
|
8905
|
+
return {
|
|
8906
|
+
harness: "pi",
|
|
8907
|
+
action: plan.action,
|
|
8908
|
+
applied: true,
|
|
8909
|
+
summary: `Applied complete ordered Pi ${plan.action} including provider evidence and final ledger commit.`,
|
|
8910
|
+
changedTargets: [
|
|
8911
|
+
...setupTargets,
|
|
8912
|
+
...skillTargets,
|
|
8913
|
+
{
|
|
8914
|
+
kind: "file",
|
|
8915
|
+
path: completion.ledger.path,
|
|
8916
|
+
label: "CLI-managed install version",
|
|
8917
|
+
state: "installed"
|
|
8918
|
+
}
|
|
8919
|
+
],
|
|
8920
|
+
diagnosticTargets: packageSkills.skills.map((skill) => ({
|
|
8921
|
+
kind: "skill",
|
|
8922
|
+
path: join12(skill.destinationPath, "SKILL.md"),
|
|
8923
|
+
label: `Pi package-declared skill: ${skill.name}`,
|
|
8924
|
+
state: "installed"
|
|
8925
|
+
})),
|
|
8926
|
+
backups: [],
|
|
8927
|
+
warnings: [],
|
|
8928
|
+
disclaimers: disclaimers()
|
|
8929
|
+
};
|
|
8930
|
+
}
|
|
8931
|
+
return reject(`Unsupported Pi apply action: ${plan.action}.`);
|
|
8932
|
+
}
|
|
8933
|
+
|
|
8934
|
+
// src/cli/operations/index.ts
|
|
8935
|
+
var OPERATION_HARNESSES = {
|
|
8936
|
+
opencode: opencodeOperationAdapter,
|
|
8937
|
+
codex: codexOperationAdapter,
|
|
8938
|
+
claude: claudeCodeOperationAdapter,
|
|
8939
|
+
pi: piOperationAdapter
|
|
8940
|
+
};
|
|
8941
|
+
var SUPPORTED_OPERATION_HARNESSES = Object.keys(
|
|
8942
|
+
OPERATION_HARNESSES
|
|
8943
|
+
);
|
|
8944
|
+
function listOperationHarnesses() {
|
|
8945
|
+
return SUPPORTED_OPERATION_HARNESSES.map(
|
|
8946
|
+
(harness) => OPERATION_HARNESSES[harness]
|
|
8947
|
+
);
|
|
8948
|
+
}
|
|
8949
|
+
function getOperationHarness(harness) {
|
|
8950
|
+
return OPERATION_HARNESSES[harness];
|
|
8951
|
+
}
|
|
8952
|
+
|
|
8953
|
+
// src/cli/tui/model-catalog.ts
|
|
8954
|
+
import { execFileSync } from "child_process";
|
|
8955
|
+
|
|
8956
|
+
// src/cli/model-catalog/cache.ts
|
|
8957
|
+
import {
|
|
8958
|
+
existsSync as existsSync16,
|
|
8959
|
+
mkdirSync as mkdirSync8,
|
|
8960
|
+
readFileSync as readFileSync13,
|
|
8961
|
+
renameSync as renameSync7,
|
|
8962
|
+
unlinkSync,
|
|
8963
|
+
writeFileSync as writeFileSync8
|
|
8964
|
+
} from "fs";
|
|
8965
|
+
import { homedir as homedir7 } from "os";
|
|
8966
|
+
import { dirname as dirname10, posix, win32 } from "path";
|
|
8967
|
+
import { z as z2 } from "zod";
|
|
8968
|
+
var modelCatalogSourceSchema = z2.enum(["remote", "lkg", "manual"]);
|
|
8969
|
+
var cachedModelOptionSchema = z2.object({
|
|
8970
|
+
id: z2.string().min(1),
|
|
8971
|
+
catalogId: z2.string().min(1).optional(),
|
|
8972
|
+
label: z2.string().min(1),
|
|
8973
|
+
provider: z2.string().min(1),
|
|
8974
|
+
efforts: z2.array(z2.string().min(1)),
|
|
8975
|
+
source: modelCatalogSourceSchema
|
|
8976
|
+
}).passthrough();
|
|
8977
|
+
var modelsDevCacheSchema = z2.object({
|
|
8978
|
+
version: z2.literal(1),
|
|
8979
|
+
etag: z2.string().optional(),
|
|
8980
|
+
fetchedAt: z2.string().datetime(),
|
|
8981
|
+
catalog: z2.object({ models: z2.array(cachedModelOptionSchema) }).passthrough()
|
|
8982
|
+
}).passthrough();
|
|
8983
|
+
function resolveModelsDevCachePath(options = {}) {
|
|
8984
|
+
const platform = options.platform ?? process.platform;
|
|
8985
|
+
const env = options.env ?? process.env;
|
|
8986
|
+
const home = options.homeDir ?? homedir7();
|
|
8987
|
+
if (platform === "win32") {
|
|
8988
|
+
const path = win32;
|
|
8989
|
+
const base2 = env.LOCALAPPDATA ?? env.APPDATA ?? path.join(home, "AppData", "Local");
|
|
8990
|
+
return path.join(base2, "thoth-agents", "models-dev-v1.json");
|
|
8991
|
+
}
|
|
8992
|
+
const base = env.XDG_CACHE_HOME ?? posix.join(home, ".cache");
|
|
8993
|
+
return posix.join(base, "thoth-agents", "models-dev-v1.json");
|
|
8994
|
+
}
|
|
8995
|
+
function readModelsDevCache(cachePath) {
|
|
8996
|
+
if (!existsSync16(cachePath)) return void 0;
|
|
8997
|
+
try {
|
|
8998
|
+
const input = JSON.parse(readFileSync13(cachePath, "utf8"));
|
|
8999
|
+
const parsed = modelsDevCacheSchema.safeParse(input);
|
|
9000
|
+
return parsed.success ? parsed.data : void 0;
|
|
9001
|
+
} catch {
|
|
9002
|
+
return void 0;
|
|
9003
|
+
}
|
|
9004
|
+
}
|
|
9005
|
+
function writeModelsDevCacheAtomic(cachePath, cache) {
|
|
9006
|
+
const validated = modelsDevCacheSchema.safeParse(cache);
|
|
9007
|
+
if (!validated.success) {
|
|
9008
|
+
throw new Error(`Invalid models.dev cache: ${validated.error.message}`);
|
|
9009
|
+
}
|
|
9010
|
+
mkdirSync8(dirname10(cachePath), { recursive: true });
|
|
9011
|
+
const temporaryPath = `${cachePath}.${process.pid}.${Date.now()}.tmp`;
|
|
9012
|
+
try {
|
|
9013
|
+
writeFileSync8(
|
|
9014
|
+
temporaryPath,
|
|
9015
|
+
`${JSON.stringify(validated.data, null, 2)}
|
|
9016
|
+
`
|
|
9017
|
+
);
|
|
9018
|
+
renameSync7(temporaryPath, cachePath);
|
|
9019
|
+
} catch (error) {
|
|
9020
|
+
if (existsSync16(temporaryPath)) unlinkSync(temporaryPath);
|
|
9021
|
+
throw error;
|
|
9022
|
+
}
|
|
9023
|
+
}
|
|
9024
|
+
|
|
9025
|
+
// src/cli/model-catalog/models-dev.ts
|
|
9026
|
+
import { z as z3 } from "zod";
|
|
9027
|
+
var modelsDevModelSchema = z3.object({
|
|
9028
|
+
id: z3.string().min(1).optional(),
|
|
9029
|
+
name: z3.string().min(1).optional(),
|
|
9030
|
+
reasoning: z3.boolean().optional(),
|
|
9031
|
+
reasoning_options: z3.array(z3.unknown()).optional()
|
|
9032
|
+
}).passthrough();
|
|
9033
|
+
var modelsDevProviderSchema = z3.object({
|
|
9034
|
+
models: z3.record(z3.string(), modelsDevModelSchema)
|
|
9035
|
+
}).passthrough();
|
|
9036
|
+
var modelsDevCatalogSchema = z3.record(
|
|
9037
|
+
z3.string(),
|
|
9038
|
+
modelsDevProviderSchema
|
|
9039
|
+
);
|
|
9040
|
+
var effortOptionSchema = z3.object({
|
|
9041
|
+
type: z3.literal("effort"),
|
|
9042
|
+
values: z3.array(z3.string().min(1))
|
|
9043
|
+
}).passthrough();
|
|
9044
|
+
function normalizedCatalogId(provider, modelKey, declaredId) {
|
|
9045
|
+
const candidate = (declaredId ?? modelKey).trim().toLowerCase();
|
|
9046
|
+
return candidate.includes("/") ? candidate : `${provider}/${candidate}`;
|
|
9047
|
+
}
|
|
9048
|
+
function runtimeModelId(provider, catalogId) {
|
|
9049
|
+
const prefix = `${provider}/`;
|
|
9050
|
+
return catalogId.startsWith(prefix) ? catalogId.slice(prefix.length) : catalogId;
|
|
9051
|
+
}
|
|
9052
|
+
function effortValues(options) {
|
|
9053
|
+
const values = /* @__PURE__ */ new Set();
|
|
9054
|
+
for (const option of options ?? []) {
|
|
9055
|
+
const parsed = effortOptionSchema.safeParse(option);
|
|
9056
|
+
if (!parsed.success) continue;
|
|
9057
|
+
for (const value of parsed.data.values) {
|
|
9058
|
+
const normalized = value.trim();
|
|
9059
|
+
if (normalized) values.add(normalized);
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
return [...values];
|
|
9063
|
+
}
|
|
9064
|
+
function normalizeModelsDevCatalog(input) {
|
|
9065
|
+
const parsed = modelsDevCatalogSchema.safeParse(input);
|
|
9066
|
+
if (!parsed.success) {
|
|
9067
|
+
return {
|
|
9068
|
+
ok: false,
|
|
9069
|
+
issues: parsed.error.issues.map(
|
|
9070
|
+
(issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`
|
|
9071
|
+
)
|
|
6999
9072
|
};
|
|
7000
9073
|
}
|
|
7001
9074
|
const models = [];
|
|
@@ -7137,6 +9210,7 @@ var CODEX_DOCUMENTED_EFFORTS2 = /* @__PURE__ */ new Set([
|
|
|
7137
9210
|
"ultra"
|
|
7138
9211
|
]);
|
|
7139
9212
|
var CLAUDE_CODE_EFFORTS2 = ["low", "medium", "high", "xhigh", "max"];
|
|
9213
|
+
var PI_EFFORTS2 = new Set(PI_EFFORT_VALUES);
|
|
7140
9214
|
function parseOpenCodeModels(output) {
|
|
7141
9215
|
const seen = /* @__PURE__ */ new Set();
|
|
7142
9216
|
const options = [];
|
|
@@ -7172,6 +9246,9 @@ function writableEfforts(harness, option) {
|
|
|
7172
9246
|
(effort) => CLAUDE_CODE_EFFORTS2.includes(effort)
|
|
7173
9247
|
);
|
|
7174
9248
|
}
|
|
9249
|
+
if (harness === "pi") {
|
|
9250
|
+
return option.efforts.filter((effort) => PI_EFFORTS2.has(effort));
|
|
9251
|
+
}
|
|
7175
9252
|
return option.efforts.filter(
|
|
7176
9253
|
(effort) => resolveOpenCodeEffort({
|
|
7177
9254
|
model: option.id,
|
|
@@ -7246,6 +9323,12 @@ async function getModelOptions(harness) {
|
|
|
7246
9323
|
efforts: writableEfforts(harness, option)
|
|
7247
9324
|
}));
|
|
7248
9325
|
}
|
|
9326
|
+
if (harness === "pi") {
|
|
9327
|
+
return loaded.models.map((option) => ({
|
|
9328
|
+
...option,
|
|
9329
|
+
efforts: writableEfforts(harness, option)
|
|
9330
|
+
}));
|
|
9331
|
+
}
|
|
7249
9332
|
const catalogById = new Map(
|
|
7250
9333
|
loaded.models.map((option) => [option.catalogId ?? option.id, option])
|
|
7251
9334
|
);
|
|
@@ -7257,7 +9340,7 @@ async function getModelOptions(harness) {
|
|
|
7257
9340
|
}
|
|
7258
9341
|
|
|
7259
9342
|
// src/cli/tui/operations.ts
|
|
7260
|
-
import { existsSync as
|
|
9343
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
|
|
7261
9344
|
var context = { cwd: process.cwd() };
|
|
7262
9345
|
var codexContext = { cwd: process.cwd() };
|
|
7263
9346
|
var claudeCodeContext = {
|
|
@@ -7299,7 +9382,7 @@ function getCodexModelRoles(source = codexContext) {
|
|
|
7299
9382
|
const item = plan.items.find(
|
|
7300
9383
|
(candidate) => candidate.action === "write-role-toml" && candidate.role === role
|
|
7301
9384
|
);
|
|
7302
|
-
const content = item &&
|
|
9385
|
+
const content = item && existsSync17(item.targetPath) ? readFileSync14(item.targetPath, "utf8") : item?.content;
|
|
7303
9386
|
const effort = content ? parseRoleTomlEffort(content) : void 0;
|
|
7304
9387
|
return {
|
|
7305
9388
|
role,
|
|
@@ -7314,6 +9397,9 @@ function getCodexModelRoles(source = codexContext) {
|
|
|
7314
9397
|
function getClaudeCodeModelRoles(_source = claudeCodeContext) {
|
|
7315
9398
|
return defaultClaudeCodeModelRoles();
|
|
7316
9399
|
}
|
|
9400
|
+
function getPiModelRoles() {
|
|
9401
|
+
return defaultPiModelRoles(context);
|
|
9402
|
+
}
|
|
7317
9403
|
function readRoleField(config, role, field) {
|
|
7318
9404
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
7319
9405
|
return void 0;
|
|
@@ -7356,6 +9442,9 @@ function buildTuiModelPlan(harness, roles) {
|
|
|
7356
9442
|
claudeCodeContext
|
|
7357
9443
|
);
|
|
7358
9444
|
}
|
|
9445
|
+
if (harness === "pi") {
|
|
9446
|
+
return buildPiModelPlan({ harness, dryRun: true, roles }, context);
|
|
9447
|
+
}
|
|
7359
9448
|
return buildCodexModelPlan({ harness, dryRun: true, roles }, codexContext);
|
|
7360
9449
|
}
|
|
7361
9450
|
var defaultTuiOperations = {
|
|
@@ -7364,6 +9453,7 @@ var defaultTuiOperations = {
|
|
|
7364
9453
|
if (harness === "claude") {
|
|
7365
9454
|
return getClaudeCodeStatus(claudeCodeContext, evidence);
|
|
7366
9455
|
}
|
|
9456
|
+
if (harness === "pi") return getPiStatus(context, evidence);
|
|
7367
9457
|
return getCodexStatus(codexContext, evidence);
|
|
7368
9458
|
},
|
|
7369
9459
|
modelRoles(harness) {
|
|
@@ -7371,6 +9461,7 @@ var defaultTuiOperations = {
|
|
|
7371
9461
|
if (harness === "claude") {
|
|
7372
9462
|
return getClaudeCodeModelRoles(claudeCodeContext);
|
|
7373
9463
|
}
|
|
9464
|
+
if (harness === "pi") return getPiModelRoles();
|
|
7374
9465
|
return getCodexModelRoles(codexContext);
|
|
7375
9466
|
},
|
|
7376
9467
|
modelOptions(harness) {
|
|
@@ -7396,6 +9487,12 @@ var defaultTuiOperations = {
|
|
|
7396
9487
|
getClaudeCodeModelRoles(claudeCodeContext)
|
|
7397
9488
|
);
|
|
7398
9489
|
}
|
|
9490
|
+
if (harness === "pi") {
|
|
9491
|
+
if (action === "install") return buildPiInstallPlan(context);
|
|
9492
|
+
if (action === "update") return buildPiUpdatePlan(context);
|
|
9493
|
+
if (action === "sync") return buildPiSyncPlan(context);
|
|
9494
|
+
return buildTuiModelPlan(harness, getPiModelRoles());
|
|
9495
|
+
}
|
|
7399
9496
|
if (action === "install") return buildCodexInstallPlan(codexContext);
|
|
7400
9497
|
if (action === "update") return buildCodexUpdatePlan(codexContext);
|
|
7401
9498
|
if (action === "sync") return buildCodexSyncPlan(codexContext);
|
|
@@ -7407,6 +9504,7 @@ var defaultTuiOperations = {
|
|
|
7407
9504
|
apply(plan) {
|
|
7408
9505
|
if (plan.harness === "opencode") return applyOpenCodePlan(plan);
|
|
7409
9506
|
if (plan.harness === "claude") return applyClaudeCodePlan(plan);
|
|
9507
|
+
if (plan.harness === "pi") return applyPiPlan(plan);
|
|
7410
9508
|
return applyCodexPlan(plan);
|
|
7411
9509
|
}
|
|
7412
9510
|
};
|
|
@@ -7425,8 +9523,13 @@ export {
|
|
|
7425
9523
|
buildCodexPluginSetupPlan,
|
|
7426
9524
|
applyCodexPluginSetup,
|
|
7427
9525
|
formatCodexPluginSetupPlan,
|
|
9526
|
+
recordHarnessInstallCompletion,
|
|
7428
9527
|
finalizeHarnessInstall,
|
|
7429
9528
|
syncOpenCodeOwnedSkills,
|
|
9529
|
+
inspectPiPackageSkills,
|
|
9530
|
+
buildPiSetupPlan,
|
|
9531
|
+
formatPiSetupPlan,
|
|
9532
|
+
applyPiSetup,
|
|
7430
9533
|
REQUIRED_SKILLS,
|
|
7431
9534
|
getRequiredSkillInstallCommand,
|
|
7432
9535
|
installRequiredSkill,
|
|
@@ -7446,6 +9549,12 @@ export {
|
|
|
7446
9549
|
buildOpenCodeSyncPlan,
|
|
7447
9550
|
buildOpenCodeModelPlan,
|
|
7448
9551
|
applyOpenCodePlan,
|
|
9552
|
+
getPiStatus,
|
|
9553
|
+
buildPiUpdatePlan,
|
|
9554
|
+
buildPiSyncPlan,
|
|
9555
|
+
defaultPiModelRoles,
|
|
9556
|
+
buildPiModelPlan,
|
|
9557
|
+
applyPiPlan,
|
|
7449
9558
|
SUPPORTED_OPERATION_HARNESSES,
|
|
7450
9559
|
listOperationHarnesses,
|
|
7451
9560
|
getOperationHarness,
|
|
@@ -7453,6 +9562,7 @@ export {
|
|
|
7453
9562
|
getModelOptions,
|
|
7454
9563
|
getCodexModelRoles,
|
|
7455
9564
|
getClaudeCodeModelRoles,
|
|
9565
|
+
getPiModelRoles,
|
|
7456
9566
|
getOpenCodeModelRoles,
|
|
7457
9567
|
defaultTuiOperations
|
|
7458
9568
|
};
|