thoth-agents 0.3.11 → 0.3.12
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 +119 -33
- package/dist/{chunk-CB3EBA7H.js → chunk-5UBJB5QO.js} +1 -1
- package/dist/{chunk-GIUU6FNE.js → chunk-DX4U3AD3.js} +95 -50
- package/dist/{chunk-ZEAAWBF2.js → chunk-VPVBYBPU.js} +575 -98
- package/dist/cli/codex-plugin-install.d.ts +16 -2
- package/dist/cli/index.js +7 -5
- package/dist/cli/tui/index.js +2 -2
- package/dist/harness/core/agent-pack.d.ts +26 -6
- package/dist/harness/core/sdd.d.ts +27 -15
- package/dist/harness/writers/codex-plugin-package.d.ts +1 -0
- package/dist/index.js +2 -2
- package/package.json +4 -4
- 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
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
updateOpenCodeMainConfig,
|
|
36
36
|
writeConfig,
|
|
37
37
|
writeLiteConfig
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-DX4U3AD3.js";
|
|
39
39
|
|
|
40
40
|
// src/cli/codex-paths.ts
|
|
41
41
|
import { homedir } from "os";
|
|
@@ -638,8 +638,8 @@ function stripProviderMcp(content) {
|
|
|
638
638
|
}
|
|
639
639
|
function pluginRootReference(pathValue) {
|
|
640
640
|
const normalized = normalizePath(pathValue);
|
|
641
|
-
const
|
|
642
|
-
return `./${
|
|
641
|
+
const relative2 = normalized.slice(".codex-plugin/".length);
|
|
642
|
+
return `./${relative2}`;
|
|
643
643
|
}
|
|
644
644
|
function sha256(content) {
|
|
645
645
|
return `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
@@ -692,7 +692,7 @@ function codexHookPackageDiagnostic(code, message, severity = "warning") {
|
|
|
692
692
|
function normalizeHookDefinition(hook) {
|
|
693
693
|
return {
|
|
694
694
|
command: hook.handler.command,
|
|
695
|
-
|
|
695
|
+
type: "command"
|
|
696
696
|
};
|
|
697
697
|
}
|
|
698
698
|
function renderHookDefinitions(hookDefinitions) {
|
|
@@ -705,7 +705,10 @@ function renderHookDefinitions(hookDefinitions) {
|
|
|
705
705
|
continue;
|
|
706
706
|
}
|
|
707
707
|
const eventHooks = hooksByEvent.get(validation.event) ?? [];
|
|
708
|
-
eventHooks.push(
|
|
708
|
+
eventHooks.push({
|
|
709
|
+
...hook.matcher !== void 0 ? { matcher: hook.matcher } : {},
|
|
710
|
+
hooks: [normalizeHookDefinition(hook)]
|
|
711
|
+
});
|
|
709
712
|
hooksByEvent.set(validation.event, eventHooks);
|
|
710
713
|
}
|
|
711
714
|
if (hooksByEvent.size === 0) {
|
|
@@ -718,11 +721,11 @@ function renderHookDefinitions(hookDefinitions) {
|
|
|
718
721
|
);
|
|
719
722
|
return { diagnostics };
|
|
720
723
|
}
|
|
721
|
-
const content = stableJson(
|
|
722
|
-
Object.fromEntries(
|
|
724
|
+
const content = stableJson({
|
|
725
|
+
hooks: Object.fromEntries(
|
|
723
726
|
[...hooksByEvent.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([event, hooks]) => [event, hooks.map(stableValue)])
|
|
724
727
|
)
|
|
725
|
-
);
|
|
728
|
+
});
|
|
726
729
|
return { content, diagnostics };
|
|
727
730
|
}
|
|
728
731
|
function parseRawHookContent(content) {
|
|
@@ -753,37 +756,66 @@ function parseRawHookContent(content) {
|
|
|
753
756
|
]
|
|
754
757
|
};
|
|
755
758
|
}
|
|
756
|
-
|
|
759
|
+
if (!isRecord(parsed.hooks)) {
|
|
760
|
+
return {
|
|
761
|
+
hookDefinitions,
|
|
762
|
+
diagnostics: [
|
|
763
|
+
codexHookPackageDiagnostic(
|
|
764
|
+
"codex.plugin.hooks.invalid_shape",
|
|
765
|
+
'Skipping Codex plugin hooks asset because hooks.json must contain a nested "hooks" object.'
|
|
766
|
+
)
|
|
767
|
+
]
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
for (const [event, definitions] of Object.entries(parsed.hooks)) {
|
|
757
771
|
if (!Array.isArray(definitions)) {
|
|
758
772
|
diagnostics.push(
|
|
759
773
|
codexHookPackageDiagnostic(
|
|
760
774
|
"codex.plugin.hooks.invalid_shape",
|
|
761
|
-
`Skipping Codex plugin hook event "${event}" because its value is not an array of
|
|
775
|
+
`Skipping Codex plugin hook event "${event}" because its value is not an array of matcher groups.`
|
|
762
776
|
)
|
|
763
777
|
);
|
|
764
778
|
continue;
|
|
765
779
|
}
|
|
766
|
-
for (const
|
|
767
|
-
if (!isRecord(
|
|
780
|
+
for (const group of definitions) {
|
|
781
|
+
if (!isRecord(group) || !Array.isArray(group.hooks)) {
|
|
768
782
|
diagnostics.push(
|
|
769
783
|
codexHookPackageDiagnostic(
|
|
770
784
|
"codex.plugin.hooks.invalid_shape",
|
|
771
|
-
`Skipping Codex plugin hook event "${event}" entry because it is not
|
|
785
|
+
`Skipping Codex plugin hook event "${event}" entry because it is not a matcher group with a hooks array.`
|
|
772
786
|
)
|
|
773
787
|
);
|
|
774
788
|
continue;
|
|
775
789
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
790
|
+
if (group.matcher !== void 0 && typeof group.matcher !== "string") {
|
|
791
|
+
diagnostics.push(
|
|
792
|
+
codexHookPackageDiagnostic(
|
|
793
|
+
"codex.plugin.hooks.invalid_shape",
|
|
794
|
+
`Skipping Codex plugin hook event "${event}" matcher group because matcher is not a string.`
|
|
795
|
+
)
|
|
796
|
+
);
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
for (const definition of group.hooks) {
|
|
800
|
+
if (!isRecord(definition)) {
|
|
801
|
+
diagnostics.push(
|
|
802
|
+
codexHookPackageDiagnostic(
|
|
803
|
+
"codex.plugin.hooks.invalid_shape",
|
|
804
|
+
`Skipping Codex plugin hook event "${event}" handler because it is not an object.`
|
|
805
|
+
)
|
|
806
|
+
);
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
hookDefinitions.push({
|
|
810
|
+
event,
|
|
811
|
+
...typeof group.matcher === "string" ? { matcher: group.matcher } : {},
|
|
812
|
+
handler: {
|
|
813
|
+
type: typeof definition.type === "string" ? definition.type : "command",
|
|
814
|
+
command: definition.command,
|
|
815
|
+
async: definition.async
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
}
|
|
787
819
|
}
|
|
788
820
|
}
|
|
789
821
|
return { hookDefinitions, diagnostics };
|
|
@@ -1763,7 +1795,7 @@ function buildCodexSetupPlan(config) {
|
|
|
1763
1795
|
configPath: targets.configPath,
|
|
1764
1796
|
pluginId: config.pluginId,
|
|
1765
1797
|
diagnostics: [
|
|
1766
|
-
"The combined install flow runs `codex plugin marketplace add EremesNG/thoth-
|
|
1798
|
+
"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
1799
|
"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
1800
|
"The /hooks surface may show hooks from independently installed plugins; this thoth-agents setup plan does not install provider hooks.",
|
|
1769
1801
|
"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 +2171,18 @@ var CLAUDE_CODE_ROLE_NAMES = [
|
|
|
2139
2171
|
];
|
|
2140
2172
|
|
|
2141
2173
|
// src/cli/claude-code-install.ts
|
|
2142
|
-
var
|
|
2143
|
-
var
|
|
2144
|
-
var
|
|
2145
|
-
var
|
|
2146
|
-
var
|
|
2174
|
+
var PLUGIN_NAME = "thoth-agents";
|
|
2175
|
+
var MARKETPLACE_NAME = "thoth-plugins";
|
|
2176
|
+
var MARKETPLACE_SOURCE = "https://github.com/EremesNG/thoth-plugins.git";
|
|
2177
|
+
var MARKETPLACE_INSTALL_SOURCE = MARKETPLACE_SOURCE;
|
|
2178
|
+
var PLUGIN_ID = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
2179
|
+
var LEGACY_MARKETPLACE_SOURCE = "https://github.com/EremesNG/thoth-agents.git";
|
|
2180
|
+
var LEGACY_MARKETPLACE_NAMES = [PLUGIN_NAME, `${PLUGIN_NAME}-claude`];
|
|
2181
|
+
var LEGACY_PLUGIN_IDS = LEGACY_MARKETPLACE_NAMES.map(
|
|
2182
|
+
(name) => `${PLUGIN_NAME}@${name}`
|
|
2183
|
+
);
|
|
2184
|
+
var MARKETPLACE_TARGET = `claude://marketplaces/${MARKETPLACE_NAME}`;
|
|
2185
|
+
var PLUGIN_TARGET = `claude://plugins/${PLUGIN_ID}`;
|
|
2147
2186
|
function defaultCommandExecutor(command, args, options) {
|
|
2148
2187
|
const result = spawnSync(command, [...args], {
|
|
2149
2188
|
cwd: options.cwd,
|
|
@@ -2171,12 +2210,26 @@ function parseJsonRecords(content) {
|
|
|
2171
2210
|
}
|
|
2172
2211
|
}
|
|
2173
2212
|
function isCanonicalMarketplace(entry) {
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2213
|
+
return isMarketplaceIdentity(entry, MARKETPLACE_NAME);
|
|
2214
|
+
}
|
|
2215
|
+
function normalizeMarketplaceSource(value) {
|
|
2216
|
+
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();
|
|
2217
|
+
}
|
|
2218
|
+
function marketplaceSources(entry) {
|
|
2219
|
+
const sources = [entry.repo, entry.source];
|
|
2220
|
+
if (isRecord3(entry.marketplaceSource)) {
|
|
2221
|
+
sources.push(entry.marketplaceSource.repo, entry.marketplaceSource.source);
|
|
2177
2222
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2223
|
+
return sources.filter(
|
|
2224
|
+
(source) => typeof source === "string"
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2227
|
+
function isMarketplaceIdentity(entry, name, source = MARKETPLACE_SOURCE) {
|
|
2228
|
+
if (entry.name !== name) return false;
|
|
2229
|
+
const canonical = normalizeMarketplaceSource(source);
|
|
2230
|
+
return marketplaceSources(entry).some(
|
|
2231
|
+
(source2) => normalizeMarketplaceSource(source2) === canonical
|
|
2232
|
+
);
|
|
2180
2233
|
}
|
|
2181
2234
|
function inspectClaudeManager(executor, cwd, scope) {
|
|
2182
2235
|
const marketplaceResult = executor(
|
|
@@ -2212,6 +2265,19 @@ function inspectClaudeManager(executor, cwd, scope) {
|
|
|
2212
2265
|
diagnostics
|
|
2213
2266
|
};
|
|
2214
2267
|
}
|
|
2268
|
+
const legacyMarketplace = LEGACY_MARKETPLACE_NAMES.some(
|
|
2269
|
+
(name) => marketplaces.some(
|
|
2270
|
+
(entry) => isMarketplaceIdentity(entry, name, LEGACY_MARKETPLACE_SOURCE)
|
|
2271
|
+
)
|
|
2272
|
+
);
|
|
2273
|
+
const hasLegacyState = legacyMarketplace || plugins.some(
|
|
2274
|
+
(entry) => typeof entry.id === "string" && LEGACY_PLUGIN_IDS.includes(entry.id)
|
|
2275
|
+
);
|
|
2276
|
+
if (hasLegacyState) {
|
|
2277
|
+
diagnostics.push(
|
|
2278
|
+
"Legacy Claude thoth-agents marketplace or plugin state was detected and preserved; the host-specific identity will be managed independently."
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2215
2281
|
const namedMarketplaces = marketplaces.filter(
|
|
2216
2282
|
(entry) => entry.name === MARKETPLACE_NAME
|
|
2217
2283
|
);
|
|
@@ -2221,7 +2287,7 @@ function inspectClaudeManager(executor, cwd, scope) {
|
|
|
2221
2287
|
}
|
|
2222
2288
|
if (marketplace === "conflict") {
|
|
2223
2289
|
diagnostics.push(
|
|
2224
|
-
"A Claude marketplace named thoth-
|
|
2290
|
+
"A Claude marketplace named thoth-plugins is registered from a different source; resolve it through Claude Code before retrying."
|
|
2225
2291
|
);
|
|
2226
2292
|
}
|
|
2227
2293
|
const matchingPlugins = plugins.filter(
|
|
@@ -2246,7 +2312,7 @@ function commandItem(action, scope, projectRoot) {
|
|
|
2246
2312
|
kind: "native-marketplace",
|
|
2247
2313
|
action,
|
|
2248
2314
|
targetPath: MARKETPLACE_TARGET,
|
|
2249
|
-
description: "Register the
|
|
2315
|
+
description: "Register the central Thoth Claude marketplace.",
|
|
2250
2316
|
requiresBackup: false,
|
|
2251
2317
|
command: {
|
|
2252
2318
|
executable: "claude",
|
|
@@ -2254,7 +2320,7 @@ function commandItem(action, scope, projectRoot) {
|
|
|
2254
2320
|
"plugin",
|
|
2255
2321
|
"marketplace",
|
|
2256
2322
|
"add",
|
|
2257
|
-
|
|
2323
|
+
MARKETPLACE_INSTALL_SOURCE,
|
|
2258
2324
|
"--scope",
|
|
2259
2325
|
scope
|
|
2260
2326
|
],
|
|
@@ -2328,7 +2394,7 @@ function buildClaudeCodeSetupPlan(config) {
|
|
|
2328
2394
|
commandExecutor,
|
|
2329
2395
|
diagnostics: [
|
|
2330
2396
|
...inspection.diagnostics,
|
|
2331
|
-
"Claude Code installs thoth-agents from
|
|
2397
|
+
"Claude Code installs thoth-agents from the central thoth-plugins marketplace, while the thoth-agents repository remains the pinned plugin source.",
|
|
2332
2398
|
"Restart Claude Code or run /reload-plugins after installation; use /plugin to inspect marketplace and plugin state.",
|
|
2333
2399
|
"Provider capability is owned by the external provider and is not established by this thoth-agents setup plan."
|
|
2334
2400
|
],
|
|
@@ -3549,16 +3615,52 @@ function defaultClaudeCodeModelRoles() {
|
|
|
3549
3615
|
}
|
|
3550
3616
|
|
|
3551
3617
|
// src/cli/operations/codex.ts
|
|
3552
|
-
import { existsSync as existsSync8, readFileSync as
|
|
3618
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
3553
3619
|
import { basename as basename2 } from "path";
|
|
3554
3620
|
|
|
3555
3621
|
// src/cli/codex-plugin-install.ts
|
|
3556
3622
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3623
|
+
import {
|
|
3624
|
+
lstatSync,
|
|
3625
|
+
readdirSync,
|
|
3626
|
+
readFileSync as readFileSync6,
|
|
3627
|
+
realpathSync,
|
|
3628
|
+
rmSync as rmSync2
|
|
3629
|
+
} from "fs";
|
|
3630
|
+
import { isAbsolute, join as join4, relative, resolve as resolve2, sep } from "path";
|
|
3631
|
+
var PLUGIN_NAME2 = "thoth-agents";
|
|
3632
|
+
var MARKETPLACE_NAME2 = "thoth-plugins";
|
|
3633
|
+
var MARKETPLACE_SOURCE2 = "https://github.com/EremesNG/thoth-plugins.git";
|
|
3634
|
+
var PLUGIN_ID2 = `${PLUGIN_NAME2}@${MARKETPLACE_NAME2}`;
|
|
3635
|
+
var LEGACY_MARKETPLACE_SOURCE2 = "https://github.com/EremesNG/thoth-agents.git";
|
|
3636
|
+
var LEGACY_MARKETPLACE_NAMES2 = [PLUGIN_NAME2, `${PLUGIN_NAME2}-codex`];
|
|
3637
|
+
var LEGACY_PLUGIN_IDS2 = LEGACY_MARKETPLACE_NAMES2.map(
|
|
3638
|
+
(name) => `${PLUGIN_NAME2}@${name}`
|
|
3639
|
+
);
|
|
3640
|
+
var LEGACY_ROOTS = [
|
|
3641
|
+
{
|
|
3642
|
+
relativePath: "plugins/cache/thoth-agents",
|
|
3643
|
+
kind: "cache",
|
|
3644
|
+
marketplaceName: "thoth-agents"
|
|
3645
|
+
},
|
|
3646
|
+
{
|
|
3647
|
+
relativePath: "plugins/cache/thoth-agents-codex",
|
|
3648
|
+
kind: "cache",
|
|
3649
|
+
marketplaceName: "thoth-agents-codex"
|
|
3650
|
+
},
|
|
3651
|
+
{
|
|
3652
|
+
relativePath: ".tmp/marketplaces/thoth-agents",
|
|
3653
|
+
kind: "snapshot",
|
|
3654
|
+
marketplaceName: "thoth-agents"
|
|
3655
|
+
},
|
|
3656
|
+
{
|
|
3657
|
+
relativePath: ".tmp/marketplaces/thoth-agents-codex",
|
|
3658
|
+
kind: "snapshot",
|
|
3659
|
+
marketplaceName: "thoth-agents-codex"
|
|
3660
|
+
}
|
|
3661
|
+
];
|
|
3662
|
+
var MARKETPLACE_TARGET2 = `codex://marketplaces/${MARKETPLACE_NAME2}`;
|
|
3663
|
+
var PLUGIN_TARGET2 = `codex://plugins/${PLUGIN_ID2}`;
|
|
3562
3664
|
function getCodexCommand(args, options = {}) {
|
|
3563
3665
|
if ((options.platform ?? process.platform) !== "win32") {
|
|
3564
3666
|
return { command: "codex", args: [...args] };
|
|
@@ -3596,10 +3698,153 @@ function parseManagerRecord(content) {
|
|
|
3596
3698
|
return null;
|
|
3597
3699
|
}
|
|
3598
3700
|
}
|
|
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();
|
|
3701
|
+
function normalizeMarketplaceSource2(value) {
|
|
3702
|
+
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
3703
|
}
|
|
3602
|
-
function
|
|
3704
|
+
function lstatIfPresent(path) {
|
|
3705
|
+
try {
|
|
3706
|
+
return lstatSync(path);
|
|
3707
|
+
} catch (error) {
|
|
3708
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
3709
|
+
return null;
|
|
3710
|
+
}
|
|
3711
|
+
throw error;
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
function strictDescendant(base, candidate) {
|
|
3715
|
+
const suffix = relative(base, candidate);
|
|
3716
|
+
return suffix !== "" && suffix !== ".." && !suffix.startsWith(`..${sep}`) && !isAbsolute(suffix);
|
|
3717
|
+
}
|
|
3718
|
+
function parseObjectFile(path, label) {
|
|
3719
|
+
const file = lstatIfPresent(path);
|
|
3720
|
+
if (!file || file.isSymbolicLink() || !file.isFile()) {
|
|
3721
|
+
throw new Error(`${label} must be a regular non-link file`);
|
|
3722
|
+
}
|
|
3723
|
+
try {
|
|
3724
|
+
const value = JSON.parse(readFileSync6(path, "utf8"));
|
|
3725
|
+
if (!isRecord5(value)) throw new Error("not an object");
|
|
3726
|
+
return value;
|
|
3727
|
+
} catch (error) {
|
|
3728
|
+
throw new Error(
|
|
3729
|
+
`${label} is invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
3730
|
+
);
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
function validateCacheManifest(root) {
|
|
3734
|
+
const productEntries = readdirSync(root, { withFileTypes: true });
|
|
3735
|
+
if (productEntries.length === 0) return;
|
|
3736
|
+
if (productEntries.length !== 1 || productEntries[0]?.name !== PLUGIN_NAME2) {
|
|
3737
|
+
throw new Error(
|
|
3738
|
+
`Legacy cache ${root} contains state outside ${PLUGIN_NAME2}`
|
|
3739
|
+
);
|
|
3740
|
+
}
|
|
3741
|
+
const productRoot = join4(root, PLUGIN_NAME2);
|
|
3742
|
+
const product = lstatIfPresent(productRoot);
|
|
3743
|
+
if (!product || product.isSymbolicLink() || !product.isDirectory()) {
|
|
3744
|
+
throw new Error(`Legacy cache product root ${productRoot} is unsafe`);
|
|
3745
|
+
}
|
|
3746
|
+
const versions = readdirSync(productRoot, { withFileTypes: true });
|
|
3747
|
+
if (versions.length === 0) {
|
|
3748
|
+
throw new Error(`Legacy cache ${root} has no plugin manifest`);
|
|
3749
|
+
}
|
|
3750
|
+
for (const entry of versions) {
|
|
3751
|
+
const versionRoot = join4(productRoot, entry.name);
|
|
3752
|
+
const version = lstatIfPresent(versionRoot);
|
|
3753
|
+
if (!entry.isDirectory() || !version || version.isSymbolicLink() || !version.isDirectory()) {
|
|
3754
|
+
throw new Error(`Legacy cache version root ${versionRoot} is unsafe`);
|
|
3755
|
+
}
|
|
3756
|
+
const manifest = parseObjectFile(
|
|
3757
|
+
join4(versionRoot, ".codex-plugin", "plugin.json"),
|
|
3758
|
+
`Legacy cache manifest for ${versionRoot}`
|
|
3759
|
+
);
|
|
3760
|
+
if (manifest.name !== PLUGIN_NAME2) {
|
|
3761
|
+
throw new Error(
|
|
3762
|
+
`Legacy cache manifest for ${versionRoot} does not identify ${PLUGIN_NAME2}`
|
|
3763
|
+
);
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
function validateSnapshotManifest(root, marketplaceName) {
|
|
3768
|
+
if (readdirSync(root).length === 0) return;
|
|
3769
|
+
const installation = parseObjectFile(
|
|
3770
|
+
join4(root, ".codex-marketplace-install.json"),
|
|
3771
|
+
`Legacy marketplace receipt for ${marketplaceName}`
|
|
3772
|
+
);
|
|
3773
|
+
if (typeof installation.source !== "string" || normalizeMarketplaceSource2(installation.source) !== normalizeMarketplaceSource2(LEGACY_MARKETPLACE_SOURCE2)) {
|
|
3774
|
+
throw new Error(
|
|
3775
|
+
`Legacy marketplace snapshot ${marketplaceName} has conflicting provenance`
|
|
3776
|
+
);
|
|
3777
|
+
}
|
|
3778
|
+
const marketplace = parseObjectFile(
|
|
3779
|
+
join4(root, ".agents", "plugins", "marketplace.json"),
|
|
3780
|
+
`Legacy marketplace manifest for ${marketplaceName}`
|
|
3781
|
+
);
|
|
3782
|
+
const plugins = Array.isArray(marketplace.plugins) ? marketplace.plugins : [];
|
|
3783
|
+
const pluginNames = plugins.map(
|
|
3784
|
+
(plugin) => isRecord5(plugin) ? plugin.name : null
|
|
3785
|
+
);
|
|
3786
|
+
if (marketplace.name !== marketplaceName || pluginNames.length === 0 || pluginNames.some((name) => name !== PLUGIN_NAME2)) {
|
|
3787
|
+
throw new Error(
|
|
3788
|
+
`Legacy marketplace snapshot ${marketplaceName} does not identify only ${PLUGIN_NAME2}`
|
|
3789
|
+
);
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
function validateLegacyRoot(codexHome, definition, expectedRealPath) {
|
|
3793
|
+
const path = resolve2(codexHome, ...definition.relativePath.split("/"));
|
|
3794
|
+
if (!strictDescendant(codexHome, path)) {
|
|
3795
|
+
throw new Error(
|
|
3796
|
+
`Legacy cleanup target escapes CODEX_HOME: ${definition.relativePath}`
|
|
3797
|
+
);
|
|
3798
|
+
}
|
|
3799
|
+
const target = lstatIfPresent(path);
|
|
3800
|
+
if (!target) return null;
|
|
3801
|
+
const home = lstatIfPresent(codexHome);
|
|
3802
|
+
if (!home || !home.isDirectory()) {
|
|
3803
|
+
throw new Error(`Resolved CODEX_HOME is not a directory: ${codexHome}`);
|
|
3804
|
+
}
|
|
3805
|
+
let boundary = codexHome;
|
|
3806
|
+
for (const segment of definition.relativePath.split("/")) {
|
|
3807
|
+
boundary = join4(boundary, segment);
|
|
3808
|
+
const value = lstatIfPresent(boundary);
|
|
3809
|
+
if (!value || value.isSymbolicLink() || !value.isDirectory()) {
|
|
3810
|
+
throw new Error(
|
|
3811
|
+
`Legacy cleanup boundary is not a real directory: ${boundary}`
|
|
3812
|
+
);
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
const realHome = realpathSync(codexHome);
|
|
3816
|
+
const realPath = realpathSync(path);
|
|
3817
|
+
if (!strictDescendant(realHome, realPath)) {
|
|
3818
|
+
throw new Error(
|
|
3819
|
+
`Legacy cleanup target resolves outside CODEX_HOME: ${definition.relativePath}`
|
|
3820
|
+
);
|
|
3821
|
+
}
|
|
3822
|
+
if (expectedRealPath && realPath !== expectedRealPath) {
|
|
3823
|
+
throw new Error(
|
|
3824
|
+
`Legacy cleanup target changed after preflight: ${definition.relativePath}`
|
|
3825
|
+
);
|
|
3826
|
+
}
|
|
3827
|
+
if (definition.kind === "cache") validateCacheManifest(path);
|
|
3828
|
+
else validateSnapshotManifest(path, definition.marketplaceName);
|
|
3829
|
+
return {
|
|
3830
|
+
kind: definition.kind,
|
|
3831
|
+
marketplaceName: definition.marketplaceName,
|
|
3832
|
+
path,
|
|
3833
|
+
realPath,
|
|
3834
|
+
relativePath: definition.relativePath
|
|
3835
|
+
};
|
|
3836
|
+
}
|
|
3837
|
+
function preflightLegacyRoots(codexHomeInput) {
|
|
3838
|
+
if (typeof codexHomeInput !== "string" || codexHomeInput.trim() === "") {
|
|
3839
|
+
throw new Error("CODEX_HOME could not be resolved");
|
|
3840
|
+
}
|
|
3841
|
+
const codexHome = resolve2(codexHomeInput);
|
|
3842
|
+
const roots = LEGACY_ROOTS.map(
|
|
3843
|
+
(definition) => validateLegacyRoot(codexHome, definition)
|
|
3844
|
+
).filter((root) => root !== null);
|
|
3845
|
+
return { codexHome, roots };
|
|
3846
|
+
}
|
|
3847
|
+
function marketplaceSources2(entry) {
|
|
3603
3848
|
const sources = [entry.repo, entry.source];
|
|
3604
3849
|
if (isRecord5(entry.marketplaceSource)) {
|
|
3605
3850
|
sources.push(entry.marketplaceSource.repo, entry.marketplaceSource.source);
|
|
@@ -3609,13 +3854,16 @@ function marketplaceSources(entry) {
|
|
|
3609
3854
|
);
|
|
3610
3855
|
}
|
|
3611
3856
|
function isCanonicalMarketplace2(entry) {
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3857
|
+
return isMarketplaceIdentity2(entry, MARKETPLACE_NAME2);
|
|
3858
|
+
}
|
|
3859
|
+
function isMarketplaceIdentity2(entry, name, source = MARKETPLACE_SOURCE2) {
|
|
3860
|
+
if (entry.name !== name) return false;
|
|
3861
|
+
const canonical = normalizeMarketplaceSource2(source);
|
|
3862
|
+
return marketplaceSources2(entry).some(
|
|
3863
|
+
(source2) => normalizeMarketplaceSource2(source2) === canonical
|
|
3616
3864
|
);
|
|
3617
3865
|
}
|
|
3618
|
-
function inspectCodexManager(executor, cwd) {
|
|
3866
|
+
function inspectCodexManager(executor, cwd, expectedVersion) {
|
|
3619
3867
|
const marketplaceResult = executor(
|
|
3620
3868
|
"codex",
|
|
3621
3869
|
["plugin", "marketplace", "list", "--json"],
|
|
@@ -3635,6 +3883,9 @@ function inspectCodexManager(executor, cwd) {
|
|
|
3635
3883
|
ready: false,
|
|
3636
3884
|
marketplace: "unknown",
|
|
3637
3885
|
plugin: "unknown",
|
|
3886
|
+
legacyPluginIds: [],
|
|
3887
|
+
legacyMarketplaceNames: [],
|
|
3888
|
+
legacyMarketplaceConflicts: [],
|
|
3638
3889
|
diagnostics
|
|
3639
3890
|
};
|
|
3640
3891
|
}
|
|
@@ -3650,9 +3901,35 @@ function inspectCodexManager(executor, cwd) {
|
|
|
3650
3901
|
ready: false,
|
|
3651
3902
|
marketplace: "unknown",
|
|
3652
3903
|
plugin: "unknown",
|
|
3904
|
+
legacyPluginIds: [],
|
|
3905
|
+
legacyMarketplaceNames: [],
|
|
3906
|
+
legacyMarketplaceConflicts: [],
|
|
3653
3907
|
diagnostics
|
|
3654
3908
|
};
|
|
3655
3909
|
}
|
|
3910
|
+
const legacyMarketplaceNames = LEGACY_MARKETPLACE_NAMES2.filter(
|
|
3911
|
+
(name) => marketplaces.some(
|
|
3912
|
+
(entry) => isMarketplaceIdentity2(entry, name, LEGACY_MARKETPLACE_SOURCE2)
|
|
3913
|
+
)
|
|
3914
|
+
);
|
|
3915
|
+
const legacyMarketplaceConflicts = LEGACY_MARKETPLACE_NAMES2.filter(
|
|
3916
|
+
(name) => marketplaces.some(
|
|
3917
|
+
(entry) => entry.name === name && !isMarketplaceIdentity2(entry, name, LEGACY_MARKETPLACE_SOURCE2)
|
|
3918
|
+
)
|
|
3919
|
+
);
|
|
3920
|
+
const legacyPluginIds = LEGACY_PLUGIN_IDS2.filter(
|
|
3921
|
+
(pluginId) => installedPlugins.some((entry) => entry.pluginId === pluginId)
|
|
3922
|
+
);
|
|
3923
|
+
if (legacyPluginIds.length > 0 || legacyMarketplaceNames.length > 0) {
|
|
3924
|
+
diagnostics.push(
|
|
3925
|
+
"Legacy Codex thoth-agents state was detected; cleanup is planned only after the central plugin is verified."
|
|
3926
|
+
);
|
|
3927
|
+
}
|
|
3928
|
+
if (legacyMarketplaceConflicts.length > 0) {
|
|
3929
|
+
diagnostics.push(
|
|
3930
|
+
`Legacy Codex marketplace provenance conflicts were found for ${legacyMarketplaceConflicts.join(", ")}; no manager mutation will be attempted.`
|
|
3931
|
+
);
|
|
3932
|
+
}
|
|
3656
3933
|
const namedMarketplaces = marketplaces.filter(
|
|
3657
3934
|
(entry) => entry.name === MARKETPLACE_NAME2
|
|
3658
3935
|
);
|
|
@@ -3662,31 +3939,81 @@ function inspectCodexManager(executor, cwd) {
|
|
|
3662
3939
|
}
|
|
3663
3940
|
if (marketplace === "conflict") {
|
|
3664
3941
|
diagnostics.push(
|
|
3665
|
-
"A Codex marketplace named thoth-
|
|
3942
|
+
"A Codex marketplace named thoth-plugins is registered from a different source; resolve it through Codex before retrying."
|
|
3666
3943
|
);
|
|
3667
3944
|
}
|
|
3668
3945
|
const matchingPlugins = installedPlugins.filter(
|
|
3669
3946
|
(entry) => entry.pluginId === PLUGIN_ID2
|
|
3670
3947
|
);
|
|
3671
3948
|
let plugin = "absent";
|
|
3672
|
-
if (matchingPlugins.some(
|
|
3949
|
+
if (matchingPlugins.some(
|
|
3950
|
+
(entry) => entry.enabled === true && entry.version === expectedVersion
|
|
3951
|
+
)) {
|
|
3673
3952
|
plugin = "installed";
|
|
3953
|
+
} else if (matchingPlugins.some((entry) => entry.enabled === true)) {
|
|
3954
|
+
plugin = "outdated";
|
|
3674
3955
|
} else if (matchingPlugins.length > 0) {
|
|
3675
3956
|
plugin = "disabled";
|
|
3676
3957
|
}
|
|
3677
3958
|
return {
|
|
3678
|
-
ready: marketplace !== "conflict",
|
|
3959
|
+
ready: marketplace !== "conflict" && legacyMarketplaceConflicts.length === 0,
|
|
3679
3960
|
marketplace,
|
|
3680
3961
|
plugin,
|
|
3962
|
+
legacyPluginIds,
|
|
3963
|
+
legacyMarketplaceNames,
|
|
3964
|
+
legacyMarketplaceConflicts,
|
|
3681
3965
|
diagnostics
|
|
3682
3966
|
};
|
|
3683
3967
|
}
|
|
3968
|
+
function centralPluginVerified(inspection) {
|
|
3969
|
+
return inspection.ready && inspection.marketplace === "installed" && inspection.plugin === "installed";
|
|
3970
|
+
}
|
|
3971
|
+
function legacyManagerClean(inspection) {
|
|
3972
|
+
return inspection.legacyPluginIds.length === 0 && inspection.legacyMarketplaceNames.length === 0;
|
|
3973
|
+
}
|
|
3974
|
+
function legacyRootsAbsent(codexHome) {
|
|
3975
|
+
return LEGACY_ROOTS.every(
|
|
3976
|
+
({ relativePath }) => !lstatIfPresent(resolve2(codexHome, ...relativePath.split("/")))
|
|
3977
|
+
);
|
|
3978
|
+
}
|
|
3979
|
+
function legacyPluginCommandItem(pluginId, projectRoot) {
|
|
3980
|
+
return {
|
|
3981
|
+
action: "remove-legacy-plugin",
|
|
3982
|
+
targetPath: `codex://plugins/${pluginId}`,
|
|
3983
|
+
description: `Remove the registered legacy Codex plugin ${pluginId}.`,
|
|
3984
|
+
command: {
|
|
3985
|
+
executable: "codex",
|
|
3986
|
+
args: ["plugin", "remove", pluginId, "--json"],
|
|
3987
|
+
cwd: projectRoot
|
|
3988
|
+
}
|
|
3989
|
+
};
|
|
3990
|
+
}
|
|
3991
|
+
function legacyMarketplaceCommandItem(marketplaceName, projectRoot) {
|
|
3992
|
+
return {
|
|
3993
|
+
action: "remove-legacy-marketplace",
|
|
3994
|
+
targetPath: `codex://marketplaces/${marketplaceName}`,
|
|
3995
|
+
description: `Remove the registered legacy Codex marketplace ${marketplaceName}.`,
|
|
3996
|
+
command: {
|
|
3997
|
+
executable: "codex",
|
|
3998
|
+
args: ["plugin", "marketplace", "remove", marketplaceName, "--json"],
|
|
3999
|
+
cwd: projectRoot
|
|
4000
|
+
}
|
|
4001
|
+
};
|
|
4002
|
+
}
|
|
4003
|
+
function legacyRootPlanItem(root) {
|
|
4004
|
+
return {
|
|
4005
|
+
action: "remove-legacy-root",
|
|
4006
|
+
targetPath: root.path,
|
|
4007
|
+
description: `Remove the preflight-approved legacy Codex root ${root.relativePath}.`,
|
|
4008
|
+
cleanupRoot: root
|
|
4009
|
+
};
|
|
4010
|
+
}
|
|
3684
4011
|
function commandItem2(action, projectRoot) {
|
|
3685
4012
|
if (action === "register-marketplace") {
|
|
3686
4013
|
return {
|
|
3687
4014
|
action,
|
|
3688
4015
|
targetPath: MARKETPLACE_TARGET2,
|
|
3689
|
-
description: "Register the
|
|
4016
|
+
description: "Register the central Thoth Codex marketplace.",
|
|
3690
4017
|
command: {
|
|
3691
4018
|
executable: "codex",
|
|
3692
4019
|
args: ["plugin", "marketplace", "add", MARKETPLACE_SOURCE2, "--json"],
|
|
@@ -3707,25 +4034,54 @@ function commandItem2(action, projectRoot) {
|
|
|
3707
4034
|
}
|
|
3708
4035
|
function buildCodexPluginSetupPlan(config) {
|
|
3709
4036
|
const commandExecutor = config.commandExecutor ?? defaultCommandExecutor3;
|
|
3710
|
-
const inspection = inspectCodexManager(
|
|
3711
|
-
|
|
4037
|
+
const inspection = inspectCodexManager(
|
|
4038
|
+
commandExecutor,
|
|
4039
|
+
config.projectRoot,
|
|
4040
|
+
config.expectedVersion
|
|
4041
|
+
);
|
|
4042
|
+
let cleanupPreflight;
|
|
4043
|
+
let cleanupPreflightError;
|
|
3712
4044
|
if (inspection.ready) {
|
|
4045
|
+
try {
|
|
4046
|
+
cleanupPreflight = preflightLegacyRoots(
|
|
4047
|
+
getCodexHome({ codexHome: config.codexHome, homeDir: config.homeDir })
|
|
4048
|
+
);
|
|
4049
|
+
} catch (error) {
|
|
4050
|
+
cleanupPreflightError = `Legacy cleanup preflight failed: ${error instanceof Error ? error.message : String(error)}. No manager mutation will be attempted.`;
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
const ready = inspection.ready && cleanupPreflightError === void 0;
|
|
4054
|
+
const items = [];
|
|
4055
|
+
if (ready) {
|
|
3713
4056
|
if (inspection.marketplace === "absent") {
|
|
3714
4057
|
items.push(commandItem2("register-marketplace", config.projectRoot));
|
|
3715
4058
|
}
|
|
3716
|
-
if (inspection.plugin === "absent" || inspection.plugin === "disabled") {
|
|
4059
|
+
if (inspection.plugin === "absent" || inspection.plugin === "disabled" || inspection.plugin === "outdated") {
|
|
3717
4060
|
items.push(commandItem2("install-plugin", config.projectRoot));
|
|
3718
4061
|
}
|
|
4062
|
+
items.push(
|
|
4063
|
+
...inspection.legacyPluginIds.map(
|
|
4064
|
+
(pluginId) => legacyPluginCommandItem(pluginId, config.projectRoot)
|
|
4065
|
+
),
|
|
4066
|
+
...inspection.legacyMarketplaceNames.map(
|
|
4067
|
+
(marketplaceName) => legacyMarketplaceCommandItem(marketplaceName, config.projectRoot)
|
|
4068
|
+
),
|
|
4069
|
+
...cleanupPreflight?.roots.map(legacyRootPlanItem) ?? []
|
|
4070
|
+
);
|
|
3719
4071
|
}
|
|
3720
4072
|
return {
|
|
4073
|
+
codexHome: cleanupPreflight?.codexHome ?? getCodexHome({ codexHome: config.codexHome, homeDir: config.homeDir }),
|
|
3721
4074
|
dryRun: config.dryRun === true,
|
|
4075
|
+
expectedVersion: config.expectedVersion,
|
|
3722
4076
|
items,
|
|
3723
|
-
ready
|
|
4077
|
+
ready,
|
|
3724
4078
|
projectRoot: config.projectRoot,
|
|
3725
4079
|
commandExecutor,
|
|
3726
4080
|
diagnostics: [
|
|
3727
4081
|
...inspection.diagnostics,
|
|
3728
|
-
|
|
4082
|
+
...cleanupPreflightError ? [cleanupPreflightError] : [],
|
|
4083
|
+
"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.",
|
|
4084
|
+
"Close every Codex process before applying legacy cleanup; process-name detection is not a portable or race-free safety boundary.",
|
|
3729
4085
|
"Restart Codex after installation; use /plugins to inspect marketplace and plugin state."
|
|
3730
4086
|
]
|
|
3731
4087
|
};
|
|
@@ -3749,12 +4105,28 @@ function applyCodexPluginSetup(plan) {
|
|
|
3749
4105
|
};
|
|
3750
4106
|
}
|
|
3751
4107
|
if (plan.dryRun) return { success: true, changed, diagnostics };
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
4108
|
+
const installationItems = plan.items.filter(
|
|
4109
|
+
(item) => item.action === "register-marketplace" || item.action === "install-plugin"
|
|
4110
|
+
);
|
|
4111
|
+
const cleanupItems = plan.items.filter(
|
|
4112
|
+
(item) => item.action === "remove-legacy-plugin" || item.action === "remove-legacy-marketplace"
|
|
4113
|
+
);
|
|
4114
|
+
const rootItems = plan.items.filter(
|
|
4115
|
+
(item) => item.action === "remove-legacy-root"
|
|
4116
|
+
);
|
|
4117
|
+
for (const item of installationItems) {
|
|
4118
|
+
const command = item.command;
|
|
4119
|
+
if (!command) {
|
|
4120
|
+
return {
|
|
4121
|
+
success: false,
|
|
4122
|
+
changed,
|
|
4123
|
+
diagnostics,
|
|
4124
|
+
error: `Codex plugin setup plan item ${item.action} has no command.`
|
|
4125
|
+
};
|
|
4126
|
+
}
|
|
4127
|
+
const result = plan.commandExecutor(command.executable, command.args, {
|
|
4128
|
+
cwd: command.cwd
|
|
4129
|
+
});
|
|
3758
4130
|
if (result.exitCode !== 0) {
|
|
3759
4131
|
return {
|
|
3760
4132
|
success: false,
|
|
@@ -3767,10 +4139,11 @@ function applyCodexPluginSetup(plan) {
|
|
|
3767
4139
|
}
|
|
3768
4140
|
const inspection = inspectCodexManager(
|
|
3769
4141
|
plan.commandExecutor,
|
|
3770
|
-
plan.projectRoot
|
|
4142
|
+
plan.projectRoot,
|
|
4143
|
+
plan.expectedVersion
|
|
3771
4144
|
);
|
|
3772
4145
|
diagnostics.push(...inspection.diagnostics);
|
|
3773
|
-
if (inspection
|
|
4146
|
+
if (!centralPluginVerified(inspection)) {
|
|
3774
4147
|
return {
|
|
3775
4148
|
success: false,
|
|
3776
4149
|
changed,
|
|
@@ -3778,11 +4151,112 @@ function applyCodexPluginSetup(plan) {
|
|
|
3778
4151
|
error: "Codex did not verify the expected marketplace and enabled plugin after mutation."
|
|
3779
4152
|
};
|
|
3780
4153
|
}
|
|
4154
|
+
for (const item of cleanupItems) {
|
|
4155
|
+
const command = item.command;
|
|
4156
|
+
if (!command) {
|
|
4157
|
+
return {
|
|
4158
|
+
success: false,
|
|
4159
|
+
changed,
|
|
4160
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4161
|
+
error: `Codex plugin cleanup plan item ${item.action} has no command.`
|
|
4162
|
+
};
|
|
4163
|
+
}
|
|
4164
|
+
const result = plan.commandExecutor(command.executable, command.args, {
|
|
4165
|
+
cwd: command.cwd
|
|
4166
|
+
});
|
|
4167
|
+
if (result.exitCode !== 0) {
|
|
4168
|
+
return {
|
|
4169
|
+
success: false,
|
|
4170
|
+
changed,
|
|
4171
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4172
|
+
error: `${boundedCommandFailure2(item, result)} Central ${PLUGIN_ID2} remains installed; close Codex and retry.`
|
|
4173
|
+
};
|
|
4174
|
+
}
|
|
4175
|
+
changed.push(item.targetPath);
|
|
4176
|
+
}
|
|
4177
|
+
const managerCleanupInspection = inspectCodexManager(
|
|
4178
|
+
plan.commandExecutor,
|
|
4179
|
+
plan.projectRoot,
|
|
4180
|
+
plan.expectedVersion
|
|
4181
|
+
);
|
|
4182
|
+
diagnostics.push(...managerCleanupInspection.diagnostics);
|
|
4183
|
+
if (!centralPluginVerified(managerCleanupInspection) || !legacyManagerClean(managerCleanupInspection)) {
|
|
4184
|
+
return {
|
|
4185
|
+
success: false,
|
|
4186
|
+
changed,
|
|
4187
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4188
|
+
error: `Central ${PLUGIN_ID2} remains installed, but exact legacy manager cleanup did not converge; close Codex and retry.`
|
|
4189
|
+
};
|
|
4190
|
+
}
|
|
4191
|
+
for (const item of rootItems) {
|
|
4192
|
+
const root = item.cleanupRoot;
|
|
4193
|
+
const definition = root ? LEGACY_ROOTS.find(
|
|
4194
|
+
(candidate) => candidate.relativePath === root.relativePath
|
|
4195
|
+
) : void 0;
|
|
4196
|
+
if (!root || !definition) {
|
|
4197
|
+
return {
|
|
4198
|
+
success: false,
|
|
4199
|
+
changed,
|
|
4200
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4201
|
+
error: `Central ${PLUGIN_ID2} remains installed, but the fixed legacy cleanup plan is invalid; close Codex and retry.`
|
|
4202
|
+
};
|
|
4203
|
+
}
|
|
4204
|
+
const exactPath = resolve2(
|
|
4205
|
+
plan.codexHome,
|
|
4206
|
+
...definition.relativePath.split("/")
|
|
4207
|
+
);
|
|
4208
|
+
if (resolve2(root.path) !== exactPath || resolve2(item.targetPath) !== exactPath || root.kind !== definition.kind || root.marketplaceName !== definition.marketplaceName) {
|
|
4209
|
+
return {
|
|
4210
|
+
success: false,
|
|
4211
|
+
changed,
|
|
4212
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4213
|
+
error: `Central ${PLUGIN_ID2} remains installed, but the fixed legacy cleanup target changed; close Codex and retry.`
|
|
4214
|
+
};
|
|
4215
|
+
}
|
|
4216
|
+
if (!lstatIfPresent(exactPath)) continue;
|
|
4217
|
+
try {
|
|
4218
|
+
const validated = validateLegacyRoot(
|
|
4219
|
+
plan.codexHome,
|
|
4220
|
+
definition,
|
|
4221
|
+
root.realPath
|
|
4222
|
+
);
|
|
4223
|
+
if (!validated) continue;
|
|
4224
|
+
rmSync2(validated.path, { recursive: true, force: false });
|
|
4225
|
+
if (lstatIfPresent(validated.path)) {
|
|
4226
|
+
throw new Error(
|
|
4227
|
+
`Legacy cleanup target still exists: ${root.relativePath}`
|
|
4228
|
+
);
|
|
4229
|
+
}
|
|
4230
|
+
changed.push(item.targetPath);
|
|
4231
|
+
} catch (error) {
|
|
4232
|
+
const detail = (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").slice(0, 240);
|
|
4233
|
+
return {
|
|
4234
|
+
success: false,
|
|
4235
|
+
changed,
|
|
4236
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4237
|
+
error: `Central ${PLUGIN_ID2} remains installed; legacy root cleanup failed${detail ? `: ${detail}` : ""}. Close Codex and retry.`
|
|
4238
|
+
};
|
|
4239
|
+
}
|
|
4240
|
+
}
|
|
4241
|
+
const finalInspection = inspectCodexManager(
|
|
4242
|
+
plan.commandExecutor,
|
|
4243
|
+
plan.projectRoot,
|
|
4244
|
+
plan.expectedVersion
|
|
4245
|
+
);
|
|
4246
|
+
diagnostics.push(...finalInspection.diagnostics);
|
|
4247
|
+
if (!centralPluginVerified(finalInspection) || !legacyManagerClean(finalInspection) || !legacyRootsAbsent(plan.codexHome)) {
|
|
4248
|
+
return {
|
|
4249
|
+
success: false,
|
|
4250
|
+
changed,
|
|
4251
|
+
diagnostics: uniqueMessages3(diagnostics),
|
|
4252
|
+
error: `Central ${PLUGIN_ID2} remains installed, but exact legacy cleanup did not converge; close Codex and retry.`
|
|
4253
|
+
};
|
|
4254
|
+
}
|
|
3781
4255
|
return { success: true, changed, diagnostics: uniqueMessages3(diagnostics) };
|
|
3782
4256
|
}
|
|
3783
4257
|
function formatCodexPluginSetupPlan(plan) {
|
|
3784
4258
|
const lines = plan.items.map(
|
|
3785
|
-
(item) => `- ${item.action}: ${item.command.executable} ${item.command.args.join(" ")}`
|
|
4259
|
+
(item) => item.command ? `- ${item.action}: ${item.command.executable} ${item.command.args.join(" ")}` : `- ${item.action}: ${item.cleanupRoot?.relativePath ?? item.targetPath}`
|
|
3786
4260
|
);
|
|
3787
4261
|
return ["Codex plugin setup plan:", ...lines].join("\n");
|
|
3788
4262
|
}
|
|
@@ -3910,7 +4384,7 @@ function backupForItem(item) {
|
|
|
3910
4384
|
function rootBlockState(item) {
|
|
3911
4385
|
if (!existsSync8(item.targetPath))
|
|
3912
4386
|
return { state: "missing", observed: "absent" };
|
|
3913
|
-
const content =
|
|
4387
|
+
const content = readFileSync7(item.targetPath, "utf8");
|
|
3914
4388
|
if (item.content && content.includes(item.content)) {
|
|
3915
4389
|
return { state: "installed", observed: "managed root block present" };
|
|
3916
4390
|
}
|
|
@@ -3923,11 +4397,11 @@ function managedModelState(item) {
|
|
|
3923
4397
|
if (!existsSync8(item.targetPath))
|
|
3924
4398
|
return { state: "missing", observed: "absent" };
|
|
3925
4399
|
try {
|
|
3926
|
-
const parsed = JSON.parse(
|
|
4400
|
+
const parsed = JSON.parse(readFileSync7(item.targetPath, "utf8"));
|
|
3927
4401
|
if (parsed.version !== MANAGED_MODEL_STATE_VERSION || !parsed.models || typeof parsed.models !== "object" || Array.isArray(parsed.models)) {
|
|
3928
4402
|
return { state: "unknown", observed: "invalid managed model state" };
|
|
3929
4403
|
}
|
|
3930
|
-
return item.content ===
|
|
4404
|
+
return item.content === readFileSync7(item.targetPath, "utf8") ? { state: "installed", observed: "managed model state current" } : { state: "drift", observed: "managed model state differs" };
|
|
3931
4405
|
} catch {
|
|
3932
4406
|
return { state: "unknown", observed: "unparseable managed model state" };
|
|
3933
4407
|
}
|
|
@@ -3935,7 +4409,7 @@ function managedModelState(item) {
|
|
|
3935
4409
|
function userConfigState(item) {
|
|
3936
4410
|
if (!existsSync8(item.targetPath))
|
|
3937
4411
|
return { state: "missing", observed: "absent" };
|
|
3938
|
-
const content =
|
|
4412
|
+
const content = readFileSync7(item.targetPath, "utf8");
|
|
3939
4413
|
if (/^\s*default_mode_request_user_input\s*=\s*true\b/m.test(content)) {
|
|
3940
4414
|
return {
|
|
3941
4415
|
state: "installed",
|
|
@@ -3950,7 +4424,7 @@ function userConfigState(item) {
|
|
|
3950
4424
|
function contentState(item) {
|
|
3951
4425
|
if (!existsSync8(item.targetPath))
|
|
3952
4426
|
return { state: "missing", observed: "absent" };
|
|
3953
|
-
const observed =
|
|
4427
|
+
const observed = readFileSync7(item.targetPath, "utf8");
|
|
3954
4428
|
if (item.content === observed)
|
|
3955
4429
|
return { state: "installed", observed: "current" };
|
|
3956
4430
|
if (item.role) {
|
|
@@ -4117,7 +4591,7 @@ function planItemFromPluginSetup(item) {
|
|
|
4117
4591
|
path: item.targetPath,
|
|
4118
4592
|
label: "Codex native plugin manager target"
|
|
4119
4593
|
},
|
|
4120
|
-
preview: `${item.command.executable} ${item.command.args.join(" ")}`,
|
|
4594
|
+
preview: item.command ? `${item.command.executable} ${item.command.args.join(" ")}` : `Remove exact preflight-approved Codex root ${item.cleanupRoot?.relativePath ?? item.targetPath}`,
|
|
4121
4595
|
backup: { required: false, strategy: "external" }
|
|
4122
4596
|
};
|
|
4123
4597
|
}
|
|
@@ -4215,7 +4689,10 @@ function buildCompleteCodexPlan(action, context2) {
|
|
|
4215
4689
|
const packageVersion = resolveVersion();
|
|
4216
4690
|
const buildPlugin = context2.buildCodexPluginSetupPlan ?? buildCodexPluginSetupPlan;
|
|
4217
4691
|
const pluginPlan = buildPlugin({
|
|
4692
|
+
codexHome: context2.codexHome,
|
|
4218
4693
|
dryRun: true,
|
|
4694
|
+
expectedVersion: packageVersion.ok ? packageVersion.version : "0.0.0",
|
|
4695
|
+
homeDir: context2.homeDir,
|
|
4219
4696
|
projectRoot: context2.cwd,
|
|
4220
4697
|
commandExecutor: context2.codexPluginCommandExecutor
|
|
4221
4698
|
});
|
|
@@ -4611,7 +5088,7 @@ function applyCodexPlan(plan) {
|
|
|
4611
5088
|
// src/cli/operations/opencode.ts
|
|
4612
5089
|
import { createHash as createHash3 } from "crypto";
|
|
4613
5090
|
import { existsSync as existsSync10 } from "fs";
|
|
4614
|
-
import { join as
|
|
5091
|
+
import { join as join6 } from "path";
|
|
4615
5092
|
|
|
4616
5093
|
// src/cli/opencode-effort.ts
|
|
4617
5094
|
var OPENAI_GPT_RUNTIME_VARIANTS = /* @__PURE__ */ new Set([
|
|
@@ -4650,11 +5127,11 @@ import {
|
|
|
4650
5127
|
mkdirSync as mkdirSync4,
|
|
4651
5128
|
mkdtempSync,
|
|
4652
5129
|
renameSync as renameSync3,
|
|
4653
|
-
rmSync as
|
|
5130
|
+
rmSync as rmSync3,
|
|
4654
5131
|
statSync
|
|
4655
5132
|
} from "fs";
|
|
4656
5133
|
import { homedir as homedir4 } from "os";
|
|
4657
|
-
import { dirname as dirname5, join as
|
|
5134
|
+
import { dirname as dirname5, join as join5 } from "path";
|
|
4658
5135
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4659
5136
|
|
|
4660
5137
|
// src/harness/core/owned-skills.ts
|
|
@@ -4684,8 +5161,8 @@ function getOpenCodeOwnedSkillEntries(options = {}) {
|
|
|
4684
5161
|
const homeDir = options.homeDir ?? homedir4();
|
|
4685
5162
|
return THOTH_OWNED_SKILL_NAMES.map((name) => ({
|
|
4686
5163
|
name,
|
|
4687
|
-
sourcePath:
|
|
4688
|
-
destinationPath:
|
|
5164
|
+
sourcePath: join5(packageRoot, "skills", name),
|
|
5165
|
+
destinationPath: join5(homeDir, ".config", "opencode", "skills", name)
|
|
4689
5166
|
}));
|
|
4690
5167
|
}
|
|
4691
5168
|
function validateCanonicalSkill(entry) {
|
|
@@ -4694,7 +5171,7 @@ function validateCanonicalSkill(entry) {
|
|
|
4694
5171
|
`Canonical owned skill directory is missing: ${entry.name}`
|
|
4695
5172
|
);
|
|
4696
5173
|
}
|
|
4697
|
-
const contractPath =
|
|
5174
|
+
const contractPath = join5(entry.sourcePath, "SKILL.md");
|
|
4698
5175
|
if (!existsSync9(contractPath) || !statSync(contractPath).isFile()) {
|
|
4699
5176
|
throw new Error(`Canonical owned skill contract is missing: ${entry.name}`);
|
|
4700
5177
|
}
|
|
@@ -4704,21 +5181,21 @@ function installOwnedSkills(skills) {
|
|
|
4704
5181
|
if (!firstSkill) return;
|
|
4705
5182
|
const globalRoot = dirname5(firstSkill.destinationPath);
|
|
4706
5183
|
mkdirSync4(globalRoot, { recursive: true });
|
|
4707
|
-
const transactionRoot = mkdtempSync(
|
|
4708
|
-
const stagedRoot =
|
|
4709
|
-
const backupRoot =
|
|
5184
|
+
const transactionRoot = mkdtempSync(join5(globalRoot, ".thoth-agents-owned-"));
|
|
5185
|
+
const stagedRoot = join5(transactionRoot, "staged");
|
|
5186
|
+
const backupRoot = join5(transactionRoot, "backup");
|
|
4710
5187
|
mkdirSync4(stagedRoot, { recursive: true });
|
|
4711
5188
|
mkdirSync4(backupRoot, { recursive: true });
|
|
4712
5189
|
try {
|
|
4713
5190
|
for (const skill of skills) {
|
|
4714
|
-
cpSync(skill.sourcePath,
|
|
5191
|
+
cpSync(skill.sourcePath, join5(stagedRoot, skill.name), {
|
|
4715
5192
|
recursive: true,
|
|
4716
5193
|
force: true
|
|
4717
5194
|
});
|
|
4718
5195
|
}
|
|
4719
5196
|
for (const skill of skills) {
|
|
4720
|
-
const stagedPath =
|
|
4721
|
-
const backupPath =
|
|
5197
|
+
const stagedPath = join5(stagedRoot, skill.name);
|
|
5198
|
+
const backupPath = join5(backupRoot, skill.name);
|
|
4722
5199
|
const hadExistingDestination = existsSync9(skill.destinationPath);
|
|
4723
5200
|
if (hadExistingDestination) {
|
|
4724
5201
|
renameSync3(skill.destinationPath, backupPath);
|
|
@@ -4727,7 +5204,7 @@ function installOwnedSkills(skills) {
|
|
|
4727
5204
|
renameSync3(stagedPath, skill.destinationPath);
|
|
4728
5205
|
} catch (error) {
|
|
4729
5206
|
if (existsSync9(skill.destinationPath)) {
|
|
4730
|
-
|
|
5207
|
+
rmSync3(skill.destinationPath, { recursive: true, force: true });
|
|
4731
5208
|
}
|
|
4732
5209
|
if (hadExistingDestination && existsSync9(backupPath)) {
|
|
4733
5210
|
renameSync3(backupPath, skill.destinationPath);
|
|
@@ -4735,11 +5212,11 @@ function installOwnedSkills(skills) {
|
|
|
4735
5212
|
throw error;
|
|
4736
5213
|
}
|
|
4737
5214
|
if (existsSync9(backupPath)) {
|
|
4738
|
-
|
|
5215
|
+
rmSync3(backupPath, { recursive: true, force: true });
|
|
4739
5216
|
}
|
|
4740
5217
|
}
|
|
4741
5218
|
} finally {
|
|
4742
|
-
|
|
5219
|
+
rmSync3(transactionRoot, { recursive: true, force: true });
|
|
4743
5220
|
}
|
|
4744
5221
|
}
|
|
4745
5222
|
function syncOpenCodeOwnedSkills(options = {}) {
|
|
@@ -5000,7 +5477,7 @@ function titleCaseSkillName(value) {
|
|
|
5000
5477
|
}).join("-");
|
|
5001
5478
|
}
|
|
5002
5479
|
function homeDirFromContext(context2) {
|
|
5003
|
-
return context2.env?.HOME ?? context2.env?.USERPROFILE ?? (context2.cwd !== process.cwd() ?
|
|
5480
|
+
return context2.env?.HOME ?? context2.env?.USERPROFILE ?? (context2.cwd !== process.cwd() ? join6(context2.cwd, "home") : void 0);
|
|
5004
5481
|
}
|
|
5005
5482
|
function openCodeSkillTargets(context2) {
|
|
5006
5483
|
const homeDir = homeDirFromContext(context2);
|
|
@@ -5024,7 +5501,7 @@ function openCodeSkillTargets(context2) {
|
|
|
5024
5501
|
const ownedTargets = getOpenCodeOwnedSkillEntries({
|
|
5025
5502
|
homeDir
|
|
5026
5503
|
}).map((skill) => {
|
|
5027
|
-
const path =
|
|
5504
|
+
const path = join6(skill.destinationPath, "SKILL.md");
|
|
5028
5505
|
const installed = existsSync10(path);
|
|
5029
5506
|
return {
|
|
5030
5507
|
kind: "skill",
|
|
@@ -6407,7 +6884,7 @@ import { execFileSync } from "child_process";
|
|
|
6407
6884
|
import {
|
|
6408
6885
|
existsSync as existsSync11,
|
|
6409
6886
|
mkdirSync as mkdirSync5,
|
|
6410
|
-
readFileSync as
|
|
6887
|
+
readFileSync as readFileSync8,
|
|
6411
6888
|
renameSync as renameSync4,
|
|
6412
6889
|
unlinkSync,
|
|
6413
6890
|
writeFileSync as writeFileSync4
|
|
@@ -6445,7 +6922,7 @@ function resolveModelsDevCachePath(options = {}) {
|
|
|
6445
6922
|
function readModelsDevCache(cachePath) {
|
|
6446
6923
|
if (!existsSync11(cachePath)) return void 0;
|
|
6447
6924
|
try {
|
|
6448
|
-
const input = JSON.parse(
|
|
6925
|
+
const input = JSON.parse(readFileSync8(cachePath, "utf8"));
|
|
6449
6926
|
const parsed = modelsDevCacheSchema.safeParse(input);
|
|
6450
6927
|
return parsed.success ? parsed.data : void 0;
|
|
6451
6928
|
} catch {
|
|
@@ -6780,7 +7257,7 @@ async function getModelOptions(harness) {
|
|
|
6780
7257
|
}
|
|
6781
7258
|
|
|
6782
7259
|
// src/cli/tui/operations.ts
|
|
6783
|
-
import { existsSync as existsSync12, readFileSync as
|
|
7260
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
6784
7261
|
var context = { cwd: process.cwd() };
|
|
6785
7262
|
var codexContext = { cwd: process.cwd() };
|
|
6786
7263
|
var claudeCodeContext = {
|
|
@@ -6822,7 +7299,7 @@ function getCodexModelRoles(source = codexContext) {
|
|
|
6822
7299
|
const item = plan.items.find(
|
|
6823
7300
|
(candidate) => candidate.action === "write-role-toml" && candidate.role === role
|
|
6824
7301
|
);
|
|
6825
|
-
const content = item && existsSync12(item.targetPath) ?
|
|
7302
|
+
const content = item && existsSync12(item.targetPath) ? readFileSync9(item.targetPath, "utf8") : item?.content;
|
|
6826
7303
|
const effort = content ? parseRoleTomlEffort(content) : void 0;
|
|
6827
7304
|
return {
|
|
6828
7305
|
role,
|