thoth-agents 0.3.11 → 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 +182 -389
- package/dist/agents/prompt-dialects.d.ts +4 -1
- package/dist/agents/prompt-sections.d.ts +2 -1
- package/dist/{chunk-CB3EBA7H.js → chunk-5EBXGMOK.js} +1 -1
- package/dist/chunk-62L3ORXN.js +30 -0
- package/dist/{chunk-GIUU6FNE.js → chunk-Q6SDM4KP.js} +208 -106
- package/dist/chunk-SECDI3TL.js +312 -0
- package/dist/{chunk-ZEAAWBF2.js → chunk-WJ2YMM2E.js} +2877 -290
- package/dist/cli/codex-plugin-install.d.ts +16 -2
- package/dist/cli/index.js +188 -15
- 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/core/agent-pack.d.ts +26 -6
- package/dist/harness/core/sdd.d.ts +27 -15
- package/dist/harness/pi-specialists.d.ts +6 -0
- package/dist/harness/types.d.ts +2 -2
- package/dist/harness/writers/codex-plugin-package.d.ts +1 -0
- 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 +17 -6
- 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/.thoth-agents-plugin-assets.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/agents/oracle.md +3 -3
- package/plugin/agents/orchestrator.md +35 -30
- package/plugin/skills/plan-reviewer/SKILL.md +54 -15
- package/plugin/skills/thoth-archive/SKILL.md +5 -0
- package/plugin/skills/thoth-constitution/SKILL.md +5 -0
- package/plugin/skills/thoth-constitution/templates/constitution.md +19 -2
- package/plugin/skills/thoth-init/SKILL.md +5 -0
- package/plugin/skills/thoth-sdd/SKILL.md +35 -10
- package/plugin/skills/thoth-sdd/references/phases/implement.md +13 -2
- package/plugin/skills/thoth-sdd/references/phases/tasks.md +23 -2
- package/plugin/skills/thoth-sdd/references/phases/verify.md +7 -5
- package/plugin/skills/thoth-sdd/scripts/validate.mjs +263 -30
- package/plugin/skills/thoth-sdd/templates/tasks.md +18 -2
- package/skills/README.md +2 -1
- package/skills/plan-reviewer/SKILL.md +54 -15
- package/skills/thoth-archive/SKILL.md +5 -0
- package/skills/thoth-constitution/SKILL.md +5 -0
- package/skills/thoth-constitution/templates/constitution.md +19 -2
- package/skills/thoth-init/SKILL.md +5 -0
- package/skills/thoth-sdd/SKILL.md +35 -10
- package/skills/thoth-sdd/references/phases/implement.md +13 -2
- package/skills/thoth-sdd/references/phases/tasks.md +23 -2
- package/skills/thoth-sdd/references/phases/verify.md +7 -5
- package/skills/thoth-sdd/scripts/validate.mjs +263 -30
- package/skills/thoth-sdd/templates/tasks.md +18 -2
- package/.agents/plugins/marketplace.json +0 -20
- package/.claude-plugin/marketplace.json +0 -21
|
@@ -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 = [];
|
|
@@ -638,8 +617,8 @@ function stripProviderMcp(content) {
|
|
|
638
617
|
}
|
|
639
618
|
function pluginRootReference(pathValue) {
|
|
640
619
|
const normalized = normalizePath(pathValue);
|
|
641
|
-
const
|
|
642
|
-
return `./${
|
|
620
|
+
const relative2 = normalized.slice(".codex-plugin/".length);
|
|
621
|
+
return `./${relative2}`;
|
|
643
622
|
}
|
|
644
623
|
function sha256(content) {
|
|
645
624
|
return `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
@@ -692,7 +671,7 @@ function codexHookPackageDiagnostic(code, message, severity = "warning") {
|
|
|
692
671
|
function normalizeHookDefinition(hook) {
|
|
693
672
|
return {
|
|
694
673
|
command: hook.handler.command,
|
|
695
|
-
|
|
674
|
+
type: "command"
|
|
696
675
|
};
|
|
697
676
|
}
|
|
698
677
|
function renderHookDefinitions(hookDefinitions) {
|
|
@@ -705,7 +684,10 @@ function renderHookDefinitions(hookDefinitions) {
|
|
|
705
684
|
continue;
|
|
706
685
|
}
|
|
707
686
|
const eventHooks = hooksByEvent.get(validation.event) ?? [];
|
|
708
|
-
eventHooks.push(
|
|
687
|
+
eventHooks.push({
|
|
688
|
+
...hook.matcher !== void 0 ? { matcher: hook.matcher } : {},
|
|
689
|
+
hooks: [normalizeHookDefinition(hook)]
|
|
690
|
+
});
|
|
709
691
|
hooksByEvent.set(validation.event, eventHooks);
|
|
710
692
|
}
|
|
711
693
|
if (hooksByEvent.size === 0) {
|
|
@@ -718,11 +700,11 @@ function renderHookDefinitions(hookDefinitions) {
|
|
|
718
700
|
);
|
|
719
701
|
return { diagnostics };
|
|
720
702
|
}
|
|
721
|
-
const content = stableJson(
|
|
722
|
-
Object.fromEntries(
|
|
703
|
+
const content = stableJson({
|
|
704
|
+
hooks: Object.fromEntries(
|
|
723
705
|
[...hooksByEvent.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([event, hooks]) => [event, hooks.map(stableValue)])
|
|
724
706
|
)
|
|
725
|
-
);
|
|
707
|
+
});
|
|
726
708
|
return { content, diagnostics };
|
|
727
709
|
}
|
|
728
710
|
function parseRawHookContent(content) {
|
|
@@ -753,37 +735,66 @@ function parseRawHookContent(content) {
|
|
|
753
735
|
]
|
|
754
736
|
};
|
|
755
737
|
}
|
|
756
|
-
|
|
738
|
+
if (!isRecord(parsed.hooks)) {
|
|
739
|
+
return {
|
|
740
|
+
hookDefinitions,
|
|
741
|
+
diagnostics: [
|
|
742
|
+
codexHookPackageDiagnostic(
|
|
743
|
+
"codex.plugin.hooks.invalid_shape",
|
|
744
|
+
'Skipping Codex plugin hooks asset because hooks.json must contain a nested "hooks" object.'
|
|
745
|
+
)
|
|
746
|
+
]
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
for (const [event, definitions] of Object.entries(parsed.hooks)) {
|
|
757
750
|
if (!Array.isArray(definitions)) {
|
|
758
751
|
diagnostics.push(
|
|
759
752
|
codexHookPackageDiagnostic(
|
|
760
753
|
"codex.plugin.hooks.invalid_shape",
|
|
761
|
-
`Skipping Codex plugin hook event "${event}" because its value is not an array of
|
|
754
|
+
`Skipping Codex plugin hook event "${event}" because its value is not an array of matcher groups.`
|
|
762
755
|
)
|
|
763
756
|
);
|
|
764
757
|
continue;
|
|
765
758
|
}
|
|
766
|
-
for (const
|
|
767
|
-
if (!isRecord(
|
|
759
|
+
for (const group of definitions) {
|
|
760
|
+
if (!isRecord(group) || !Array.isArray(group.hooks)) {
|
|
768
761
|
diagnostics.push(
|
|
769
762
|
codexHookPackageDiagnostic(
|
|
770
763
|
"codex.plugin.hooks.invalid_shape",
|
|
771
|
-
`Skipping Codex plugin hook event "${event}" entry because it is not
|
|
764
|
+
`Skipping Codex plugin hook event "${event}" entry because it is not a matcher group with a hooks array.`
|
|
772
765
|
)
|
|
773
766
|
);
|
|
774
767
|
continue;
|
|
775
768
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
769
|
+
if (group.matcher !== void 0 && typeof group.matcher !== "string") {
|
|
770
|
+
diagnostics.push(
|
|
771
|
+
codexHookPackageDiagnostic(
|
|
772
|
+
"codex.plugin.hooks.invalid_shape",
|
|
773
|
+
`Skipping Codex plugin hook event "${event}" matcher group because matcher is not a string.`
|
|
774
|
+
)
|
|
775
|
+
);
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
for (const definition of group.hooks) {
|
|
779
|
+
if (!isRecord(definition)) {
|
|
780
|
+
diagnostics.push(
|
|
781
|
+
codexHookPackageDiagnostic(
|
|
782
|
+
"codex.plugin.hooks.invalid_shape",
|
|
783
|
+
`Skipping Codex plugin hook event "${event}" handler because it is not an object.`
|
|
784
|
+
)
|
|
785
|
+
);
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
hookDefinitions.push({
|
|
789
|
+
event,
|
|
790
|
+
...typeof group.matcher === "string" ? { matcher: group.matcher } : {},
|
|
791
|
+
handler: {
|
|
792
|
+
type: typeof definition.type === "string" ? definition.type : "command",
|
|
793
|
+
command: definition.command,
|
|
794
|
+
async: definition.async
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
}
|
|
787
798
|
}
|
|
788
799
|
}
|
|
789
800
|
return { hookDefinitions, diagnostics };
|
|
@@ -921,9 +932,9 @@ function stripProviderMcpValues(surfaceId, values) {
|
|
|
921
932
|
const { thoth_mem: _provider, ...mcpServers } = servers;
|
|
922
933
|
return { ...values, mcp_servers: mcpServers };
|
|
923
934
|
}
|
|
924
|
-
function fieldOrder(fields,
|
|
935
|
+
function fieldOrder(fields, keys2) {
|
|
925
936
|
const orderedRoots = fields.map((field) => field.split(".")[0]);
|
|
926
|
-
return [...
|
|
937
|
+
return [...keys2].sort((left, right) => {
|
|
927
938
|
const leftIndex = orderedRoots.indexOf(left);
|
|
928
939
|
const rightIndex = orderedRoots.indexOf(right);
|
|
929
940
|
if (leftIndex !== -1 || rightIndex !== -1) {
|
|
@@ -1763,7 +1774,7 @@ function buildCodexSetupPlan(config) {
|
|
|
1763
1774
|
configPath: targets.configPath,
|
|
1764
1775
|
pluginId: config.pluginId,
|
|
1765
1776
|
diagnostics: [
|
|
1766
|
-
"The combined install flow runs `codex plugin marketplace add EremesNG/thoth-
|
|
1777
|
+
"The combined install flow runs `codex plugin marketplace add https://github.com/EremesNG/thoth-plugins.git` and `codex plugin add thoth-agents@thoth-plugins` through the native manager; restart Codex, then use /plugins to review its state.",
|
|
1767
1778
|
"The CLI does not copy a plugin into ~/.codex/plugins or merge ~/.agents/plugins/marketplace.json directly; Codex owns marketplace snapshots and installed plugin cache state.",
|
|
1768
1779
|
"The /hooks surface may show hooks from independently installed plugins; this thoth-agents setup plan does not install provider hooks.",
|
|
1769
1780
|
"Codex Default mode user-input requests require features.default_mode_request_user_input = true and use the request_user_input tool; other modes may not expose it.",
|
|
@@ -2139,11 +2150,18 @@ var CLAUDE_CODE_ROLE_NAMES = [
|
|
|
2139
2150
|
];
|
|
2140
2151
|
|
|
2141
2152
|
// src/cli/claude-code-install.ts
|
|
2142
|
-
var
|
|
2143
|
-
var
|
|
2144
|
-
var
|
|
2145
|
-
var
|
|
2146
|
-
var
|
|
2153
|
+
var PLUGIN_NAME = "thoth-agents";
|
|
2154
|
+
var MARKETPLACE_NAME = "thoth-plugins";
|
|
2155
|
+
var MARKETPLACE_SOURCE = "https://github.com/EremesNG/thoth-plugins.git";
|
|
2156
|
+
var MARKETPLACE_INSTALL_SOURCE = MARKETPLACE_SOURCE;
|
|
2157
|
+
var PLUGIN_ID = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
2158
|
+
var LEGACY_MARKETPLACE_SOURCE = "https://github.com/EremesNG/thoth-agents.git";
|
|
2159
|
+
var LEGACY_MARKETPLACE_NAMES = [PLUGIN_NAME, `${PLUGIN_NAME}-claude`];
|
|
2160
|
+
var LEGACY_PLUGIN_IDS = LEGACY_MARKETPLACE_NAMES.map(
|
|
2161
|
+
(name) => `${PLUGIN_NAME}@${name}`
|
|
2162
|
+
);
|
|
2163
|
+
var MARKETPLACE_TARGET = `claude://marketplaces/${MARKETPLACE_NAME}`;
|
|
2164
|
+
var PLUGIN_TARGET = `claude://plugins/${PLUGIN_ID}`;
|
|
2147
2165
|
function defaultCommandExecutor(command, args, options) {
|
|
2148
2166
|
const result = spawnSync(command, [...args], {
|
|
2149
2167
|
cwd: options.cwd,
|
|
@@ -2171,12 +2189,26 @@ function parseJsonRecords(content) {
|
|
|
2171
2189
|
}
|
|
2172
2190
|
}
|
|
2173
2191
|
function isCanonicalMarketplace(entry) {
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2192
|
+
return isMarketplaceIdentity(entry, MARKETPLACE_NAME);
|
|
2193
|
+
}
|
|
2194
|
+
function normalizeMarketplaceSource(value) {
|
|
2195
|
+
return value.trim().replace(/#.*$/u, "").replace(/^git@github\.com:/i, "").replace(/^(?:https?|ssh):\/\/(?:git@)?github\.com\//i, "").replace(/^github\.com\//i, "").replace(/\/?\.git\/?$/i, "").replace(/\/$/u, "").toLowerCase();
|
|
2196
|
+
}
|
|
2197
|
+
function marketplaceSources(entry) {
|
|
2198
|
+
const sources = [entry.repo, entry.source];
|
|
2199
|
+
if (isRecord3(entry.marketplaceSource)) {
|
|
2200
|
+
sources.push(entry.marketplaceSource.repo, entry.marketplaceSource.source);
|
|
2177
2201
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2202
|
+
return sources.filter(
|
|
2203
|
+
(source) => typeof source === "string"
|
|
2204
|
+
);
|
|
2205
|
+
}
|
|
2206
|
+
function isMarketplaceIdentity(entry, name, source = MARKETPLACE_SOURCE) {
|
|
2207
|
+
if (entry.name !== name) return false;
|
|
2208
|
+
const canonical = normalizeMarketplaceSource(source);
|
|
2209
|
+
return marketplaceSources(entry).some(
|
|
2210
|
+
(source2) => normalizeMarketplaceSource(source2) === canonical
|
|
2211
|
+
);
|
|
2180
2212
|
}
|
|
2181
2213
|
function inspectClaudeManager(executor, cwd, scope) {
|
|
2182
2214
|
const marketplaceResult = executor(
|
|
@@ -2212,6 +2244,19 @@ function inspectClaudeManager(executor, cwd, scope) {
|
|
|
2212
2244
|
diagnostics
|
|
2213
2245
|
};
|
|
2214
2246
|
}
|
|
2247
|
+
const legacyMarketplace = LEGACY_MARKETPLACE_NAMES.some(
|
|
2248
|
+
(name) => marketplaces.some(
|
|
2249
|
+
(entry) => isMarketplaceIdentity(entry, name, LEGACY_MARKETPLACE_SOURCE)
|
|
2250
|
+
)
|
|
2251
|
+
);
|
|
2252
|
+
const hasLegacyState = legacyMarketplace || plugins.some(
|
|
2253
|
+
(entry) => typeof entry.id === "string" && LEGACY_PLUGIN_IDS.includes(entry.id)
|
|
2254
|
+
);
|
|
2255
|
+
if (hasLegacyState) {
|
|
2256
|
+
diagnostics.push(
|
|
2257
|
+
"Legacy Claude thoth-agents marketplace or plugin state was detected and preserved; the host-specific identity will be managed independently."
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2215
2260
|
const namedMarketplaces = marketplaces.filter(
|
|
2216
2261
|
(entry) => entry.name === MARKETPLACE_NAME
|
|
2217
2262
|
);
|
|
@@ -2221,7 +2266,7 @@ function inspectClaudeManager(executor, cwd, scope) {
|
|
|
2221
2266
|
}
|
|
2222
2267
|
if (marketplace === "conflict") {
|
|
2223
2268
|
diagnostics.push(
|
|
2224
|
-
"A Claude marketplace named thoth-
|
|
2269
|
+
"A Claude marketplace named thoth-plugins is registered from a different source; resolve it through Claude Code before retrying."
|
|
2225
2270
|
);
|
|
2226
2271
|
}
|
|
2227
2272
|
const matchingPlugins = plugins.filter(
|
|
@@ -2246,7 +2291,7 @@ function commandItem(action, scope, projectRoot) {
|
|
|
2246
2291
|
kind: "native-marketplace",
|
|
2247
2292
|
action,
|
|
2248
2293
|
targetPath: MARKETPLACE_TARGET,
|
|
2249
|
-
description: "Register the
|
|
2294
|
+
description: "Register the central Thoth Claude marketplace.",
|
|
2250
2295
|
requiresBackup: false,
|
|
2251
2296
|
command: {
|
|
2252
2297
|
executable: "claude",
|
|
@@ -2254,7 +2299,7 @@ function commandItem(action, scope, projectRoot) {
|
|
|
2254
2299
|
"plugin",
|
|
2255
2300
|
"marketplace",
|
|
2256
2301
|
"add",
|
|
2257
|
-
|
|
2302
|
+
MARKETPLACE_INSTALL_SOURCE,
|
|
2258
2303
|
"--scope",
|
|
2259
2304
|
scope
|
|
2260
2305
|
],
|
|
@@ -2328,7 +2373,7 @@ function buildClaudeCodeSetupPlan(config) {
|
|
|
2328
2373
|
commandExecutor,
|
|
2329
2374
|
diagnostics: [
|
|
2330
2375
|
...inspection.diagnostics,
|
|
2331
|
-
"Claude Code installs thoth-agents from
|
|
2376
|
+
"Claude Code installs thoth-agents from the central thoth-plugins marketplace, while the thoth-agents repository remains the pinned plugin source.",
|
|
2332
2377
|
"Restart Claude Code or run /reload-plugins after installation; use /plugin to inspect marketplace and plugin state.",
|
|
2333
2378
|
"Provider capability is owned by the external provider and is not established by this thoth-agents setup plan."
|
|
2334
2379
|
],
|
|
@@ -2416,11 +2461,12 @@ var INSTALL_LEDGER_SCHEMA_VERSION = 1;
|
|
|
2416
2461
|
var INSTALL_HARNESSES = /* @__PURE__ */ new Set([
|
|
2417
2462
|
"opencode",
|
|
2418
2463
|
"codex",
|
|
2419
|
-
"claude"
|
|
2464
|
+
"claude",
|
|
2465
|
+
"pi"
|
|
2420
2466
|
]);
|
|
2421
|
-
function hasOnlyKeys(value,
|
|
2467
|
+
function hasOnlyKeys(value, keys2) {
|
|
2422
2468
|
const actual = Object.keys(value);
|
|
2423
|
-
return actual.length ===
|
|
2469
|
+
return actual.length === keys2.length && actual.every((key) => keys2.includes(key));
|
|
2424
2470
|
}
|
|
2425
2471
|
function parseInstallLedger(serialized) {
|
|
2426
2472
|
let parsed;
|
|
@@ -2612,8 +2658,6 @@ function getThothMemSetupCommand(harness, dryRun, options = {}) {
|
|
|
2612
2658
|
"thoth-mem@latest",
|
|
2613
2659
|
"setup",
|
|
2614
2660
|
harness,
|
|
2615
|
-
"--scope",
|
|
2616
|
-
"global",
|
|
2617
2661
|
...dryRun ? ["--plan"] : [],
|
|
2618
2662
|
"--json"
|
|
2619
2663
|
],
|
|
@@ -2691,26 +2735,11 @@ function runThothMemSetup(options) {
|
|
|
2691
2735
|
function isConsistentProviderSuccess(result, harness) {
|
|
2692
2736
|
return result.success && result.evidenceValid && result.status === "complete" && result.exitCode === 0 && result.harness === harness;
|
|
2693
2737
|
}
|
|
2694
|
-
function
|
|
2738
|
+
function recordHarnessInstallCompletion(options) {
|
|
2695
2739
|
const ledgerPath = getInstallLedgerPath(options.ledgerOptions);
|
|
2696
|
-
const setup = options.runThothMemSetup ?? runThothMemSetup;
|
|
2697
|
-
const provider = setup({
|
|
2698
|
-
harness: options.harness,
|
|
2699
|
-
dryRun: options.dryRun,
|
|
2700
|
-
cwd: options.cwd
|
|
2701
|
-
});
|
|
2702
|
-
if (!isConsistentProviderSuccess(provider, options.harness)) {
|
|
2703
|
-
return {
|
|
2704
|
-
success: false,
|
|
2705
|
-
provider,
|
|
2706
|
-
ledger: { status: "not-attempted", path: ledgerPath },
|
|
2707
|
-
error: provider.error ?? "thoth-mem setup did not return consistent complete evidence."
|
|
2708
|
-
};
|
|
2709
|
-
}
|
|
2710
2740
|
if (options.dryRun) {
|
|
2711
2741
|
return {
|
|
2712
2742
|
success: true,
|
|
2713
|
-
provider,
|
|
2714
2743
|
ledger: { status: "planned", path: ledgerPath }
|
|
2715
2744
|
};
|
|
2716
2745
|
}
|
|
@@ -2723,7 +2752,6 @@ function finalizeHarnessInstall(options) {
|
|
|
2723
2752
|
if (!ledger.success) {
|
|
2724
2753
|
return {
|
|
2725
2754
|
success: false,
|
|
2726
|
-
provider,
|
|
2727
2755
|
ledger: {
|
|
2728
2756
|
status: "failed",
|
|
2729
2757
|
path: ledger.path,
|
|
@@ -2734,7 +2762,6 @@ function finalizeHarnessInstall(options) {
|
|
|
2734
2762
|
}
|
|
2735
2763
|
return {
|
|
2736
2764
|
success: true,
|
|
2737
|
-
provider,
|
|
2738
2765
|
ledger: {
|
|
2739
2766
|
status: "recorded",
|
|
2740
2767
|
path: ledger.path,
|
|
@@ -2743,6 +2770,34 @@ function finalizeHarnessInstall(options) {
|
|
|
2743
2770
|
}
|
|
2744
2771
|
};
|
|
2745
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
|
+
}
|
|
2746
2801
|
|
|
2747
2802
|
// src/cli/skills.ts
|
|
2748
2803
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
@@ -2752,7 +2807,8 @@ import { join as join3 } from "path";
|
|
|
2752
2807
|
var SKILLS_CLI_AGENT = {
|
|
2753
2808
|
opencode: "opencode",
|
|
2754
2809
|
codex: "codex",
|
|
2755
|
-
claude: "claude-code"
|
|
2810
|
+
claude: "claude-code",
|
|
2811
|
+
pi: "pi"
|
|
2756
2812
|
};
|
|
2757
2813
|
var GLOBAL_SKILL_ROOTS = {
|
|
2758
2814
|
opencode: [
|
|
@@ -2760,7 +2816,8 @@ var GLOBAL_SKILL_ROOTS = {
|
|
|
2760
2816
|
[".agents", "skills"]
|
|
2761
2817
|
],
|
|
2762
2818
|
codex: [[".agents", "skills"]],
|
|
2763
|
-
claude: [[".claude", "skills"]]
|
|
2819
|
+
claude: [[".claude", "skills"]],
|
|
2820
|
+
pi: [[".pi", "agent", "skills"]]
|
|
2764
2821
|
};
|
|
2765
2822
|
var REQUIRED_SKILLS = [
|
|
2766
2823
|
{
|
|
@@ -2817,7 +2874,8 @@ function getRequiredSkillInstallCommand(skill, harness, options = {}) {
|
|
|
2817
2874
|
"--global",
|
|
2818
2875
|
"--agent",
|
|
2819
2876
|
SKILLS_CLI_AGENT[harness],
|
|
2820
|
-
"--yes"
|
|
2877
|
+
"--yes",
|
|
2878
|
+
...harness === "pi" ? ["--copy"] : []
|
|
2821
2879
|
],
|
|
2822
2880
|
options
|
|
2823
2881
|
);
|
|
@@ -3549,16 +3607,52 @@ function defaultClaudeCodeModelRoles() {
|
|
|
3549
3607
|
}
|
|
3550
3608
|
|
|
3551
3609
|
// src/cli/operations/codex.ts
|
|
3552
|
-
import { existsSync as existsSync8, readFileSync as
|
|
3610
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
3553
3611
|
import { basename as basename2 } from "path";
|
|
3554
3612
|
|
|
3555
3613
|
// src/cli/codex-plugin-install.ts
|
|
3556
3614
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3615
|
+
import {
|
|
3616
|
+
lstatSync,
|
|
3617
|
+
readdirSync,
|
|
3618
|
+
readFileSync as readFileSync6,
|
|
3619
|
+
realpathSync,
|
|
3620
|
+
rmSync as rmSync2
|
|
3621
|
+
} from "fs";
|
|
3622
|
+
import { isAbsolute, join as join4, relative, resolve as resolve2, sep } from "path";
|
|
3623
|
+
var PLUGIN_NAME2 = "thoth-agents";
|
|
3624
|
+
var MARKETPLACE_NAME2 = "thoth-plugins";
|
|
3625
|
+
var MARKETPLACE_SOURCE2 = "https://github.com/EremesNG/thoth-plugins.git";
|
|
3626
|
+
var PLUGIN_ID2 = `${PLUGIN_NAME2}@${MARKETPLACE_NAME2}`;
|
|
3627
|
+
var LEGACY_MARKETPLACE_SOURCE2 = "https://github.com/EremesNG/thoth-agents.git";
|
|
3628
|
+
var LEGACY_MARKETPLACE_NAMES2 = [PLUGIN_NAME2, `${PLUGIN_NAME2}-codex`];
|
|
3629
|
+
var LEGACY_PLUGIN_IDS2 = LEGACY_MARKETPLACE_NAMES2.map(
|
|
3630
|
+
(name) => `${PLUGIN_NAME2}@${name}`
|
|
3631
|
+
);
|
|
3632
|
+
var LEGACY_ROOTS = [
|
|
3633
|
+
{
|
|
3634
|
+
relativePath: "plugins/cache/thoth-agents",
|
|
3635
|
+
kind: "cache",
|
|
3636
|
+
marketplaceName: "thoth-agents"
|
|
3637
|
+
},
|
|
3638
|
+
{
|
|
3639
|
+
relativePath: "plugins/cache/thoth-agents-codex",
|
|
3640
|
+
kind: "cache",
|
|
3641
|
+
marketplaceName: "thoth-agents-codex"
|
|
3642
|
+
},
|
|
3643
|
+
{
|
|
3644
|
+
relativePath: ".tmp/marketplaces/thoth-agents",
|
|
3645
|
+
kind: "snapshot",
|
|
3646
|
+
marketplaceName: "thoth-agents"
|
|
3647
|
+
},
|
|
3648
|
+
{
|
|
3649
|
+
relativePath: ".tmp/marketplaces/thoth-agents-codex",
|
|
3650
|
+
kind: "snapshot",
|
|
3651
|
+
marketplaceName: "thoth-agents-codex"
|
|
3652
|
+
}
|
|
3653
|
+
];
|
|
3654
|
+
var MARKETPLACE_TARGET2 = `codex://marketplaces/${MARKETPLACE_NAME2}`;
|
|
3655
|
+
var PLUGIN_TARGET2 = `codex://plugins/${PLUGIN_ID2}`;
|
|
3562
3656
|
function getCodexCommand(args, options = {}) {
|
|
3563
3657
|
if ((options.platform ?? process.platform) !== "win32") {
|
|
3564
3658
|
return { command: "codex", args: [...args] };
|
|
@@ -3596,10 +3690,153 @@ function parseManagerRecord(content) {
|
|
|
3596
3690
|
return null;
|
|
3597
3691
|
}
|
|
3598
3692
|
}
|
|
3599
|
-
function
|
|
3600
|
-
return value.trim().replace(/^git@github\.com:/i, "").replace(/^(?:https?|ssh):\/\/(?:git@)?github\.com\//i, "").replace(/^github\.com\//i, "").replace(/\/?\.git\/?$/i, "").replace(/\/$/, "").toLowerCase();
|
|
3693
|
+
function normalizeMarketplaceSource2(value) {
|
|
3694
|
+
return value.trim().replace(/#.*$/u, "").replace(/^git@github\.com:/i, "").replace(/^(?:https?|ssh):\/\/(?:git@)?github\.com\//i, "").replace(/^github\.com\//i, "").replace(/\/?\.git\/?$/i, "").replace(/\/$/, "").toLowerCase();
|
|
3601
3695
|
}
|
|
3602
|
-
function
|
|
3696
|
+
function lstatIfPresent(path) {
|
|
3697
|
+
try {
|
|
3698
|
+
return lstatSync(path);
|
|
3699
|
+
} catch (error) {
|
|
3700
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
3701
|
+
return null;
|
|
3702
|
+
}
|
|
3703
|
+
throw error;
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
function strictDescendant(base, candidate) {
|
|
3707
|
+
const suffix = relative(base, candidate);
|
|
3708
|
+
return suffix !== "" && suffix !== ".." && !suffix.startsWith(`..${sep}`) && !isAbsolute(suffix);
|
|
3709
|
+
}
|
|
3710
|
+
function parseObjectFile(path, label) {
|
|
3711
|
+
const file = lstatIfPresent(path);
|
|
3712
|
+
if (!file || file.isSymbolicLink() || !file.isFile()) {
|
|
3713
|
+
throw new Error(`${label} must be a regular non-link file`);
|
|
3714
|
+
}
|
|
3715
|
+
try {
|
|
3716
|
+
const value = JSON.parse(readFileSync6(path, "utf8"));
|
|
3717
|
+
if (!isRecord5(value)) throw new Error("not an object");
|
|
3718
|
+
return value;
|
|
3719
|
+
} catch (error) {
|
|
3720
|
+
throw new Error(
|
|
3721
|
+
`${label} is invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
3722
|
+
);
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
function validateCacheManifest(root) {
|
|
3726
|
+
const productEntries = readdirSync(root, { withFileTypes: true });
|
|
3727
|
+
if (productEntries.length === 0) return;
|
|
3728
|
+
if (productEntries.length !== 1 || productEntries[0]?.name !== PLUGIN_NAME2) {
|
|
3729
|
+
throw new Error(
|
|
3730
|
+
`Legacy cache ${root} contains state outside ${PLUGIN_NAME2}`
|
|
3731
|
+
);
|
|
3732
|
+
}
|
|
3733
|
+
const productRoot = join4(root, PLUGIN_NAME2);
|
|
3734
|
+
const product = lstatIfPresent(productRoot);
|
|
3735
|
+
if (!product || product.isSymbolicLink() || !product.isDirectory()) {
|
|
3736
|
+
throw new Error(`Legacy cache product root ${productRoot} is unsafe`);
|
|
3737
|
+
}
|
|
3738
|
+
const versions = readdirSync(productRoot, { withFileTypes: true });
|
|
3739
|
+
if (versions.length === 0) {
|
|
3740
|
+
throw new Error(`Legacy cache ${root} has no plugin manifest`);
|
|
3741
|
+
}
|
|
3742
|
+
for (const entry of versions) {
|
|
3743
|
+
const versionRoot = join4(productRoot, entry.name);
|
|
3744
|
+
const version = lstatIfPresent(versionRoot);
|
|
3745
|
+
if (!entry.isDirectory() || !version || version.isSymbolicLink() || !version.isDirectory()) {
|
|
3746
|
+
throw new Error(`Legacy cache version root ${versionRoot} is unsafe`);
|
|
3747
|
+
}
|
|
3748
|
+
const manifest = parseObjectFile(
|
|
3749
|
+
join4(versionRoot, ".codex-plugin", "plugin.json"),
|
|
3750
|
+
`Legacy cache manifest for ${versionRoot}`
|
|
3751
|
+
);
|
|
3752
|
+
if (manifest.name !== PLUGIN_NAME2) {
|
|
3753
|
+
throw new Error(
|
|
3754
|
+
`Legacy cache manifest for ${versionRoot} does not identify ${PLUGIN_NAME2}`
|
|
3755
|
+
);
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
function validateSnapshotManifest(root, marketplaceName) {
|
|
3760
|
+
if (readdirSync(root).length === 0) return;
|
|
3761
|
+
const installation = parseObjectFile(
|
|
3762
|
+
join4(root, ".codex-marketplace-install.json"),
|
|
3763
|
+
`Legacy marketplace receipt for ${marketplaceName}`
|
|
3764
|
+
);
|
|
3765
|
+
if (typeof installation.source !== "string" || normalizeMarketplaceSource2(installation.source) !== normalizeMarketplaceSource2(LEGACY_MARKETPLACE_SOURCE2)) {
|
|
3766
|
+
throw new Error(
|
|
3767
|
+
`Legacy marketplace snapshot ${marketplaceName} has conflicting provenance`
|
|
3768
|
+
);
|
|
3769
|
+
}
|
|
3770
|
+
const marketplace = parseObjectFile(
|
|
3771
|
+
join4(root, ".agents", "plugins", "marketplace.json"),
|
|
3772
|
+
`Legacy marketplace manifest for ${marketplaceName}`
|
|
3773
|
+
);
|
|
3774
|
+
const plugins = Array.isArray(marketplace.plugins) ? marketplace.plugins : [];
|
|
3775
|
+
const pluginNames = plugins.map(
|
|
3776
|
+
(plugin) => isRecord5(plugin) ? plugin.name : null
|
|
3777
|
+
);
|
|
3778
|
+
if (marketplace.name !== marketplaceName || pluginNames.length === 0 || pluginNames.some((name) => name !== PLUGIN_NAME2)) {
|
|
3779
|
+
throw new Error(
|
|
3780
|
+
`Legacy marketplace snapshot ${marketplaceName} does not identify only ${PLUGIN_NAME2}`
|
|
3781
|
+
);
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
function validateLegacyRoot(codexHome, definition, expectedRealPath) {
|
|
3785
|
+
const path = resolve2(codexHome, ...definition.relativePath.split("/"));
|
|
3786
|
+
if (!strictDescendant(codexHome, path)) {
|
|
3787
|
+
throw new Error(
|
|
3788
|
+
`Legacy cleanup target escapes CODEX_HOME: ${definition.relativePath}`
|
|
3789
|
+
);
|
|
3790
|
+
}
|
|
3791
|
+
const target = lstatIfPresent(path);
|
|
3792
|
+
if (!target) return null;
|
|
3793
|
+
const home = lstatIfPresent(codexHome);
|
|
3794
|
+
if (!home || !home.isDirectory()) {
|
|
3795
|
+
throw new Error(`Resolved CODEX_HOME is not a directory: ${codexHome}`);
|
|
3796
|
+
}
|
|
3797
|
+
let boundary = codexHome;
|
|
3798
|
+
for (const segment of definition.relativePath.split("/")) {
|
|
3799
|
+
boundary = join4(boundary, segment);
|
|
3800
|
+
const value = lstatIfPresent(boundary);
|
|
3801
|
+
if (!value || value.isSymbolicLink() || !value.isDirectory()) {
|
|
3802
|
+
throw new Error(
|
|
3803
|
+
`Legacy cleanup boundary is not a real directory: ${boundary}`
|
|
3804
|
+
);
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
const realHome = realpathSync(codexHome);
|
|
3808
|
+
const realPath = realpathSync(path);
|
|
3809
|
+
if (!strictDescendant(realHome, realPath)) {
|
|
3810
|
+
throw new Error(
|
|
3811
|
+
`Legacy cleanup target resolves outside CODEX_HOME: ${definition.relativePath}`
|
|
3812
|
+
);
|
|
3813
|
+
}
|
|
3814
|
+
if (expectedRealPath && realPath !== expectedRealPath) {
|
|
3815
|
+
throw new Error(
|
|
3816
|
+
`Legacy cleanup target changed after preflight: ${definition.relativePath}`
|
|
3817
|
+
);
|
|
3818
|
+
}
|
|
3819
|
+
if (definition.kind === "cache") validateCacheManifest(path);
|
|
3820
|
+
else validateSnapshotManifest(path, definition.marketplaceName);
|
|
3821
|
+
return {
|
|
3822
|
+
kind: definition.kind,
|
|
3823
|
+
marketplaceName: definition.marketplaceName,
|
|
3824
|
+
path,
|
|
3825
|
+
realPath,
|
|
3826
|
+
relativePath: definition.relativePath
|
|
3827
|
+
};
|
|
3828
|
+
}
|
|
3829
|
+
function preflightLegacyRoots(codexHomeInput) {
|
|
3830
|
+
if (typeof codexHomeInput !== "string" || codexHomeInput.trim() === "") {
|
|
3831
|
+
throw new Error("CODEX_HOME could not be resolved");
|
|
3832
|
+
}
|
|
3833
|
+
const codexHome = resolve2(codexHomeInput);
|
|
3834
|
+
const roots = LEGACY_ROOTS.map(
|
|
3835
|
+
(definition) => validateLegacyRoot(codexHome, definition)
|
|
3836
|
+
).filter((root) => root !== null);
|
|
3837
|
+
return { codexHome, roots };
|
|
3838
|
+
}
|
|
3839
|
+
function marketplaceSources2(entry) {
|
|
3603
3840
|
const sources = [entry.repo, entry.source];
|
|
3604
3841
|
if (isRecord5(entry.marketplaceSource)) {
|
|
3605
3842
|
sources.push(entry.marketplaceSource.repo, entry.marketplaceSource.source);
|
|
@@ -3609,13 +3846,16 @@ function marketplaceSources(entry) {
|
|
|
3609
3846
|
);
|
|
3610
3847
|
}
|
|
3611
3848
|
function isCanonicalMarketplace2(entry) {
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3849
|
+
return isMarketplaceIdentity2(entry, MARKETPLACE_NAME2);
|
|
3850
|
+
}
|
|
3851
|
+
function isMarketplaceIdentity2(entry, name, source = MARKETPLACE_SOURCE2) {
|
|
3852
|
+
if (entry.name !== name) return false;
|
|
3853
|
+
const canonical = normalizeMarketplaceSource2(source);
|
|
3854
|
+
return marketplaceSources2(entry).some(
|
|
3855
|
+
(source2) => normalizeMarketplaceSource2(source2) === canonical
|
|
3616
3856
|
);
|
|
3617
3857
|
}
|
|
3618
|
-
function inspectCodexManager(executor, cwd) {
|
|
3858
|
+
function inspectCodexManager(executor, cwd, expectedVersion) {
|
|
3619
3859
|
const marketplaceResult = executor(
|
|
3620
3860
|
"codex",
|
|
3621
3861
|
["plugin", "marketplace", "list", "--json"],
|
|
@@ -3635,6 +3875,9 @@ function inspectCodexManager(executor, cwd) {
|
|
|
3635
3875
|
ready: false,
|
|
3636
3876
|
marketplace: "unknown",
|
|
3637
3877
|
plugin: "unknown",
|
|
3878
|
+
legacyPluginIds: [],
|
|
3879
|
+
legacyMarketplaceNames: [],
|
|
3880
|
+
legacyMarketplaceConflicts: [],
|
|
3638
3881
|
diagnostics
|
|
3639
3882
|
};
|
|
3640
3883
|
}
|
|
@@ -3650,9 +3893,35 @@ function inspectCodexManager(executor, cwd) {
|
|
|
3650
3893
|
ready: false,
|
|
3651
3894
|
marketplace: "unknown",
|
|
3652
3895
|
plugin: "unknown",
|
|
3896
|
+
legacyPluginIds: [],
|
|
3897
|
+
legacyMarketplaceNames: [],
|
|
3898
|
+
legacyMarketplaceConflicts: [],
|
|
3653
3899
|
diagnostics
|
|
3654
3900
|
};
|
|
3655
3901
|
}
|
|
3902
|
+
const legacyMarketplaceNames = LEGACY_MARKETPLACE_NAMES2.filter(
|
|
3903
|
+
(name) => marketplaces.some(
|
|
3904
|
+
(entry) => isMarketplaceIdentity2(entry, name, LEGACY_MARKETPLACE_SOURCE2)
|
|
3905
|
+
)
|
|
3906
|
+
);
|
|
3907
|
+
const legacyMarketplaceConflicts = LEGACY_MARKETPLACE_NAMES2.filter(
|
|
3908
|
+
(name) => marketplaces.some(
|
|
3909
|
+
(entry) => entry.name === name && !isMarketplaceIdentity2(entry, name, LEGACY_MARKETPLACE_SOURCE2)
|
|
3910
|
+
)
|
|
3911
|
+
);
|
|
3912
|
+
const legacyPluginIds = LEGACY_PLUGIN_IDS2.filter(
|
|
3913
|
+
(pluginId) => installedPlugins.some((entry) => entry.pluginId === pluginId)
|
|
3914
|
+
);
|
|
3915
|
+
if (legacyPluginIds.length > 0 || legacyMarketplaceNames.length > 0) {
|
|
3916
|
+
diagnostics.push(
|
|
3917
|
+
"Legacy Codex thoth-agents state was detected; cleanup is planned only after the central plugin is verified."
|
|
3918
|
+
);
|
|
3919
|
+
}
|
|
3920
|
+
if (legacyMarketplaceConflicts.length > 0) {
|
|
3921
|
+
diagnostics.push(
|
|
3922
|
+
`Legacy Codex marketplace provenance conflicts were found for ${legacyMarketplaceConflicts.join(", ")}; no manager mutation will be attempted.`
|
|
3923
|
+
);
|
|
3924
|
+
}
|
|
3656
3925
|
const namedMarketplaces = marketplaces.filter(
|
|
3657
3926
|
(entry) => entry.name === MARKETPLACE_NAME2
|
|
3658
3927
|
);
|
|
@@ -3662,31 +3931,81 @@ function inspectCodexManager(executor, cwd) {
|
|
|
3662
3931
|
}
|
|
3663
3932
|
if (marketplace === "conflict") {
|
|
3664
3933
|
diagnostics.push(
|
|
3665
|
-
"A Codex marketplace named thoth-
|
|
3934
|
+
"A Codex marketplace named thoth-plugins is registered from a different source; resolve it through Codex before retrying."
|
|
3666
3935
|
);
|
|
3667
3936
|
}
|
|
3668
3937
|
const matchingPlugins = installedPlugins.filter(
|
|
3669
3938
|
(entry) => entry.pluginId === PLUGIN_ID2
|
|
3670
3939
|
);
|
|
3671
3940
|
let plugin = "absent";
|
|
3672
|
-
if (matchingPlugins.some(
|
|
3941
|
+
if (matchingPlugins.some(
|
|
3942
|
+
(entry) => entry.enabled === true && entry.version === expectedVersion
|
|
3943
|
+
)) {
|
|
3673
3944
|
plugin = "installed";
|
|
3945
|
+
} else if (matchingPlugins.some((entry) => entry.enabled === true)) {
|
|
3946
|
+
plugin = "outdated";
|
|
3674
3947
|
} else if (matchingPlugins.length > 0) {
|
|
3675
3948
|
plugin = "disabled";
|
|
3676
3949
|
}
|
|
3677
3950
|
return {
|
|
3678
|
-
ready: marketplace !== "conflict",
|
|
3951
|
+
ready: marketplace !== "conflict" && legacyMarketplaceConflicts.length === 0,
|
|
3679
3952
|
marketplace,
|
|
3680
3953
|
plugin,
|
|
3954
|
+
legacyPluginIds,
|
|
3955
|
+
legacyMarketplaceNames,
|
|
3956
|
+
legacyMarketplaceConflicts,
|
|
3681
3957
|
diagnostics
|
|
3682
3958
|
};
|
|
3683
3959
|
}
|
|
3960
|
+
function centralPluginVerified(inspection) {
|
|
3961
|
+
return inspection.ready && inspection.marketplace === "installed" && inspection.plugin === "installed";
|
|
3962
|
+
}
|
|
3963
|
+
function legacyManagerClean(inspection) {
|
|
3964
|
+
return inspection.legacyPluginIds.length === 0 && inspection.legacyMarketplaceNames.length === 0;
|
|
3965
|
+
}
|
|
3966
|
+
function legacyRootsAbsent(codexHome) {
|
|
3967
|
+
return LEGACY_ROOTS.every(
|
|
3968
|
+
({ relativePath }) => !lstatIfPresent(resolve2(codexHome, ...relativePath.split("/")))
|
|
3969
|
+
);
|
|
3970
|
+
}
|
|
3971
|
+
function legacyPluginCommandItem(pluginId, projectRoot) {
|
|
3972
|
+
return {
|
|
3973
|
+
action: "remove-legacy-plugin",
|
|
3974
|
+
targetPath: `codex://plugins/${pluginId}`,
|
|
3975
|
+
description: `Remove the registered legacy Codex plugin ${pluginId}.`,
|
|
3976
|
+
command: {
|
|
3977
|
+
executable: "codex",
|
|
3978
|
+
args: ["plugin", "remove", pluginId, "--json"],
|
|
3979
|
+
cwd: projectRoot
|
|
3980
|
+
}
|
|
3981
|
+
};
|
|
3982
|
+
}
|
|
3983
|
+
function legacyMarketplaceCommandItem(marketplaceName, projectRoot) {
|
|
3984
|
+
return {
|
|
3985
|
+
action: "remove-legacy-marketplace",
|
|
3986
|
+
targetPath: `codex://marketplaces/${marketplaceName}`,
|
|
3987
|
+
description: `Remove the registered legacy Codex marketplace ${marketplaceName}.`,
|
|
3988
|
+
command: {
|
|
3989
|
+
executable: "codex",
|
|
3990
|
+
args: ["plugin", "marketplace", "remove", marketplaceName, "--json"],
|
|
3991
|
+
cwd: projectRoot
|
|
3992
|
+
}
|
|
3993
|
+
};
|
|
3994
|
+
}
|
|
3995
|
+
function legacyRootPlanItem(root) {
|
|
3996
|
+
return {
|
|
3997
|
+
action: "remove-legacy-root",
|
|
3998
|
+
targetPath: root.path,
|
|
3999
|
+
description: `Remove the preflight-approved legacy Codex root ${root.relativePath}.`,
|
|
4000
|
+
cleanupRoot: root
|
|
4001
|
+
};
|
|
4002
|
+
}
|
|
3684
4003
|
function commandItem2(action, projectRoot) {
|
|
3685
4004
|
if (action === "register-marketplace") {
|
|
3686
4005
|
return {
|
|
3687
4006
|
action,
|
|
3688
4007
|
targetPath: MARKETPLACE_TARGET2,
|
|
3689
|
-
description: "Register the
|
|
4008
|
+
description: "Register the central Thoth Codex marketplace.",
|
|
3690
4009
|
command: {
|
|
3691
4010
|
executable: "codex",
|
|
3692
4011
|
args: ["plugin", "marketplace", "add", MARKETPLACE_SOURCE2, "--json"],
|
|
@@ -3707,25 +4026,54 @@ function commandItem2(action, projectRoot) {
|
|
|
3707
4026
|
}
|
|
3708
4027
|
function buildCodexPluginSetupPlan(config) {
|
|
3709
4028
|
const commandExecutor = config.commandExecutor ?? defaultCommandExecutor3;
|
|
3710
|
-
const inspection = inspectCodexManager(
|
|
3711
|
-
|
|
4029
|
+
const inspection = inspectCodexManager(
|
|
4030
|
+
commandExecutor,
|
|
4031
|
+
config.projectRoot,
|
|
4032
|
+
config.expectedVersion
|
|
4033
|
+
);
|
|
4034
|
+
let cleanupPreflight;
|
|
4035
|
+
let cleanupPreflightError;
|
|
3712
4036
|
if (inspection.ready) {
|
|
4037
|
+
try {
|
|
4038
|
+
cleanupPreflight = preflightLegacyRoots(
|
|
4039
|
+
getCodexHome({ codexHome: config.codexHome, homeDir: config.homeDir })
|
|
4040
|
+
);
|
|
4041
|
+
} catch (error) {
|
|
4042
|
+
cleanupPreflightError = `Legacy cleanup preflight failed: ${error instanceof Error ? error.message : String(error)}. No manager mutation will be attempted.`;
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
const ready = inspection.ready && cleanupPreflightError === void 0;
|
|
4046
|
+
const items = [];
|
|
4047
|
+
if (ready) {
|
|
3713
4048
|
if (inspection.marketplace === "absent") {
|
|
3714
4049
|
items.push(commandItem2("register-marketplace", config.projectRoot));
|
|
3715
4050
|
}
|
|
3716
|
-
if (inspection.plugin === "absent" || inspection.plugin === "disabled") {
|
|
4051
|
+
if (inspection.plugin === "absent" || inspection.plugin === "disabled" || inspection.plugin === "outdated") {
|
|
3717
4052
|
items.push(commandItem2("install-plugin", config.projectRoot));
|
|
3718
4053
|
}
|
|
4054
|
+
items.push(
|
|
4055
|
+
...inspection.legacyPluginIds.map(
|
|
4056
|
+
(pluginId) => legacyPluginCommandItem(pluginId, config.projectRoot)
|
|
4057
|
+
),
|
|
4058
|
+
...inspection.legacyMarketplaceNames.map(
|
|
4059
|
+
(marketplaceName) => legacyMarketplaceCommandItem(marketplaceName, config.projectRoot)
|
|
4060
|
+
),
|
|
4061
|
+
...cleanupPreflight?.roots.map(legacyRootPlanItem) ?? []
|
|
4062
|
+
);
|
|
3719
4063
|
}
|
|
3720
4064
|
return {
|
|
4065
|
+
codexHome: cleanupPreflight?.codexHome ?? getCodexHome({ codexHome: config.codexHome, homeDir: config.homeDir }),
|
|
3721
4066
|
dryRun: config.dryRun === true,
|
|
4067
|
+
expectedVersion: config.expectedVersion,
|
|
3722
4068
|
items,
|
|
3723
|
-
ready
|
|
4069
|
+
ready,
|
|
3724
4070
|
projectRoot: config.projectRoot,
|
|
3725
4071
|
commandExecutor,
|
|
3726
4072
|
diagnostics: [
|
|
3727
4073
|
...inspection.diagnostics,
|
|
3728
|
-
|
|
4074
|
+
...cleanupPreflightError ? [cleanupPreflightError] : [],
|
|
4075
|
+
"Codex installs thoth-agents from the central thoth-plugins marketplace through its native plugin manager, while the thoth-agents repository remains the pinned plugin source.",
|
|
4076
|
+
"Close every Codex process before applying legacy cleanup; process-name detection is not a portable or race-free safety boundary.",
|
|
3729
4077
|
"Restart Codex after installation; use /plugins to inspect marketplace and plugin state."
|
|
3730
4078
|
]
|
|
3731
4079
|
};
|
|
@@ -3749,12 +4097,28 @@ function applyCodexPluginSetup(plan) {
|
|
|
3749
4097
|
};
|
|
3750
4098
|
}
|
|
3751
4099
|
if (plan.dryRun) return { success: true, changed, diagnostics };
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
4100
|
+
const installationItems = plan.items.filter(
|
|
4101
|
+
(item) => item.action === "register-marketplace" || item.action === "install-plugin"
|
|
4102
|
+
);
|
|
4103
|
+
const cleanupItems = plan.items.filter(
|
|
4104
|
+
(item) => item.action === "remove-legacy-plugin" || item.action === "remove-legacy-marketplace"
|
|
4105
|
+
);
|
|
4106
|
+
const rootItems = plan.items.filter(
|
|
4107
|
+
(item) => item.action === "remove-legacy-root"
|
|
4108
|
+
);
|
|
4109
|
+
for (const item of installationItems) {
|
|
4110
|
+
const command = item.command;
|
|
4111
|
+
if (!command) {
|
|
4112
|
+
return {
|
|
4113
|
+
success: false,
|
|
4114
|
+
changed,
|
|
4115
|
+
diagnostics,
|
|
4116
|
+
error: `Codex plugin setup plan item ${item.action} has no command.`
|
|
4117
|
+
};
|
|
4118
|
+
}
|
|
4119
|
+
const result = plan.commandExecutor(command.executable, command.args, {
|
|
4120
|
+
cwd: command.cwd
|
|
4121
|
+
});
|
|
3758
4122
|
if (result.exitCode !== 0) {
|
|
3759
4123
|
return {
|
|
3760
4124
|
success: false,
|
|
@@ -3767,10 +4131,11 @@ function applyCodexPluginSetup(plan) {
|
|
|
3767
4131
|
}
|
|
3768
4132
|
const inspection = inspectCodexManager(
|
|
3769
4133
|
plan.commandExecutor,
|
|
3770
|
-
plan.projectRoot
|
|
4134
|
+
plan.projectRoot,
|
|
4135
|
+
plan.expectedVersion
|
|
3771
4136
|
);
|
|
3772
4137
|
diagnostics.push(...inspection.diagnostics);
|
|
3773
|
-
if (inspection
|
|
4138
|
+
if (!centralPluginVerified(inspection)) {
|
|
3774
4139
|
return {
|
|
3775
4140
|
success: false,
|
|
3776
4141
|
changed,
|
|
@@ -3778,25 +4143,126 @@ function applyCodexPluginSetup(plan) {
|
|
|
3778
4143
|
error: "Codex did not verify the expected marketplace and enabled plugin after mutation."
|
|
3779
4144
|
};
|
|
3780
4145
|
}
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
}
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
4146
|
+
for (const item of cleanupItems) {
|
|
4147
|
+
const command = item.command;
|
|
4148
|
+
if (!command) {
|
|
4149
|
+
return {
|
|
4150
|
+
success: false,
|
|
4151
|
+
changed,
|
|
4152
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4153
|
+
error: `Codex plugin cleanup plan item ${item.action} has no command.`
|
|
4154
|
+
};
|
|
4155
|
+
}
|
|
4156
|
+
const result = plan.commandExecutor(command.executable, command.args, {
|
|
4157
|
+
cwd: command.cwd
|
|
4158
|
+
});
|
|
4159
|
+
if (result.exitCode !== 0) {
|
|
4160
|
+
return {
|
|
4161
|
+
success: false,
|
|
4162
|
+
changed,
|
|
4163
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4164
|
+
error: `${boundedCommandFailure2(item, result)} Central ${PLUGIN_ID2} remains installed; close Codex and retry.`
|
|
4165
|
+
};
|
|
4166
|
+
}
|
|
4167
|
+
changed.push(item.targetPath);
|
|
4168
|
+
}
|
|
4169
|
+
const managerCleanupInspection = inspectCodexManager(
|
|
4170
|
+
plan.commandExecutor,
|
|
4171
|
+
plan.projectRoot,
|
|
4172
|
+
plan.expectedVersion
|
|
4173
|
+
);
|
|
4174
|
+
diagnostics.push(...managerCleanupInspection.diagnostics);
|
|
4175
|
+
if (!centralPluginVerified(managerCleanupInspection) || !legacyManagerClean(managerCleanupInspection)) {
|
|
4176
|
+
return {
|
|
4177
|
+
success: false,
|
|
4178
|
+
changed,
|
|
4179
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4180
|
+
error: `Central ${PLUGIN_ID2} remains installed, but exact legacy manager cleanup did not converge; close Codex and retry.`
|
|
4181
|
+
};
|
|
4182
|
+
}
|
|
4183
|
+
for (const item of rootItems) {
|
|
4184
|
+
const root = item.cleanupRoot;
|
|
4185
|
+
const definition = root ? LEGACY_ROOTS.find(
|
|
4186
|
+
(candidate) => candidate.relativePath === root.relativePath
|
|
4187
|
+
) : void 0;
|
|
4188
|
+
if (!root || !definition) {
|
|
4189
|
+
return {
|
|
4190
|
+
success: false,
|
|
4191
|
+
changed,
|
|
4192
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4193
|
+
error: `Central ${PLUGIN_ID2} remains installed, but the fixed legacy cleanup plan is invalid; close Codex and retry.`
|
|
4194
|
+
};
|
|
4195
|
+
}
|
|
4196
|
+
const exactPath = resolve2(
|
|
4197
|
+
plan.codexHome,
|
|
4198
|
+
...definition.relativePath.split("/")
|
|
4199
|
+
);
|
|
4200
|
+
if (resolve2(root.path) !== exactPath || resolve2(item.targetPath) !== exactPath || root.kind !== definition.kind || root.marketplaceName !== definition.marketplaceName) {
|
|
4201
|
+
return {
|
|
4202
|
+
success: false,
|
|
4203
|
+
changed,
|
|
4204
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4205
|
+
error: `Central ${PLUGIN_ID2} remains installed, but the fixed legacy cleanup target changed; close Codex and retry.`
|
|
4206
|
+
};
|
|
4207
|
+
}
|
|
4208
|
+
if (!lstatIfPresent(exactPath)) continue;
|
|
4209
|
+
try {
|
|
4210
|
+
const validated = validateLegacyRoot(
|
|
4211
|
+
plan.codexHome,
|
|
4212
|
+
definition,
|
|
4213
|
+
root.realPath
|
|
4214
|
+
);
|
|
4215
|
+
if (!validated) continue;
|
|
4216
|
+
rmSync2(validated.path, { recursive: true, force: false });
|
|
4217
|
+
if (lstatIfPresent(validated.path)) {
|
|
4218
|
+
throw new Error(
|
|
4219
|
+
`Legacy cleanup target still exists: ${root.relativePath}`
|
|
4220
|
+
);
|
|
4221
|
+
}
|
|
4222
|
+
changed.push(item.targetPath);
|
|
4223
|
+
} catch (error) {
|
|
4224
|
+
const detail = (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").slice(0, 240);
|
|
4225
|
+
return {
|
|
4226
|
+
success: false,
|
|
4227
|
+
changed,
|
|
4228
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4229
|
+
error: `Central ${PLUGIN_ID2} remains installed; legacy root cleanup failed${detail ? `: ${detail}` : ""}. Close Codex and retry.`
|
|
4230
|
+
};
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
const finalInspection = inspectCodexManager(
|
|
4234
|
+
plan.commandExecutor,
|
|
4235
|
+
plan.projectRoot,
|
|
4236
|
+
plan.expectedVersion
|
|
4237
|
+
);
|
|
4238
|
+
diagnostics.push(...finalInspection.diagnostics);
|
|
4239
|
+
if (!centralPluginVerified(finalInspection) || !legacyManagerClean(finalInspection) || !legacyRootsAbsent(plan.codexHome)) {
|
|
4240
|
+
return {
|
|
4241
|
+
success: false,
|
|
4242
|
+
changed,
|
|
4243
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4244
|
+
error: `Central ${PLUGIN_ID2} remains installed, but exact legacy cleanup did not converge; close Codex and retry.`
|
|
4245
|
+
};
|
|
4246
|
+
}
|
|
4247
|
+
return { success: true, changed, diagnostics: uniqueMessages3(diagnostics) };
|
|
4248
|
+
}
|
|
4249
|
+
function formatCodexPluginSetupPlan(plan) {
|
|
4250
|
+
const lines = plan.items.map(
|
|
4251
|
+
(item) => item.command ? `- ${item.action}: ${item.command.executable} ${item.command.args.join(" ")}` : `- ${item.action}: ${item.cleanupRoot?.relativePath ?? item.targetPath}`
|
|
4252
|
+
);
|
|
4253
|
+
return ["Codex plugin setup plan:", ...lines].join("\n");
|
|
4254
|
+
}
|
|
4255
|
+
|
|
4256
|
+
// src/cli/operations/codex.ts
|
|
4257
|
+
var CODEX_DISPLAY_NAME = "Codex";
|
|
4258
|
+
var codexPlanSources = /* @__PURE__ */ new WeakMap();
|
|
4259
|
+
var codexModelSources = /* @__PURE__ */ new WeakMap();
|
|
4260
|
+
var codexActions = [
|
|
4261
|
+
{
|
|
4262
|
+
id: "codex-status",
|
|
4263
|
+
kind: "status",
|
|
4264
|
+
label: "Status",
|
|
4265
|
+
description: "Inspect managed Codex setup state",
|
|
3800
4266
|
dryRun: false,
|
|
3801
4267
|
requiresConfirmation: false,
|
|
3802
4268
|
supported: true
|
|
@@ -3910,7 +4376,7 @@ function backupForItem(item) {
|
|
|
3910
4376
|
function rootBlockState(item) {
|
|
3911
4377
|
if (!existsSync8(item.targetPath))
|
|
3912
4378
|
return { state: "missing", observed: "absent" };
|
|
3913
|
-
const content =
|
|
4379
|
+
const content = readFileSync7(item.targetPath, "utf8");
|
|
3914
4380
|
if (item.content && content.includes(item.content)) {
|
|
3915
4381
|
return { state: "installed", observed: "managed root block present" };
|
|
3916
4382
|
}
|
|
@@ -3923,11 +4389,11 @@ function managedModelState(item) {
|
|
|
3923
4389
|
if (!existsSync8(item.targetPath))
|
|
3924
4390
|
return { state: "missing", observed: "absent" };
|
|
3925
4391
|
try {
|
|
3926
|
-
const parsed = JSON.parse(
|
|
4392
|
+
const parsed = JSON.parse(readFileSync7(item.targetPath, "utf8"));
|
|
3927
4393
|
if (parsed.version !== MANAGED_MODEL_STATE_VERSION || !parsed.models || typeof parsed.models !== "object" || Array.isArray(parsed.models)) {
|
|
3928
4394
|
return { state: "unknown", observed: "invalid managed model state" };
|
|
3929
4395
|
}
|
|
3930
|
-
return item.content ===
|
|
4396
|
+
return item.content === readFileSync7(item.targetPath, "utf8") ? { state: "installed", observed: "managed model state current" } : { state: "drift", observed: "managed model state differs" };
|
|
3931
4397
|
} catch {
|
|
3932
4398
|
return { state: "unknown", observed: "unparseable managed model state" };
|
|
3933
4399
|
}
|
|
@@ -3935,7 +4401,7 @@ function managedModelState(item) {
|
|
|
3935
4401
|
function userConfigState(item) {
|
|
3936
4402
|
if (!existsSync8(item.targetPath))
|
|
3937
4403
|
return { state: "missing", observed: "absent" };
|
|
3938
|
-
const content =
|
|
4404
|
+
const content = readFileSync7(item.targetPath, "utf8");
|
|
3939
4405
|
if (/^\s*default_mode_request_user_input\s*=\s*true\b/m.test(content)) {
|
|
3940
4406
|
return {
|
|
3941
4407
|
state: "installed",
|
|
@@ -3950,7 +4416,7 @@ function userConfigState(item) {
|
|
|
3950
4416
|
function contentState(item) {
|
|
3951
4417
|
if (!existsSync8(item.targetPath))
|
|
3952
4418
|
return { state: "missing", observed: "absent" };
|
|
3953
|
-
const observed =
|
|
4419
|
+
const observed = readFileSync7(item.targetPath, "utf8");
|
|
3954
4420
|
if (item.content === observed)
|
|
3955
4421
|
return { state: "installed", observed: "current" };
|
|
3956
4422
|
if (item.role) {
|
|
@@ -4117,7 +4583,7 @@ function planItemFromPluginSetup(item) {
|
|
|
4117
4583
|
path: item.targetPath,
|
|
4118
4584
|
label: "Codex native plugin manager target"
|
|
4119
4585
|
},
|
|
4120
|
-
preview: `${item.command.executable} ${item.command.args.join(" ")}`,
|
|
4586
|
+
preview: item.command ? `${item.command.executable} ${item.command.args.join(" ")}` : `Remove exact preflight-approved Codex root ${item.cleanupRoot?.relativePath ?? item.targetPath}`,
|
|
4121
4587
|
backup: { required: false, strategy: "external" }
|
|
4122
4588
|
};
|
|
4123
4589
|
}
|
|
@@ -4215,7 +4681,10 @@ function buildCompleteCodexPlan(action, context2) {
|
|
|
4215
4681
|
const packageVersion = resolveVersion();
|
|
4216
4682
|
const buildPlugin = context2.buildCodexPluginSetupPlan ?? buildCodexPluginSetupPlan;
|
|
4217
4683
|
const pluginPlan = buildPlugin({
|
|
4684
|
+
codexHome: context2.codexHome,
|
|
4218
4685
|
dryRun: true,
|
|
4686
|
+
expectedVersion: packageVersion.ok ? packageVersion.version : "0.0.0",
|
|
4687
|
+
homeDir: context2.homeDir,
|
|
4219
4688
|
projectRoot: context2.cwd,
|
|
4220
4689
|
commandExecutor: context2.codexPluginCommandExecutor
|
|
4221
4690
|
});
|
|
@@ -4611,7 +5080,7 @@ function applyCodexPlan(plan) {
|
|
|
4611
5080
|
// src/cli/operations/opencode.ts
|
|
4612
5081
|
import { createHash as createHash3 } from "crypto";
|
|
4613
5082
|
import { existsSync as existsSync10 } from "fs";
|
|
4614
|
-
import { join as
|
|
5083
|
+
import { join as join6 } from "path";
|
|
4615
5084
|
|
|
4616
5085
|
// src/cli/opencode-effort.ts
|
|
4617
5086
|
var OPENAI_GPT_RUNTIME_VARIANTS = /* @__PURE__ */ new Set([
|
|
@@ -4647,14 +5116,16 @@ function resolveOpenCodeEffort(input) {
|
|
|
4647
5116
|
import {
|
|
4648
5117
|
cpSync,
|
|
4649
5118
|
existsSync as existsSync9,
|
|
5119
|
+
lstatSync as lstatSync2,
|
|
4650
5120
|
mkdirSync as mkdirSync4,
|
|
4651
5121
|
mkdtempSync,
|
|
5122
|
+
readFileSync as readFileSync8,
|
|
4652
5123
|
renameSync as renameSync3,
|
|
4653
|
-
rmSync as
|
|
5124
|
+
rmSync as rmSync3,
|
|
4654
5125
|
statSync
|
|
4655
5126
|
} from "fs";
|
|
4656
5127
|
import { homedir as homedir4 } from "os";
|
|
4657
|
-
import { dirname as dirname5, join as
|
|
5128
|
+
import { dirname as dirname5, join as join5 } from "path";
|
|
4658
5129
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4659
5130
|
|
|
4660
5131
|
// src/harness/core/owned-skills.ts
|
|
@@ -4667,6 +5138,68 @@ var THOTH_OWNED_SKILL_NAMES = [
|
|
|
4667
5138
|
];
|
|
4668
5139
|
|
|
4669
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
|
+
}
|
|
4670
5203
|
function resolveOwnedSkillPackageRoot(moduleUrl) {
|
|
4671
5204
|
const packageRoot = findPackageRoot(dirname5(fileURLToPath4(moduleUrl)));
|
|
4672
5205
|
if (!packageRoot) {
|
|
@@ -4684,8 +5217,16 @@ function getOpenCodeOwnedSkillEntries(options = {}) {
|
|
|
4684
5217
|
const homeDir = options.homeDir ?? homedir4();
|
|
4685
5218
|
return THOTH_OWNED_SKILL_NAMES.map((name) => ({
|
|
4686
5219
|
name,
|
|
4687
|
-
sourcePath:
|
|
4688
|
-
destinationPath:
|
|
5220
|
+
sourcePath: join5(packageRoot, "skills", name),
|
|
5221
|
+
destinationPath: join5(homeDir, ".config", "opencode", "skills", name)
|
|
5222
|
+
}));
|
|
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)
|
|
4689
5230
|
}));
|
|
4690
5231
|
}
|
|
4691
5232
|
function validateCanonicalSkill(entry) {
|
|
@@ -4694,7 +5235,7 @@ function validateCanonicalSkill(entry) {
|
|
|
4694
5235
|
`Canonical owned skill directory is missing: ${entry.name}`
|
|
4695
5236
|
);
|
|
4696
5237
|
}
|
|
4697
|
-
const contractPath =
|
|
5238
|
+
const contractPath = join5(entry.sourcePath, "SKILL.md");
|
|
4698
5239
|
if (!existsSync9(contractPath) || !statSync(contractPath).isFile()) {
|
|
4699
5240
|
throw new Error(`Canonical owned skill contract is missing: ${entry.name}`);
|
|
4700
5241
|
}
|
|
@@ -4704,21 +5245,21 @@ function installOwnedSkills(skills) {
|
|
|
4704
5245
|
if (!firstSkill) return;
|
|
4705
5246
|
const globalRoot = dirname5(firstSkill.destinationPath);
|
|
4706
5247
|
mkdirSync4(globalRoot, { recursive: true });
|
|
4707
|
-
const transactionRoot = mkdtempSync(
|
|
4708
|
-
const stagedRoot =
|
|
4709
|
-
const backupRoot =
|
|
5248
|
+
const transactionRoot = mkdtempSync(join5(globalRoot, ".thoth-agents-owned-"));
|
|
5249
|
+
const stagedRoot = join5(transactionRoot, "staged");
|
|
5250
|
+
const backupRoot = join5(transactionRoot, "backup");
|
|
4710
5251
|
mkdirSync4(stagedRoot, { recursive: true });
|
|
4711
5252
|
mkdirSync4(backupRoot, { recursive: true });
|
|
4712
5253
|
try {
|
|
4713
5254
|
for (const skill of skills) {
|
|
4714
|
-
cpSync(skill.sourcePath,
|
|
5255
|
+
cpSync(skill.sourcePath, join5(stagedRoot, skill.name), {
|
|
4715
5256
|
recursive: true,
|
|
4716
5257
|
force: true
|
|
4717
5258
|
});
|
|
4718
5259
|
}
|
|
4719
5260
|
for (const skill of skills) {
|
|
4720
|
-
const stagedPath =
|
|
4721
|
-
const backupPath =
|
|
5261
|
+
const stagedPath = join5(stagedRoot, skill.name);
|
|
5262
|
+
const backupPath = join5(backupRoot, skill.name);
|
|
4722
5263
|
const hadExistingDestination = existsSync9(skill.destinationPath);
|
|
4723
5264
|
if (hadExistingDestination) {
|
|
4724
5265
|
renameSync3(skill.destinationPath, backupPath);
|
|
@@ -4727,7 +5268,7 @@ function installOwnedSkills(skills) {
|
|
|
4727
5268
|
renameSync3(stagedPath, skill.destinationPath);
|
|
4728
5269
|
} catch (error) {
|
|
4729
5270
|
if (existsSync9(skill.destinationPath)) {
|
|
4730
|
-
|
|
5271
|
+
rmSync3(skill.destinationPath, { recursive: true, force: true });
|
|
4731
5272
|
}
|
|
4732
5273
|
if (hadExistingDestination && existsSync9(backupPath)) {
|
|
4733
5274
|
renameSync3(backupPath, skill.destinationPath);
|
|
@@ -4735,11 +5276,11 @@ function installOwnedSkills(skills) {
|
|
|
4735
5276
|
throw error;
|
|
4736
5277
|
}
|
|
4737
5278
|
if (existsSync9(backupPath)) {
|
|
4738
|
-
|
|
5279
|
+
rmSync3(backupPath, { recursive: true, force: true });
|
|
4739
5280
|
}
|
|
4740
5281
|
}
|
|
4741
5282
|
} finally {
|
|
4742
|
-
|
|
5283
|
+
rmSync3(transactionRoot, { recursive: true, force: true });
|
|
4743
5284
|
}
|
|
4744
5285
|
}
|
|
4745
5286
|
function syncOpenCodeOwnedSkills(options = {}) {
|
|
@@ -4758,6 +5299,37 @@ function syncOpenCodeOwnedSkills(options = {}) {
|
|
|
4758
5299
|
};
|
|
4759
5300
|
}
|
|
4760
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
|
+
}
|
|
4761
5333
|
|
|
4762
5334
|
// src/cli/operations/opencode.ts
|
|
4763
5335
|
var PACKAGE_NAME = "thoth-agents";
|
|
@@ -4809,7 +5381,7 @@ function canonicalData(value, active) {
|
|
|
4809
5381
|
if (ownKeys.some((key) => typeof key === "symbol")) {
|
|
4810
5382
|
throw new NonCanonicalDataError("Symbol keys are not canonical.");
|
|
4811
5383
|
}
|
|
4812
|
-
const
|
|
5384
|
+
const keys2 = ownKeys;
|
|
4813
5385
|
active.add(value);
|
|
4814
5386
|
try {
|
|
4815
5387
|
if (array) {
|
|
@@ -4821,7 +5393,7 @@ function canonicalData(value, active) {
|
|
|
4821
5393
|
if (!Number.isSafeInteger(length) || length < 0) {
|
|
4822
5394
|
throw new NonCanonicalDataError("Array length is invalid.");
|
|
4823
5395
|
}
|
|
4824
|
-
const indexKeys =
|
|
5396
|
+
const indexKeys = keys2.filter((key) => key !== "length");
|
|
4825
5397
|
if (indexKeys.length !== length || indexKeys.some(
|
|
4826
5398
|
(key) => !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= length || String(Number(key)) !== key
|
|
4827
5399
|
)) {
|
|
@@ -4843,7 +5415,7 @@ function canonicalData(value, active) {
|
|
|
4843
5415
|
return `[${parts2.join(",")}]`;
|
|
4844
5416
|
}
|
|
4845
5417
|
const parts = [];
|
|
4846
|
-
for (const key of
|
|
5418
|
+
for (const key of keys2.sort()) {
|
|
4847
5419
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
4848
5420
|
if (!descriptor || !("value" in descriptor)) {
|
|
4849
5421
|
throw new NonCanonicalDataError("Accessors are not canonical.");
|
|
@@ -5000,7 +5572,7 @@ function titleCaseSkillName(value) {
|
|
|
5000
5572
|
}).join("-");
|
|
5001
5573
|
}
|
|
5002
5574
|
function homeDirFromContext(context2) {
|
|
5003
|
-
return context2.env?.HOME ?? context2.env?.USERPROFILE ?? (context2.cwd !== process.cwd() ?
|
|
5575
|
+
return context2.env?.HOME ?? context2.env?.USERPROFILE ?? (context2.cwd !== process.cwd() ? join6(context2.cwd, "home") : void 0);
|
|
5004
5576
|
}
|
|
5005
5577
|
function openCodeSkillTargets(context2) {
|
|
5006
5578
|
const homeDir = homeDirFromContext(context2);
|
|
@@ -5024,7 +5596,7 @@ function openCodeSkillTargets(context2) {
|
|
|
5024
5596
|
const ownedTargets = getOpenCodeOwnedSkillEntries({
|
|
5025
5597
|
homeDir
|
|
5026
5598
|
}).map((skill) => {
|
|
5027
|
-
const path =
|
|
5599
|
+
const path = join6(skill.destinationPath, "SKILL.md");
|
|
5028
5600
|
const installed = existsSync10(path);
|
|
5029
5601
|
return {
|
|
5030
5602
|
kind: "skill",
|
|
@@ -6382,131 +6954,2109 @@ function applyOpenCodePlan(plan) {
|
|
|
6382
6954
|
};
|
|
6383
6955
|
}
|
|
6384
6956
|
|
|
6385
|
-
// src/cli/operations/
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
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 };
|
|
6401
6991
|
}
|
|
6402
6992
|
|
|
6403
|
-
// src/cli/
|
|
6404
|
-
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";
|
|
6405
7009
|
|
|
6406
|
-
// src/cli/
|
|
7010
|
+
// src/cli/pi-migration.ts
|
|
6407
7011
|
import {
|
|
7012
|
+
copyFileSync as copyFileSync4,
|
|
6408
7013
|
existsSync as existsSync11,
|
|
6409
7014
|
mkdirSync as mkdirSync5,
|
|
6410
|
-
|
|
7015
|
+
readdirSync as readdirSync2,
|
|
7016
|
+
readFileSync as readFileSync9,
|
|
6411
7017
|
renameSync as renameSync4,
|
|
6412
|
-
|
|
7018
|
+
statSync as statSync2,
|
|
6413
7019
|
writeFileSync as writeFileSync4
|
|
6414
7020
|
} from "fs";
|
|
6415
|
-
import {
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
etag: z2.string().optional(),
|
|
6430
|
-
fetchedAt: z2.string().datetime(),
|
|
6431
|
-
catalog: z2.object({ models: z2.array(cachedModelOptionSchema) }).passthrough()
|
|
6432
|
-
}).passthrough();
|
|
6433
|
-
function resolveModelsDevCachePath(options = {}) {
|
|
6434
|
-
const platform = options.platform ?? process.platform;
|
|
6435
|
-
const env = options.env ?? process.env;
|
|
6436
|
-
const home = options.homeDir ?? homedir5();
|
|
6437
|
-
if (platform === "win32") {
|
|
6438
|
-
const path = win32;
|
|
6439
|
-
const base2 = env.LOCALAPPDATA ?? env.APPDATA ?? path.join(home, "AppData", "Local");
|
|
6440
|
-
return path.join(base2, "thoth-agents", "models-dev-v1.json");
|
|
6441
|
-
}
|
|
6442
|
-
const base = env.XDG_CACHE_HOME ?? posix.join(home, ".cache");
|
|
6443
|
-
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
|
+
);
|
|
6444
7035
|
}
|
|
6445
|
-
function
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
}
|
|
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/, "")}`;
|
|
6454
7044
|
}
|
|
6455
|
-
function
|
|
6456
|
-
const
|
|
6457
|
-
|
|
6458
|
-
|
|
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 });
|
|
6459
7069
|
}
|
|
6460
|
-
|
|
6461
|
-
|
|
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
|
+
};
|
|
6462
7079
|
try {
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
`${
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
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 };
|
|
6469
7093
|
} catch (error) {
|
|
6470
|
-
|
|
6471
|
-
|
|
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
|
+
};
|
|
6472
7108
|
}
|
|
6473
7109
|
}
|
|
6474
7110
|
|
|
6475
|
-
// src/cli/
|
|
6476
|
-
import {
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
}
|
|
6498
|
-
|
|
6499
|
-
const
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
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];
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
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
|
+
};
|
|
7146
|
+
}
|
|
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);
|
|
6510
9060
|
}
|
|
6511
9061
|
}
|
|
6512
9062
|
return [...values];
|
|
@@ -6660,6 +9210,7 @@ var CODEX_DOCUMENTED_EFFORTS2 = /* @__PURE__ */ new Set([
|
|
|
6660
9210
|
"ultra"
|
|
6661
9211
|
]);
|
|
6662
9212
|
var CLAUDE_CODE_EFFORTS2 = ["low", "medium", "high", "xhigh", "max"];
|
|
9213
|
+
var PI_EFFORTS2 = new Set(PI_EFFORT_VALUES);
|
|
6663
9214
|
function parseOpenCodeModels(output) {
|
|
6664
9215
|
const seen = /* @__PURE__ */ new Set();
|
|
6665
9216
|
const options = [];
|
|
@@ -6695,6 +9246,9 @@ function writableEfforts(harness, option) {
|
|
|
6695
9246
|
(effort) => CLAUDE_CODE_EFFORTS2.includes(effort)
|
|
6696
9247
|
);
|
|
6697
9248
|
}
|
|
9249
|
+
if (harness === "pi") {
|
|
9250
|
+
return option.efforts.filter((effort) => PI_EFFORTS2.has(effort));
|
|
9251
|
+
}
|
|
6698
9252
|
return option.efforts.filter(
|
|
6699
9253
|
(effort) => resolveOpenCodeEffort({
|
|
6700
9254
|
model: option.id,
|
|
@@ -6769,6 +9323,12 @@ async function getModelOptions(harness) {
|
|
|
6769
9323
|
efforts: writableEfforts(harness, option)
|
|
6770
9324
|
}));
|
|
6771
9325
|
}
|
|
9326
|
+
if (harness === "pi") {
|
|
9327
|
+
return loaded.models.map((option) => ({
|
|
9328
|
+
...option,
|
|
9329
|
+
efforts: writableEfforts(harness, option)
|
|
9330
|
+
}));
|
|
9331
|
+
}
|
|
6772
9332
|
const catalogById = new Map(
|
|
6773
9333
|
loaded.models.map((option) => [option.catalogId ?? option.id, option])
|
|
6774
9334
|
);
|
|
@@ -6780,7 +9340,7 @@ async function getModelOptions(harness) {
|
|
|
6780
9340
|
}
|
|
6781
9341
|
|
|
6782
9342
|
// src/cli/tui/operations.ts
|
|
6783
|
-
import { existsSync as
|
|
9343
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
|
|
6784
9344
|
var context = { cwd: process.cwd() };
|
|
6785
9345
|
var codexContext = { cwd: process.cwd() };
|
|
6786
9346
|
var claudeCodeContext = {
|
|
@@ -6822,7 +9382,7 @@ function getCodexModelRoles(source = codexContext) {
|
|
|
6822
9382
|
const item = plan.items.find(
|
|
6823
9383
|
(candidate) => candidate.action === "write-role-toml" && candidate.role === role
|
|
6824
9384
|
);
|
|
6825
|
-
const content = item &&
|
|
9385
|
+
const content = item && existsSync17(item.targetPath) ? readFileSync14(item.targetPath, "utf8") : item?.content;
|
|
6826
9386
|
const effort = content ? parseRoleTomlEffort(content) : void 0;
|
|
6827
9387
|
return {
|
|
6828
9388
|
role,
|
|
@@ -6837,6 +9397,9 @@ function getCodexModelRoles(source = codexContext) {
|
|
|
6837
9397
|
function getClaudeCodeModelRoles(_source = claudeCodeContext) {
|
|
6838
9398
|
return defaultClaudeCodeModelRoles();
|
|
6839
9399
|
}
|
|
9400
|
+
function getPiModelRoles() {
|
|
9401
|
+
return defaultPiModelRoles(context);
|
|
9402
|
+
}
|
|
6840
9403
|
function readRoleField(config, role, field) {
|
|
6841
9404
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
6842
9405
|
return void 0;
|
|
@@ -6879,6 +9442,9 @@ function buildTuiModelPlan(harness, roles) {
|
|
|
6879
9442
|
claudeCodeContext
|
|
6880
9443
|
);
|
|
6881
9444
|
}
|
|
9445
|
+
if (harness === "pi") {
|
|
9446
|
+
return buildPiModelPlan({ harness, dryRun: true, roles }, context);
|
|
9447
|
+
}
|
|
6882
9448
|
return buildCodexModelPlan({ harness, dryRun: true, roles }, codexContext);
|
|
6883
9449
|
}
|
|
6884
9450
|
var defaultTuiOperations = {
|
|
@@ -6887,6 +9453,7 @@ var defaultTuiOperations = {
|
|
|
6887
9453
|
if (harness === "claude") {
|
|
6888
9454
|
return getClaudeCodeStatus(claudeCodeContext, evidence);
|
|
6889
9455
|
}
|
|
9456
|
+
if (harness === "pi") return getPiStatus(context, evidence);
|
|
6890
9457
|
return getCodexStatus(codexContext, evidence);
|
|
6891
9458
|
},
|
|
6892
9459
|
modelRoles(harness) {
|
|
@@ -6894,6 +9461,7 @@ var defaultTuiOperations = {
|
|
|
6894
9461
|
if (harness === "claude") {
|
|
6895
9462
|
return getClaudeCodeModelRoles(claudeCodeContext);
|
|
6896
9463
|
}
|
|
9464
|
+
if (harness === "pi") return getPiModelRoles();
|
|
6897
9465
|
return getCodexModelRoles(codexContext);
|
|
6898
9466
|
},
|
|
6899
9467
|
modelOptions(harness) {
|
|
@@ -6919,6 +9487,12 @@ var defaultTuiOperations = {
|
|
|
6919
9487
|
getClaudeCodeModelRoles(claudeCodeContext)
|
|
6920
9488
|
);
|
|
6921
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
|
+
}
|
|
6922
9496
|
if (action === "install") return buildCodexInstallPlan(codexContext);
|
|
6923
9497
|
if (action === "update") return buildCodexUpdatePlan(codexContext);
|
|
6924
9498
|
if (action === "sync") return buildCodexSyncPlan(codexContext);
|
|
@@ -6930,6 +9504,7 @@ var defaultTuiOperations = {
|
|
|
6930
9504
|
apply(plan) {
|
|
6931
9505
|
if (plan.harness === "opencode") return applyOpenCodePlan(plan);
|
|
6932
9506
|
if (plan.harness === "claude") return applyClaudeCodePlan(plan);
|
|
9507
|
+
if (plan.harness === "pi") return applyPiPlan(plan);
|
|
6933
9508
|
return applyCodexPlan(plan);
|
|
6934
9509
|
}
|
|
6935
9510
|
};
|
|
@@ -6948,8 +9523,13 @@ export {
|
|
|
6948
9523
|
buildCodexPluginSetupPlan,
|
|
6949
9524
|
applyCodexPluginSetup,
|
|
6950
9525
|
formatCodexPluginSetupPlan,
|
|
9526
|
+
recordHarnessInstallCompletion,
|
|
6951
9527
|
finalizeHarnessInstall,
|
|
6952
9528
|
syncOpenCodeOwnedSkills,
|
|
9529
|
+
inspectPiPackageSkills,
|
|
9530
|
+
buildPiSetupPlan,
|
|
9531
|
+
formatPiSetupPlan,
|
|
9532
|
+
applyPiSetup,
|
|
6953
9533
|
REQUIRED_SKILLS,
|
|
6954
9534
|
getRequiredSkillInstallCommand,
|
|
6955
9535
|
installRequiredSkill,
|
|
@@ -6969,6 +9549,12 @@ export {
|
|
|
6969
9549
|
buildOpenCodeSyncPlan,
|
|
6970
9550
|
buildOpenCodeModelPlan,
|
|
6971
9551
|
applyOpenCodePlan,
|
|
9552
|
+
getPiStatus,
|
|
9553
|
+
buildPiUpdatePlan,
|
|
9554
|
+
buildPiSyncPlan,
|
|
9555
|
+
defaultPiModelRoles,
|
|
9556
|
+
buildPiModelPlan,
|
|
9557
|
+
applyPiPlan,
|
|
6972
9558
|
SUPPORTED_OPERATION_HARNESSES,
|
|
6973
9559
|
listOperationHarnesses,
|
|
6974
9560
|
getOperationHarness,
|
|
@@ -6976,6 +9562,7 @@ export {
|
|
|
6976
9562
|
getModelOptions,
|
|
6977
9563
|
getCodexModelRoles,
|
|
6978
9564
|
getClaudeCodeModelRoles,
|
|
9565
|
+
getPiModelRoles,
|
|
6979
9566
|
getOpenCodeModelRoles,
|
|
6980
9567
|
defaultTuiOperations
|
|
6981
9568
|
};
|