thoth-agents 0.3.10 → 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-TKIDBRM3.js → chunk-5UBJB5QO.js} +4 -1
- package/dist/{chunk-M2WF46BP.js → chunk-DX4U3AD3.js} +387 -231
- package/dist/{chunk-JBG7XMIG.js → chunk-VPVBYBPU.js} +602 -180
- 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/config/constants.d.ts +3 -3
- package/dist/harness/adapters/claude-code.d.ts +8 -0
- package/dist/harness/adapters/codex-surfaces.d.ts +2 -2
- package/dist/harness/core/agent-pack.d.ts +60 -5
- package/dist/harness/core/sdd.d.ts +32 -19
- 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 +7 -7
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/agents/deep.md +10 -31
- package/plugin/agents/designer.md +10 -31
- package/plugin/agents/explorer.md +10 -30
- package/plugin/agents/librarian.md +10 -30
- package/plugin/agents/oracle.md +11 -31
- package/plugin/agents/orchestrator.md +48 -35
- package/plugin/agents/quick.md +10 -31
- package/plugin/skills/plan-reviewer/SKILL.md +54 -15
- package/plugin/skills/thoth-archive/SKILL.md +5 -0
- package/plugin/skills/thoth-archive/scripts/archive.mjs +30 -10
- 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 +48 -11
- package/plugin/skills/thoth-sdd/references/phases/implement.md +44 -6
- 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-archive/scripts/archive.mjs +30 -10
- 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 +48 -11
- package/skills/thoth-sdd/references/phases/implement.md +44 -6
- 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
|
@@ -28,13 +28,14 @@ import {
|
|
|
28
28
|
isValidPackageVersion,
|
|
29
29
|
loadAgentPrompt,
|
|
30
30
|
parseConfig,
|
|
31
|
+
renderAgentRoutingDescription,
|
|
31
32
|
renderPromptSection,
|
|
32
33
|
renderRolePrompt,
|
|
33
34
|
resolveExecutingPackageVersion,
|
|
34
35
|
updateOpenCodeMainConfig,
|
|
35
36
|
writeConfig,
|
|
36
37
|
writeLiteConfig
|
|
37
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-DX4U3AD3.js";
|
|
38
39
|
|
|
39
40
|
// src/cli/codex-paths.ts
|
|
40
41
|
import { homedir } from "os";
|
|
@@ -120,62 +121,6 @@ function renderConfiguredRolePrompt({
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
// src/harness/core/memory-governance.ts
|
|
123
|
-
function roleRules(role) {
|
|
124
|
-
const sharedRules = [
|
|
125
|
-
"thoth-mem remains an external provider; its installed provider guidance is authoritative for memory operations and thoth-agents does not prescribe the mechanism.",
|
|
126
|
-
"Provider-dependent use requires parent-scoped authorization with the stable root session identity or explicit unavailable state and project supplied by dispatch.",
|
|
127
|
-
"The MEMORY authorization is none, recall, or observe: none forbids provider work, recall permits bounded reads, and observe additionally permits one bounded durable observation under the delegated scope.",
|
|
128
|
-
"Only authorized context may be used, and the delegate must report missing, stale, contradictory, or insufficient context instead of guessing.",
|
|
129
|
-
"Memory authorization never changes workspace permissions or grants control of root lifecycle and real-user-intent ownership.",
|
|
130
|
-
"A handoff must keep accepted scope, decisions, permissions, and artifacts plus bounded memory context available to the authorized delegate.",
|
|
131
|
-
"Completion continuity is a provider-confirmed semantic summary outcome owned by the root.",
|
|
132
|
-
"Missing capability evidence is reported as degraded or unsupported and never as successful persistence or recovery.",
|
|
133
|
-
"openspec/ is the canonical SDD store; do not mirror spec.md, plan.md, tasks.md, verification reports, or archive reports into provider memory.",
|
|
134
|
-
"Do not invent a consumer fallback or silently change the selected persistence mode."
|
|
135
|
-
];
|
|
136
|
-
if (role.name === "orchestrator") {
|
|
137
|
-
return [
|
|
138
|
-
...sharedRules,
|
|
139
|
-
"The root coordinator owns authorization, root lifecycle, real-user-intent handling, and semantic completion outcomes.",
|
|
140
|
-
"Dispatch task and authorization context without embedding secrets, raw transcripts, or irrelevant context."
|
|
141
|
-
];
|
|
142
|
-
}
|
|
143
|
-
if (role.mode === "read-only") {
|
|
144
|
-
return [
|
|
145
|
-
...sharedRules,
|
|
146
|
-
"For a read-only workspace role, observe may authorize a durable provider observation but does not authorize workspace mutation.",
|
|
147
|
-
"Root lifecycle ownership never transfers to this delegate."
|
|
148
|
-
];
|
|
149
|
-
}
|
|
150
|
-
return [
|
|
151
|
-
...sharedRules,
|
|
152
|
-
"For a write-capable workspace role, provider observations still require observe authorization and the delegated parent scope.",
|
|
153
|
-
"Root lifecycle ownership never transfers to this delegate."
|
|
154
|
-
];
|
|
155
|
-
}
|
|
156
|
-
function getRoleMemoryGovernance(role) {
|
|
157
|
-
return {
|
|
158
|
-
role: role.name,
|
|
159
|
-
workspaceMode: role.mode,
|
|
160
|
-
requiresParentContext: role.name !== "orchestrator",
|
|
161
|
-
availableAuthorizations: ["none", "recall", "observe"],
|
|
162
|
-
ownsRootLifecycle: role.name === "orchestrator",
|
|
163
|
-
rules: roleRules(role)
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
function renderMemoryGovernanceInstructions(role, dialect) {
|
|
167
|
-
const governance = getRoleMemoryGovernance(role);
|
|
168
|
-
const harnessRules = dialect ? [
|
|
169
|
-
`- Harness wording: use ${dialect.renderRoleInvocation("orchestrator")} as the memory owner and \`${dialect.tools.userQuestionTool}\` for blocking memory-context questions.`,
|
|
170
|
-
`- Progress ownership remains with the coordinator; report memory-governance verification for tracking in ${dialect.tools.progressTool}.`
|
|
171
|
-
] : [];
|
|
172
|
-
return [
|
|
173
|
-
"External thoth-mem provider memory governance:",
|
|
174
|
-
...governance.rules.map((rule) => `- ${rule}`),
|
|
175
|
-
...harnessRules,
|
|
176
|
-
`- Runtime enforcement: ${role.name === "orchestrator" ? "root-owned outcomes" : "instruction-level unless the target harness validates per-agent memory controls"}.`
|
|
177
|
-
].join("\n");
|
|
178
|
-
}
|
|
179
124
|
function memoryGovernanceDiagnostics(input) {
|
|
180
125
|
const diagnostics = [];
|
|
181
126
|
if (input.permissionControls !== "supported") {
|
|
@@ -448,12 +393,13 @@ var CODEX_SURFACES = [
|
|
|
448
393
|
target: "permission-control",
|
|
449
394
|
status: "unsupported",
|
|
450
395
|
fields: [
|
|
451
|
-
"
|
|
396
|
+
"conditional agent_type selection",
|
|
397
|
+
"role-prefixed fallback",
|
|
452
398
|
"per-role permission enforcement",
|
|
453
399
|
"per-agent tool allow/deny equivalent to OpenCode permissions"
|
|
454
400
|
],
|
|
455
401
|
diagnosticCode: "codex.permission.memory.enforcement_gap",
|
|
456
|
-
summary: "
|
|
402
|
+
summary: "Static packages cannot guarantee active-runtime named-role selection: use collaboration.spawn_agent agent_type when exposed, otherwise the bounded role-prefixed fallback is instruction-only; per-role permission enforcement and OpenCode-style MCP/tool deny maps are not validated.",
|
|
457
403
|
evidence: "Codex approvals/security docs cover sandbox and approval policies, not exact OpenCode per-agent permission maps.",
|
|
458
404
|
fallback: "instruction-only"
|
|
459
405
|
},
|
|
@@ -692,8 +638,8 @@ function stripProviderMcp(content) {
|
|
|
692
638
|
}
|
|
693
639
|
function pluginRootReference(pathValue) {
|
|
694
640
|
const normalized = normalizePath(pathValue);
|
|
695
|
-
const
|
|
696
|
-
return `./${
|
|
641
|
+
const relative2 = normalized.slice(".codex-plugin/".length);
|
|
642
|
+
return `./${relative2}`;
|
|
697
643
|
}
|
|
698
644
|
function sha256(content) {
|
|
699
645
|
return `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
@@ -746,7 +692,7 @@ function codexHookPackageDiagnostic(code, message, severity = "warning") {
|
|
|
746
692
|
function normalizeHookDefinition(hook) {
|
|
747
693
|
return {
|
|
748
694
|
command: hook.handler.command,
|
|
749
|
-
|
|
695
|
+
type: "command"
|
|
750
696
|
};
|
|
751
697
|
}
|
|
752
698
|
function renderHookDefinitions(hookDefinitions) {
|
|
@@ -759,7 +705,10 @@ function renderHookDefinitions(hookDefinitions) {
|
|
|
759
705
|
continue;
|
|
760
706
|
}
|
|
761
707
|
const eventHooks = hooksByEvent.get(validation.event) ?? [];
|
|
762
|
-
eventHooks.push(
|
|
708
|
+
eventHooks.push({
|
|
709
|
+
...hook.matcher !== void 0 ? { matcher: hook.matcher } : {},
|
|
710
|
+
hooks: [normalizeHookDefinition(hook)]
|
|
711
|
+
});
|
|
763
712
|
hooksByEvent.set(validation.event, eventHooks);
|
|
764
713
|
}
|
|
765
714
|
if (hooksByEvent.size === 0) {
|
|
@@ -772,11 +721,11 @@ function renderHookDefinitions(hookDefinitions) {
|
|
|
772
721
|
);
|
|
773
722
|
return { diagnostics };
|
|
774
723
|
}
|
|
775
|
-
const content = stableJson(
|
|
776
|
-
Object.fromEntries(
|
|
724
|
+
const content = stableJson({
|
|
725
|
+
hooks: Object.fromEntries(
|
|
777
726
|
[...hooksByEvent.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([event, hooks]) => [event, hooks.map(stableValue)])
|
|
778
727
|
)
|
|
779
|
-
);
|
|
728
|
+
});
|
|
780
729
|
return { content, diagnostics };
|
|
781
730
|
}
|
|
782
731
|
function parseRawHookContent(content) {
|
|
@@ -807,37 +756,66 @@ function parseRawHookContent(content) {
|
|
|
807
756
|
]
|
|
808
757
|
};
|
|
809
758
|
}
|
|
810
|
-
|
|
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)) {
|
|
811
771
|
if (!Array.isArray(definitions)) {
|
|
812
772
|
diagnostics.push(
|
|
813
773
|
codexHookPackageDiagnostic(
|
|
814
774
|
"codex.plugin.hooks.invalid_shape",
|
|
815
|
-
`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.`
|
|
816
776
|
)
|
|
817
777
|
);
|
|
818
778
|
continue;
|
|
819
779
|
}
|
|
820
|
-
for (const
|
|
821
|
-
if (!isRecord(
|
|
780
|
+
for (const group of definitions) {
|
|
781
|
+
if (!isRecord(group) || !Array.isArray(group.hooks)) {
|
|
822
782
|
diagnostics.push(
|
|
823
783
|
codexHookPackageDiagnostic(
|
|
824
784
|
"codex.plugin.hooks.invalid_shape",
|
|
825
|
-
`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.`
|
|
826
786
|
)
|
|
827
787
|
);
|
|
828
788
|
continue;
|
|
829
789
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
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
|
+
}
|
|
841
819
|
}
|
|
842
820
|
}
|
|
843
821
|
return { hookDefinitions, diagnostics };
|
|
@@ -1109,7 +1087,7 @@ function codexRuntimeGuidance() {
|
|
|
1109
1087
|
"- The ambient Codex session is the adaptive root; no orchestrator child TOML is generated.",
|
|
1110
1088
|
"- Delegate with `collaboration.spawn_agent` only when the root determines that specialization, context isolation, review, or independent parallel work creates a net gain.",
|
|
1111
1089
|
"- Collaboration tools are direct tools and must not be called from inside `functions.exec`.",
|
|
1112
|
-
"-
|
|
1090
|
+
"- When the active `collaboration.spawn_agent` schema exposes `agent_type`, set it to the selected canonical role. When it does not, use a role-prefixed `task_name` plus a self-contained bounded envelope; that fallback is instruction-only.",
|
|
1113
1091
|
"- Keep maximum depth 1: children do not delegate. Use one writer per mutable surface and parallelize only independent work.",
|
|
1114
1092
|
"- A `collaboration.wait_agent` timeout is nonterminal; inspect `collaboration.list_agents` for the same task before rerouting or interrupting it.",
|
|
1115
1093
|
"- Use `request_user_input` only for blocking material choices and always omit `autoResolutionMs` entirely.",
|
|
@@ -1121,15 +1099,8 @@ function codexRuntimeGuidance() {
|
|
|
1121
1099
|
function codexRoleInstructions(role) {
|
|
1122
1100
|
return [
|
|
1123
1101
|
"<role-operational-contract>",
|
|
1124
|
-
`- Role: ${role.name}`,
|
|
1125
|
-
`- Mode: ${role.mode}`,
|
|
1126
|
-
`- Scope: ${role.scope}`,
|
|
1127
|
-
`- Responsibility: ${role.responsibility}`,
|
|
1128
|
-
"- Use request_user_input for local blocking decisions.",
|
|
1129
1102
|
"- Permissions, memory governance, runtime hooks, and provider-per-agent controls are instruction-level unless the active Codex runtime documents stronger enforcement.",
|
|
1130
1103
|
`- ${role.name} runs as a Codex custom-agent TOML entry; the orchestrator remains the ambient Codex root session, not a generated role TOML.`,
|
|
1131
|
-
...role.toolGovernance.map((rule) => `- ${rule}`),
|
|
1132
|
-
...role.verification.map((rule) => `- ${rule}`),
|
|
1133
1104
|
"</role-operational-contract>"
|
|
1134
1105
|
].join("\n");
|
|
1135
1106
|
}
|
|
@@ -1142,8 +1113,7 @@ function roleInstructions(role, config) {
|
|
|
1142
1113
|
config,
|
|
1143
1114
|
model
|
|
1144
1115
|
}),
|
|
1145
|
-
codexRoleInstructions(role)
|
|
1146
|
-
renderMemoryGovernanceInstructions(role, CODEX_PROMPT_DIALECT)
|
|
1116
|
+
codexRoleInstructions(role)
|
|
1147
1117
|
].join("\n\n");
|
|
1148
1118
|
}
|
|
1149
1119
|
function renderCodexRootInstructions(config) {
|
|
@@ -1189,7 +1159,7 @@ function renderAgentArtifacts({ config }) {
|
|
|
1189
1159
|
surfaceId: "project-agent-toml",
|
|
1190
1160
|
values: {
|
|
1191
1161
|
name: role.name,
|
|
1192
|
-
description: role
|
|
1162
|
+
description: renderAgentRoutingDescription(role),
|
|
1193
1163
|
developer_instructions: roleInstructions(role, config),
|
|
1194
1164
|
...model ? { model } : {},
|
|
1195
1165
|
...effort ? { model_reasoning_effort: effort } : {},
|
|
@@ -1825,7 +1795,7 @@ function buildCodexSetupPlan(config) {
|
|
|
1825
1795
|
configPath: targets.configPath,
|
|
1826
1796
|
pluginId: config.pluginId,
|
|
1827
1797
|
diagnostics: [
|
|
1828
|
-
"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.",
|
|
1829
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.",
|
|
1830
1800
|
"The /hooks surface may show hooks from independently installed plugins; this thoth-agents setup plan does not install provider hooks.",
|
|
1831
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.",
|
|
@@ -2005,6 +1975,15 @@ var CLAUDE_CODE_SUBAGENT_DEFAULT_MODELS = {
|
|
|
2005
1975
|
quick: "haiku",
|
|
2006
1976
|
deep: "sonnet"
|
|
2007
1977
|
};
|
|
1978
|
+
var CLAUDE_CODE_SUBAGENT_DEFAULT_EFFORTS = {
|
|
1979
|
+
explorer: "low",
|
|
1980
|
+
quick: "low",
|
|
1981
|
+
designer: "medium",
|
|
1982
|
+
deep: "medium",
|
|
1983
|
+
librarian: "high",
|
|
1984
|
+
oracle: "high"
|
|
1985
|
+
};
|
|
1986
|
+
var CLAUDE_CODE_EFFORTS = /* @__PURE__ */ new Set(["low", "medium", "high", "max"]);
|
|
2008
1987
|
function isClaudeCodeSubagentName(name) {
|
|
2009
1988
|
return name in CLAUDE_CODE_SUBAGENT_DEFAULT_MODELS;
|
|
2010
1989
|
}
|
|
@@ -2014,20 +1993,18 @@ function getClaudeCodeAgentModel(role, config) {
|
|
|
2014
1993
|
if (override && isClaudeCodeModel(override)) return override;
|
|
2015
1994
|
return CLAUDE_CODE_SUBAGENT_DEFAULT_MODELS[role.name];
|
|
2016
1995
|
}
|
|
1996
|
+
function getClaudeCodeAgentEffort(role, config) {
|
|
1997
|
+
if (!isClaudeCodeSubagentName(role.name)) return void 0;
|
|
1998
|
+
const configured = config?.agents?.[role.name]?.variant;
|
|
1999
|
+
return configured && CLAUDE_CODE_EFFORTS.has(configured) ? configured : CLAUDE_CODE_SUBAGENT_DEFAULT_EFFORTS[role.name];
|
|
2000
|
+
}
|
|
2017
2001
|
function claudeCodeRoleInstructions(role) {
|
|
2018
2002
|
return [
|
|
2019
2003
|
"<role-operational-contract>",
|
|
2020
|
-
`- Role: ${role.name}`,
|
|
2021
|
-
`- Mode: ${role.mode}`,
|
|
2022
|
-
`- Scope: ${role.scope}`,
|
|
2023
|
-
`- Responsibility: ${role.responsibility}`,
|
|
2024
|
-
"- Use AskUserQuestion for local blocking decisions.",
|
|
2025
2004
|
`- ${role.name} runs as an auto-discovered Claude Code plugin subagent invoked via Agent(subagent_type: ${claudeCodeSubagentType(role.name)}); plugin subagents are namespaced with the plugin name. The orchestrator is the main Claude Code session.`,
|
|
2026
2005
|
...role.mode === "read-only" ? [
|
|
2027
2006
|
"- Write and Edit are denied in frontmatter while all other inherited tools, including MCP tools, remain available."
|
|
2028
2007
|
] : [],
|
|
2029
|
-
...role.toolGovernance.map((rule) => `- ${rule}`),
|
|
2030
|
-
...role.verification.map((rule) => `- ${rule}`),
|
|
2031
2008
|
"</role-operational-contract>"
|
|
2032
2009
|
].join("\n");
|
|
2033
2010
|
}
|
|
@@ -2040,8 +2017,7 @@ function roleInstructions2(role, config) {
|
|
|
2040
2017
|
config,
|
|
2041
2018
|
model
|
|
2042
2019
|
}),
|
|
2043
|
-
claudeCodeRoleInstructions(role)
|
|
2044
|
-
renderMemoryGovernanceInstructions(role, CLAUDE_CODE_PROMPT_DIALECT)
|
|
2020
|
+
claudeCodeRoleInstructions(role)
|
|
2045
2021
|
].join("\n\n");
|
|
2046
2022
|
}
|
|
2047
2023
|
function renderClaudeCodeRootInstructions(config) {
|
|
@@ -2111,8 +2087,9 @@ function renderSubagentArtifacts(config) {
|
|
|
2111
2087
|
)) {
|
|
2112
2088
|
const content = renderClaudeCodeSubagent({
|
|
2113
2089
|
name: role.name,
|
|
2114
|
-
description: role
|
|
2090
|
+
description: renderAgentRoutingDescription(role),
|
|
2115
2091
|
model: getClaudeCodeAgentModel(role, config),
|
|
2092
|
+
effort: getClaudeCodeAgentEffort(role, config),
|
|
2116
2093
|
...role.mode === "read-only" ? { disallowedTools: "Write, Edit" } : {},
|
|
2117
2094
|
instructions: roleInstructions2(role, config)
|
|
2118
2095
|
});
|
|
@@ -2194,11 +2171,18 @@ var CLAUDE_CODE_ROLE_NAMES = [
|
|
|
2194
2171
|
];
|
|
2195
2172
|
|
|
2196
2173
|
// src/cli/claude-code-install.ts
|
|
2197
|
-
var
|
|
2198
|
-
var
|
|
2199
|
-
var
|
|
2200
|
-
var
|
|
2201
|
-
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}`;
|
|
2202
2186
|
function defaultCommandExecutor(command, args, options) {
|
|
2203
2187
|
const result = spawnSync(command, [...args], {
|
|
2204
2188
|
cwd: options.cwd,
|
|
@@ -2226,12 +2210,26 @@ function parseJsonRecords(content) {
|
|
|
2226
2210
|
}
|
|
2227
2211
|
}
|
|
2228
2212
|
function isCanonicalMarketplace(entry) {
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
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);
|
|
2232
2222
|
}
|
|
2233
|
-
|
|
2234
|
-
|
|
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
|
+
);
|
|
2235
2233
|
}
|
|
2236
2234
|
function inspectClaudeManager(executor, cwd, scope) {
|
|
2237
2235
|
const marketplaceResult = executor(
|
|
@@ -2267,6 +2265,19 @@ function inspectClaudeManager(executor, cwd, scope) {
|
|
|
2267
2265
|
diagnostics
|
|
2268
2266
|
};
|
|
2269
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
|
+
}
|
|
2270
2281
|
const namedMarketplaces = marketplaces.filter(
|
|
2271
2282
|
(entry) => entry.name === MARKETPLACE_NAME
|
|
2272
2283
|
);
|
|
@@ -2276,7 +2287,7 @@ function inspectClaudeManager(executor, cwd, scope) {
|
|
|
2276
2287
|
}
|
|
2277
2288
|
if (marketplace === "conflict") {
|
|
2278
2289
|
diagnostics.push(
|
|
2279
|
-
"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."
|
|
2280
2291
|
);
|
|
2281
2292
|
}
|
|
2282
2293
|
const matchingPlugins = plugins.filter(
|
|
@@ -2301,7 +2312,7 @@ function commandItem(action, scope, projectRoot) {
|
|
|
2301
2312
|
kind: "native-marketplace",
|
|
2302
2313
|
action,
|
|
2303
2314
|
targetPath: MARKETPLACE_TARGET,
|
|
2304
|
-
description: "Register the
|
|
2315
|
+
description: "Register the central Thoth Claude marketplace.",
|
|
2305
2316
|
requiresBackup: false,
|
|
2306
2317
|
command: {
|
|
2307
2318
|
executable: "claude",
|
|
@@ -2309,7 +2320,7 @@ function commandItem(action, scope, projectRoot) {
|
|
|
2309
2320
|
"plugin",
|
|
2310
2321
|
"marketplace",
|
|
2311
2322
|
"add",
|
|
2312
|
-
|
|
2323
|
+
MARKETPLACE_INSTALL_SOURCE,
|
|
2313
2324
|
"--scope",
|
|
2314
2325
|
scope
|
|
2315
2326
|
],
|
|
@@ -2383,7 +2394,7 @@ function buildClaudeCodeSetupPlan(config) {
|
|
|
2383
2394
|
commandExecutor,
|
|
2384
2395
|
diagnostics: [
|
|
2385
2396
|
...inspection.diagnostics,
|
|
2386
|
-
"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.",
|
|
2387
2398
|
"Restart Claude Code or run /reload-plugins after installation; use /plugin to inspect marketplace and plugin state.",
|
|
2388
2399
|
"Provider capability is owned by the external provider and is not established by this thoth-agents setup plan."
|
|
2389
2400
|
],
|
|
@@ -3604,16 +3615,52 @@ function defaultClaudeCodeModelRoles() {
|
|
|
3604
3615
|
}
|
|
3605
3616
|
|
|
3606
3617
|
// src/cli/operations/codex.ts
|
|
3607
|
-
import { existsSync as existsSync8, readFileSync as
|
|
3618
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
3608
3619
|
import { basename as basename2 } from "path";
|
|
3609
3620
|
|
|
3610
3621
|
// src/cli/codex-plugin-install.ts
|
|
3611
3622
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
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}`;
|
|
3617
3664
|
function getCodexCommand(args, options = {}) {
|
|
3618
3665
|
if ((options.platform ?? process.platform) !== "win32") {
|
|
3619
3666
|
return { command: "codex", args: [...args] };
|
|
@@ -3651,10 +3698,153 @@ function parseManagerRecord(content) {
|
|
|
3651
3698
|
return null;
|
|
3652
3699
|
}
|
|
3653
3700
|
}
|
|
3654
|
-
function
|
|
3655
|
-
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();
|
|
3656
3703
|
}
|
|
3657
|
-
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) {
|
|
3658
3848
|
const sources = [entry.repo, entry.source];
|
|
3659
3849
|
if (isRecord5(entry.marketplaceSource)) {
|
|
3660
3850
|
sources.push(entry.marketplaceSource.repo, entry.marketplaceSource.source);
|
|
@@ -3664,13 +3854,16 @@ function marketplaceSources(entry) {
|
|
|
3664
3854
|
);
|
|
3665
3855
|
}
|
|
3666
3856
|
function isCanonicalMarketplace2(entry) {
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
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
|
|
3671
3864
|
);
|
|
3672
3865
|
}
|
|
3673
|
-
function inspectCodexManager(executor, cwd) {
|
|
3866
|
+
function inspectCodexManager(executor, cwd, expectedVersion) {
|
|
3674
3867
|
const marketplaceResult = executor(
|
|
3675
3868
|
"codex",
|
|
3676
3869
|
["plugin", "marketplace", "list", "--json"],
|
|
@@ -3690,6 +3883,9 @@ function inspectCodexManager(executor, cwd) {
|
|
|
3690
3883
|
ready: false,
|
|
3691
3884
|
marketplace: "unknown",
|
|
3692
3885
|
plugin: "unknown",
|
|
3886
|
+
legacyPluginIds: [],
|
|
3887
|
+
legacyMarketplaceNames: [],
|
|
3888
|
+
legacyMarketplaceConflicts: [],
|
|
3693
3889
|
diagnostics
|
|
3694
3890
|
};
|
|
3695
3891
|
}
|
|
@@ -3705,9 +3901,35 @@ function inspectCodexManager(executor, cwd) {
|
|
|
3705
3901
|
ready: false,
|
|
3706
3902
|
marketplace: "unknown",
|
|
3707
3903
|
plugin: "unknown",
|
|
3904
|
+
legacyPluginIds: [],
|
|
3905
|
+
legacyMarketplaceNames: [],
|
|
3906
|
+
legacyMarketplaceConflicts: [],
|
|
3708
3907
|
diagnostics
|
|
3709
3908
|
};
|
|
3710
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
|
+
}
|
|
3711
3933
|
const namedMarketplaces = marketplaces.filter(
|
|
3712
3934
|
(entry) => entry.name === MARKETPLACE_NAME2
|
|
3713
3935
|
);
|
|
@@ -3717,31 +3939,81 @@ function inspectCodexManager(executor, cwd) {
|
|
|
3717
3939
|
}
|
|
3718
3940
|
if (marketplace === "conflict") {
|
|
3719
3941
|
diagnostics.push(
|
|
3720
|
-
"A Codex marketplace named thoth-
|
|
3942
|
+
"A Codex marketplace named thoth-plugins is registered from a different source; resolve it through Codex before retrying."
|
|
3721
3943
|
);
|
|
3722
3944
|
}
|
|
3723
3945
|
const matchingPlugins = installedPlugins.filter(
|
|
3724
3946
|
(entry) => entry.pluginId === PLUGIN_ID2
|
|
3725
3947
|
);
|
|
3726
3948
|
let plugin = "absent";
|
|
3727
|
-
if (matchingPlugins.some(
|
|
3949
|
+
if (matchingPlugins.some(
|
|
3950
|
+
(entry) => entry.enabled === true && entry.version === expectedVersion
|
|
3951
|
+
)) {
|
|
3728
3952
|
plugin = "installed";
|
|
3953
|
+
} else if (matchingPlugins.some((entry) => entry.enabled === true)) {
|
|
3954
|
+
plugin = "outdated";
|
|
3729
3955
|
} else if (matchingPlugins.length > 0) {
|
|
3730
3956
|
plugin = "disabled";
|
|
3731
3957
|
}
|
|
3732
3958
|
return {
|
|
3733
|
-
ready: marketplace !== "conflict",
|
|
3959
|
+
ready: marketplace !== "conflict" && legacyMarketplaceConflicts.length === 0,
|
|
3734
3960
|
marketplace,
|
|
3735
3961
|
plugin,
|
|
3962
|
+
legacyPluginIds,
|
|
3963
|
+
legacyMarketplaceNames,
|
|
3964
|
+
legacyMarketplaceConflicts,
|
|
3736
3965
|
diagnostics
|
|
3737
3966
|
};
|
|
3738
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
|
+
}
|
|
3739
4011
|
function commandItem2(action, projectRoot) {
|
|
3740
4012
|
if (action === "register-marketplace") {
|
|
3741
4013
|
return {
|
|
3742
4014
|
action,
|
|
3743
4015
|
targetPath: MARKETPLACE_TARGET2,
|
|
3744
|
-
description: "Register the
|
|
4016
|
+
description: "Register the central Thoth Codex marketplace.",
|
|
3745
4017
|
command: {
|
|
3746
4018
|
executable: "codex",
|
|
3747
4019
|
args: ["plugin", "marketplace", "add", MARKETPLACE_SOURCE2, "--json"],
|
|
@@ -3762,25 +4034,54 @@ function commandItem2(action, projectRoot) {
|
|
|
3762
4034
|
}
|
|
3763
4035
|
function buildCodexPluginSetupPlan(config) {
|
|
3764
4036
|
const commandExecutor = config.commandExecutor ?? defaultCommandExecutor3;
|
|
3765
|
-
const inspection = inspectCodexManager(
|
|
3766
|
-
|
|
4037
|
+
const inspection = inspectCodexManager(
|
|
4038
|
+
commandExecutor,
|
|
4039
|
+
config.projectRoot,
|
|
4040
|
+
config.expectedVersion
|
|
4041
|
+
);
|
|
4042
|
+
let cleanupPreflight;
|
|
4043
|
+
let cleanupPreflightError;
|
|
3767
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) {
|
|
3768
4056
|
if (inspection.marketplace === "absent") {
|
|
3769
4057
|
items.push(commandItem2("register-marketplace", config.projectRoot));
|
|
3770
4058
|
}
|
|
3771
|
-
if (inspection.plugin === "absent" || inspection.plugin === "disabled") {
|
|
4059
|
+
if (inspection.plugin === "absent" || inspection.plugin === "disabled" || inspection.plugin === "outdated") {
|
|
3772
4060
|
items.push(commandItem2("install-plugin", config.projectRoot));
|
|
3773
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
|
+
);
|
|
3774
4071
|
}
|
|
3775
4072
|
return {
|
|
4073
|
+
codexHome: cleanupPreflight?.codexHome ?? getCodexHome({ codexHome: config.codexHome, homeDir: config.homeDir }),
|
|
3776
4074
|
dryRun: config.dryRun === true,
|
|
4075
|
+
expectedVersion: config.expectedVersion,
|
|
3777
4076
|
items,
|
|
3778
|
-
ready
|
|
4077
|
+
ready,
|
|
3779
4078
|
projectRoot: config.projectRoot,
|
|
3780
4079
|
commandExecutor,
|
|
3781
4080
|
diagnostics: [
|
|
3782
4081
|
...inspection.diagnostics,
|
|
3783
|
-
|
|
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.",
|
|
3784
4085
|
"Restart Codex after installation; use /plugins to inspect marketplace and plugin state."
|
|
3785
4086
|
]
|
|
3786
4087
|
};
|
|
@@ -3804,12 +4105,28 @@ function applyCodexPluginSetup(plan) {
|
|
|
3804
4105
|
};
|
|
3805
4106
|
}
|
|
3806
4107
|
if (plan.dryRun) return { success: true, changed, diagnostics };
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
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
|
+
});
|
|
3813
4130
|
if (result.exitCode !== 0) {
|
|
3814
4131
|
return {
|
|
3815
4132
|
success: false,
|
|
@@ -3822,10 +4139,11 @@ function applyCodexPluginSetup(plan) {
|
|
|
3822
4139
|
}
|
|
3823
4140
|
const inspection = inspectCodexManager(
|
|
3824
4141
|
plan.commandExecutor,
|
|
3825
|
-
plan.projectRoot
|
|
4142
|
+
plan.projectRoot,
|
|
4143
|
+
plan.expectedVersion
|
|
3826
4144
|
);
|
|
3827
4145
|
diagnostics.push(...inspection.diagnostics);
|
|
3828
|
-
if (inspection
|
|
4146
|
+
if (!centralPluginVerified(inspection)) {
|
|
3829
4147
|
return {
|
|
3830
4148
|
success: false,
|
|
3831
4149
|
changed,
|
|
@@ -3833,11 +4151,112 @@ function applyCodexPluginSetup(plan) {
|
|
|
3833
4151
|
error: "Codex did not verify the expected marketplace and enabled plugin after mutation."
|
|
3834
4152
|
};
|
|
3835
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
|
+
}
|
|
3836
4255
|
return { success: true, changed, diagnostics: uniqueMessages3(diagnostics) };
|
|
3837
4256
|
}
|
|
3838
4257
|
function formatCodexPluginSetupPlan(plan) {
|
|
3839
4258
|
const lines = plan.items.map(
|
|
3840
|
-
(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}`
|
|
3841
4260
|
);
|
|
3842
4261
|
return ["Codex plugin setup plan:", ...lines].join("\n");
|
|
3843
4262
|
}
|
|
@@ -3965,7 +4384,7 @@ function backupForItem(item) {
|
|
|
3965
4384
|
function rootBlockState(item) {
|
|
3966
4385
|
if (!existsSync8(item.targetPath))
|
|
3967
4386
|
return { state: "missing", observed: "absent" };
|
|
3968
|
-
const content =
|
|
4387
|
+
const content = readFileSync7(item.targetPath, "utf8");
|
|
3969
4388
|
if (item.content && content.includes(item.content)) {
|
|
3970
4389
|
return { state: "installed", observed: "managed root block present" };
|
|
3971
4390
|
}
|
|
@@ -3978,11 +4397,11 @@ function managedModelState(item) {
|
|
|
3978
4397
|
if (!existsSync8(item.targetPath))
|
|
3979
4398
|
return { state: "missing", observed: "absent" };
|
|
3980
4399
|
try {
|
|
3981
|
-
const parsed = JSON.parse(
|
|
4400
|
+
const parsed = JSON.parse(readFileSync7(item.targetPath, "utf8"));
|
|
3982
4401
|
if (parsed.version !== MANAGED_MODEL_STATE_VERSION || !parsed.models || typeof parsed.models !== "object" || Array.isArray(parsed.models)) {
|
|
3983
4402
|
return { state: "unknown", observed: "invalid managed model state" };
|
|
3984
4403
|
}
|
|
3985
|
-
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" };
|
|
3986
4405
|
} catch {
|
|
3987
4406
|
return { state: "unknown", observed: "unparseable managed model state" };
|
|
3988
4407
|
}
|
|
@@ -3990,7 +4409,7 @@ function managedModelState(item) {
|
|
|
3990
4409
|
function userConfigState(item) {
|
|
3991
4410
|
if (!existsSync8(item.targetPath))
|
|
3992
4411
|
return { state: "missing", observed: "absent" };
|
|
3993
|
-
const content =
|
|
4412
|
+
const content = readFileSync7(item.targetPath, "utf8");
|
|
3994
4413
|
if (/^\s*default_mode_request_user_input\s*=\s*true\b/m.test(content)) {
|
|
3995
4414
|
return {
|
|
3996
4415
|
state: "installed",
|
|
@@ -4005,7 +4424,7 @@ function userConfigState(item) {
|
|
|
4005
4424
|
function contentState(item) {
|
|
4006
4425
|
if (!existsSync8(item.targetPath))
|
|
4007
4426
|
return { state: "missing", observed: "absent" };
|
|
4008
|
-
const observed =
|
|
4427
|
+
const observed = readFileSync7(item.targetPath, "utf8");
|
|
4009
4428
|
if (item.content === observed)
|
|
4010
4429
|
return { state: "installed", observed: "current" };
|
|
4011
4430
|
if (item.role) {
|
|
@@ -4172,7 +4591,7 @@ function planItemFromPluginSetup(item) {
|
|
|
4172
4591
|
path: item.targetPath,
|
|
4173
4592
|
label: "Codex native plugin manager target"
|
|
4174
4593
|
},
|
|
4175
|
-
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}`,
|
|
4176
4595
|
backup: { required: false, strategy: "external" }
|
|
4177
4596
|
};
|
|
4178
4597
|
}
|
|
@@ -4270,7 +4689,10 @@ function buildCompleteCodexPlan(action, context2) {
|
|
|
4270
4689
|
const packageVersion = resolveVersion();
|
|
4271
4690
|
const buildPlugin = context2.buildCodexPluginSetupPlan ?? buildCodexPluginSetupPlan;
|
|
4272
4691
|
const pluginPlan = buildPlugin({
|
|
4692
|
+
codexHome: context2.codexHome,
|
|
4273
4693
|
dryRun: true,
|
|
4694
|
+
expectedVersion: packageVersion.ok ? packageVersion.version : "0.0.0",
|
|
4695
|
+
homeDir: context2.homeDir,
|
|
4274
4696
|
projectRoot: context2.cwd,
|
|
4275
4697
|
commandExecutor: context2.codexPluginCommandExecutor
|
|
4276
4698
|
});
|
|
@@ -4666,7 +5088,7 @@ function applyCodexPlan(plan) {
|
|
|
4666
5088
|
// src/cli/operations/opencode.ts
|
|
4667
5089
|
import { createHash as createHash3 } from "crypto";
|
|
4668
5090
|
import { existsSync as existsSync10 } from "fs";
|
|
4669
|
-
import { join as
|
|
5091
|
+
import { join as join6 } from "path";
|
|
4670
5092
|
|
|
4671
5093
|
// src/cli/opencode-effort.ts
|
|
4672
5094
|
var OPENAI_GPT_RUNTIME_VARIANTS = /* @__PURE__ */ new Set([
|
|
@@ -4705,11 +5127,11 @@ import {
|
|
|
4705
5127
|
mkdirSync as mkdirSync4,
|
|
4706
5128
|
mkdtempSync,
|
|
4707
5129
|
renameSync as renameSync3,
|
|
4708
|
-
rmSync as
|
|
5130
|
+
rmSync as rmSync3,
|
|
4709
5131
|
statSync
|
|
4710
5132
|
} from "fs";
|
|
4711
5133
|
import { homedir as homedir4 } from "os";
|
|
4712
|
-
import { dirname as dirname5, join as
|
|
5134
|
+
import { dirname as dirname5, join as join5 } from "path";
|
|
4713
5135
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4714
5136
|
|
|
4715
5137
|
// src/harness/core/owned-skills.ts
|
|
@@ -4739,8 +5161,8 @@ function getOpenCodeOwnedSkillEntries(options = {}) {
|
|
|
4739
5161
|
const homeDir = options.homeDir ?? homedir4();
|
|
4740
5162
|
return THOTH_OWNED_SKILL_NAMES.map((name) => ({
|
|
4741
5163
|
name,
|
|
4742
|
-
sourcePath:
|
|
4743
|
-
destinationPath:
|
|
5164
|
+
sourcePath: join5(packageRoot, "skills", name),
|
|
5165
|
+
destinationPath: join5(homeDir, ".config", "opencode", "skills", name)
|
|
4744
5166
|
}));
|
|
4745
5167
|
}
|
|
4746
5168
|
function validateCanonicalSkill(entry) {
|
|
@@ -4749,7 +5171,7 @@ function validateCanonicalSkill(entry) {
|
|
|
4749
5171
|
`Canonical owned skill directory is missing: ${entry.name}`
|
|
4750
5172
|
);
|
|
4751
5173
|
}
|
|
4752
|
-
const contractPath =
|
|
5174
|
+
const contractPath = join5(entry.sourcePath, "SKILL.md");
|
|
4753
5175
|
if (!existsSync9(contractPath) || !statSync(contractPath).isFile()) {
|
|
4754
5176
|
throw new Error(`Canonical owned skill contract is missing: ${entry.name}`);
|
|
4755
5177
|
}
|
|
@@ -4759,21 +5181,21 @@ function installOwnedSkills(skills) {
|
|
|
4759
5181
|
if (!firstSkill) return;
|
|
4760
5182
|
const globalRoot = dirname5(firstSkill.destinationPath);
|
|
4761
5183
|
mkdirSync4(globalRoot, { recursive: true });
|
|
4762
|
-
const transactionRoot = mkdtempSync(
|
|
4763
|
-
const stagedRoot =
|
|
4764
|
-
const backupRoot =
|
|
5184
|
+
const transactionRoot = mkdtempSync(join5(globalRoot, ".thoth-agents-owned-"));
|
|
5185
|
+
const stagedRoot = join5(transactionRoot, "staged");
|
|
5186
|
+
const backupRoot = join5(transactionRoot, "backup");
|
|
4765
5187
|
mkdirSync4(stagedRoot, { recursive: true });
|
|
4766
5188
|
mkdirSync4(backupRoot, { recursive: true });
|
|
4767
5189
|
try {
|
|
4768
5190
|
for (const skill of skills) {
|
|
4769
|
-
cpSync(skill.sourcePath,
|
|
5191
|
+
cpSync(skill.sourcePath, join5(stagedRoot, skill.name), {
|
|
4770
5192
|
recursive: true,
|
|
4771
5193
|
force: true
|
|
4772
5194
|
});
|
|
4773
5195
|
}
|
|
4774
5196
|
for (const skill of skills) {
|
|
4775
|
-
const stagedPath =
|
|
4776
|
-
const backupPath =
|
|
5197
|
+
const stagedPath = join5(stagedRoot, skill.name);
|
|
5198
|
+
const backupPath = join5(backupRoot, skill.name);
|
|
4777
5199
|
const hadExistingDestination = existsSync9(skill.destinationPath);
|
|
4778
5200
|
if (hadExistingDestination) {
|
|
4779
5201
|
renameSync3(skill.destinationPath, backupPath);
|
|
@@ -4782,7 +5204,7 @@ function installOwnedSkills(skills) {
|
|
|
4782
5204
|
renameSync3(stagedPath, skill.destinationPath);
|
|
4783
5205
|
} catch (error) {
|
|
4784
5206
|
if (existsSync9(skill.destinationPath)) {
|
|
4785
|
-
|
|
5207
|
+
rmSync3(skill.destinationPath, { recursive: true, force: true });
|
|
4786
5208
|
}
|
|
4787
5209
|
if (hadExistingDestination && existsSync9(backupPath)) {
|
|
4788
5210
|
renameSync3(backupPath, skill.destinationPath);
|
|
@@ -4790,11 +5212,11 @@ function installOwnedSkills(skills) {
|
|
|
4790
5212
|
throw error;
|
|
4791
5213
|
}
|
|
4792
5214
|
if (existsSync9(backupPath)) {
|
|
4793
|
-
|
|
5215
|
+
rmSync3(backupPath, { recursive: true, force: true });
|
|
4794
5216
|
}
|
|
4795
5217
|
}
|
|
4796
5218
|
} finally {
|
|
4797
|
-
|
|
5219
|
+
rmSync3(transactionRoot, { recursive: true, force: true });
|
|
4798
5220
|
}
|
|
4799
5221
|
}
|
|
4800
5222
|
function syncOpenCodeOwnedSkills(options = {}) {
|
|
@@ -5055,7 +5477,7 @@ function titleCaseSkillName(value) {
|
|
|
5055
5477
|
}).join("-");
|
|
5056
5478
|
}
|
|
5057
5479
|
function homeDirFromContext(context2) {
|
|
5058
|
-
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);
|
|
5059
5481
|
}
|
|
5060
5482
|
function openCodeSkillTargets(context2) {
|
|
5061
5483
|
const homeDir = homeDirFromContext(context2);
|
|
@@ -5079,7 +5501,7 @@ function openCodeSkillTargets(context2) {
|
|
|
5079
5501
|
const ownedTargets = getOpenCodeOwnedSkillEntries({
|
|
5080
5502
|
homeDir
|
|
5081
5503
|
}).map((skill) => {
|
|
5082
|
-
const path =
|
|
5504
|
+
const path = join6(skill.destinationPath, "SKILL.md");
|
|
5083
5505
|
const installed = existsSync10(path);
|
|
5084
5506
|
return {
|
|
5085
5507
|
kind: "skill",
|
|
@@ -6462,7 +6884,7 @@ import { execFileSync } from "child_process";
|
|
|
6462
6884
|
import {
|
|
6463
6885
|
existsSync as existsSync11,
|
|
6464
6886
|
mkdirSync as mkdirSync5,
|
|
6465
|
-
readFileSync as
|
|
6887
|
+
readFileSync as readFileSync8,
|
|
6466
6888
|
renameSync as renameSync4,
|
|
6467
6889
|
unlinkSync,
|
|
6468
6890
|
writeFileSync as writeFileSync4
|
|
@@ -6500,7 +6922,7 @@ function resolveModelsDevCachePath(options = {}) {
|
|
|
6500
6922
|
function readModelsDevCache(cachePath) {
|
|
6501
6923
|
if (!existsSync11(cachePath)) return void 0;
|
|
6502
6924
|
try {
|
|
6503
|
-
const input = JSON.parse(
|
|
6925
|
+
const input = JSON.parse(readFileSync8(cachePath, "utf8"));
|
|
6504
6926
|
const parsed = modelsDevCacheSchema.safeParse(input);
|
|
6505
6927
|
return parsed.success ? parsed.data : void 0;
|
|
6506
6928
|
} catch {
|
|
@@ -6714,7 +7136,7 @@ var CODEX_DOCUMENTED_EFFORTS2 = /* @__PURE__ */ new Set([
|
|
|
6714
7136
|
"max",
|
|
6715
7137
|
"ultra"
|
|
6716
7138
|
]);
|
|
6717
|
-
var
|
|
7139
|
+
var CLAUDE_CODE_EFFORTS2 = ["low", "medium", "high", "xhigh", "max"];
|
|
6718
7140
|
function parseOpenCodeModels(output) {
|
|
6719
7141
|
const seen = /* @__PURE__ */ new Set();
|
|
6720
7142
|
const options = [];
|
|
@@ -6747,7 +7169,7 @@ function writableEfforts(harness, option) {
|
|
|
6747
7169
|
}
|
|
6748
7170
|
if (harness === "claude") {
|
|
6749
7171
|
return option.efforts.filter(
|
|
6750
|
-
(effort) =>
|
|
7172
|
+
(effort) => CLAUDE_CODE_EFFORTS2.includes(effort)
|
|
6751
7173
|
);
|
|
6752
7174
|
}
|
|
6753
7175
|
return option.efforts.filter(
|
|
@@ -6798,7 +7220,7 @@ var CLAUDE_CODE_MODEL_OPTIONS = [
|
|
|
6798
7220
|
}
|
|
6799
7221
|
].map((option) => ({
|
|
6800
7222
|
...option,
|
|
6801
|
-
efforts: option.id === "inherit" ? [] : [...
|
|
7223
|
+
efforts: option.id === "inherit" ? [] : [...CLAUDE_CODE_EFFORTS2],
|
|
6802
7224
|
source: "manual"
|
|
6803
7225
|
}));
|
|
6804
7226
|
async function getModelOptions(harness) {
|
|
@@ -6835,7 +7257,7 @@ async function getModelOptions(harness) {
|
|
|
6835
7257
|
}
|
|
6836
7258
|
|
|
6837
7259
|
// src/cli/tui/operations.ts
|
|
6838
|
-
import { existsSync as existsSync12, readFileSync as
|
|
7260
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
6839
7261
|
var context = { cwd: process.cwd() };
|
|
6840
7262
|
var codexContext = { cwd: process.cwd() };
|
|
6841
7263
|
var claudeCodeContext = {
|
|
@@ -6877,7 +7299,7 @@ function getCodexModelRoles(source = codexContext) {
|
|
|
6877
7299
|
const item = plan.items.find(
|
|
6878
7300
|
(candidate) => candidate.action === "write-role-toml" && candidate.role === role
|
|
6879
7301
|
);
|
|
6880
|
-
const content = item && existsSync12(item.targetPath) ?
|
|
7302
|
+
const content = item && existsSync12(item.targetPath) ? readFileSync9(item.targetPath, "utf8") : item?.content;
|
|
6881
7303
|
const effort = content ? parseRoleTomlEffort(content) : void 0;
|
|
6882
7304
|
return {
|
|
6883
7305
|
role,
|