thoth-agents 0.1.18 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -12
- package/dist/agents/prompt-dialects.d.ts +9 -0
- package/dist/{chunk-6K3ZXIMC.js → chunk-3NOVCFN7.js} +88 -29
- package/dist/chunk-4WYCZ5Z7.js +698 -0
- package/dist/{chunk-SOT5ZY53.js → chunk-WH3F3GWE.js} +1498 -350
- package/dist/cli/claude-code-install.d.ts +53 -0
- package/dist/cli/claude-code-paths.d.ts +31 -0
- package/dist/cli/codex-install.d.ts +1 -5
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/index.js +85 -27
- package/dist/cli/managed-state-io.d.ts +16 -0
- package/dist/cli/operations/claude-code.d.ts +21 -0
- package/dist/cli/tui/index.js +87 -9
- package/dist/cli/tui/operations.d.ts +2 -0
- package/dist/cli/types.d.ts +3 -3
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schema.d.ts +11 -0
- package/dist/config/utils.d.ts +5 -0
- package/dist/harness/adapters/claude-code.d.ts +24 -0
- package/dist/harness/core/memory-governance.d.ts +39 -4
- package/dist/harness/core/package-version.d.ts +7 -0
- package/dist/harness/types.d.ts +1 -1
- package/dist/harness/writers/claude-code-plugin-package.d.ts +32 -0
- package/dist/harness/writers/claude-code-skill-layout.d.ts +16 -0
- package/dist/harness/writers/claude-code-subagent.d.ts +26 -0
- package/dist/harness/writers/fs-skill-collect.d.ts +7 -0
- package/dist/hooks/phase-reminder/index.d.ts +2 -0
- package/dist/hooks/thoth-mem/protocol.d.ts +2 -2
- package/dist/index.js +54 -512
- package/package.json +1 -1
- package/src/skills/_shared/persistence-contract.md +18 -14
- package/src/skills/_shared/thoth-mem-convention.md +18 -18
- package/src/skills/executing-plans/SKILL.md +6 -4
- package/src/skills/plan-reviewer/SKILL.md +4 -2
- package/src/skills/thoth-mem-agents/SKILL.md +3 -0
- package/thoth-agents.schema.json +16 -0
- package/dist/chunk-DYGVRAMS.js +0 -182
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ALL_AGENT_NAMES,
|
|
3
|
+
CLAUDE_CODE_PROMPT_DIALECT,
|
|
4
|
+
CLAUDE_CODE_SUBAGENT_NAMESPACE,
|
|
3
5
|
CODEX_PROMPT_DIALECT,
|
|
4
6
|
CONTEXT7_MCP_URL,
|
|
5
7
|
CUSTOM_SKILLS,
|
|
@@ -7,6 +9,7 @@ import {
|
|
|
7
9
|
DEFAULT_THOTH_COMMAND,
|
|
8
10
|
GREP_APP_MCP_URL,
|
|
9
11
|
appendPromptSections,
|
|
12
|
+
claudeCodeSubagentType,
|
|
10
13
|
composeAgentPrompt,
|
|
11
14
|
createModelFamilySection,
|
|
12
15
|
createOrchestratorPromptSections,
|
|
@@ -23,6 +26,7 @@ import {
|
|
|
23
26
|
getCustomSkillsDir,
|
|
24
27
|
getExistingConfigPath,
|
|
25
28
|
getExistingLiteConfigPath,
|
|
29
|
+
getPrimaryModelId,
|
|
26
30
|
getSkillRegistry,
|
|
27
31
|
installCustomSkills,
|
|
28
32
|
loadAgentPrompt,
|
|
@@ -31,11 +35,58 @@ import {
|
|
|
31
35
|
renderRolePrompt,
|
|
32
36
|
writeConfig,
|
|
33
37
|
writeLiteConfig
|
|
34
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-3NOVCFN7.js";
|
|
39
|
+
|
|
40
|
+
// src/cli/codex-paths.ts
|
|
41
|
+
import { homedir } from "os";
|
|
42
|
+
import { join } from "path";
|
|
43
|
+
var CODEX_ROLE_NAMES = [
|
|
44
|
+
"explorer",
|
|
45
|
+
"librarian",
|
|
46
|
+
"oracle",
|
|
47
|
+
"designer",
|
|
48
|
+
"quick",
|
|
49
|
+
"deep"
|
|
50
|
+
];
|
|
51
|
+
function getCodexHome(options = {}) {
|
|
52
|
+
const explicit = options.codexHome ?? process.env.CODEX_HOME?.trim();
|
|
53
|
+
return explicit || join(options.homeDir ?? homedir(), ".codex");
|
|
54
|
+
}
|
|
55
|
+
function resolveCodexTargets(options) {
|
|
56
|
+
const codexHome = getCodexHome(options);
|
|
57
|
+
const agentsDir = options.scope === "project" ? join(options.projectRoot, ".codex", "agents") : join(codexHome, "agents");
|
|
58
|
+
return {
|
|
59
|
+
scope: options.scope,
|
|
60
|
+
codexHome,
|
|
61
|
+
configPath: join(codexHome, "config.toml"),
|
|
62
|
+
rootInstructionsPath: join(codexHome, "AGENTS.md"),
|
|
63
|
+
roleAgentPaths: CODEX_ROLE_NAMES.map((role) => ({
|
|
64
|
+
role,
|
|
65
|
+
path: join(agentsDir, `thoth-agents-${role}.toml`)
|
|
66
|
+
})),
|
|
67
|
+
managedModelsPath: join(
|
|
68
|
+
codexHome,
|
|
69
|
+
"agents",
|
|
70
|
+
".thoth-agents-managed-models.json"
|
|
71
|
+
),
|
|
72
|
+
skillsDir: options.scope === "project" ? join(options.projectRoot, ".agents", "skills") : join(options.homeDir ?? homedir(), ".agents", "skills"),
|
|
73
|
+
packageRoot: join(options.projectRoot, ".codex-plugin"),
|
|
74
|
+
personalPluginRoot: join(codexHome, "plugins", "thoth-agents"),
|
|
75
|
+
personalMarketplacePath: join(
|
|
76
|
+
options.homeDir ?? homedir(),
|
|
77
|
+
".agents",
|
|
78
|
+
"plugins",
|
|
79
|
+
"marketplace.json"
|
|
80
|
+
)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/cli/codex-install.ts
|
|
85
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, rmSync } from "fs";
|
|
86
|
+
import { basename, dirname as dirname4, isAbsolute, join as join5, relative as relative2 } from "path";
|
|
87
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
35
88
|
|
|
36
89
|
// src/harness/adapters/codex.ts
|
|
37
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
38
|
-
import { dirname, resolve } from "path";
|
|
39
90
|
import { fileURLToPath } from "url";
|
|
40
91
|
|
|
41
92
|
// src/harness/core/agent-pack.ts
|
|
@@ -172,96 +223,91 @@ function getAgentPackContract() {
|
|
|
172
223
|
}
|
|
173
224
|
|
|
174
225
|
// src/harness/core/memory-governance.ts
|
|
175
|
-
var
|
|
176
|
-
"
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
"mem_search",
|
|
182
|
-
"mem_timeline",
|
|
183
|
-
"mem_get_observation"
|
|
226
|
+
var ROOT_OWNED_OPERATIONS = [
|
|
227
|
+
{ tool: "mem_session", action: "start" },
|
|
228
|
+
{ tool: "mem_session", action: "checkpoint" },
|
|
229
|
+
{ tool: "mem_session", action: "summary" },
|
|
230
|
+
{ tool: "mem_save", kind: "prompt" },
|
|
231
|
+
{ tool: "mem_save", kind: "session_summary" }
|
|
184
232
|
];
|
|
185
|
-
var
|
|
233
|
+
var PARENT_SCOPED_READ_TOOLS = [
|
|
234
|
+
"mem_recall",
|
|
186
235
|
"mem_context",
|
|
187
|
-
"
|
|
188
|
-
"
|
|
189
|
-
"mem_topic_keys"
|
|
236
|
+
"mem_get",
|
|
237
|
+
"mem_project"
|
|
190
238
|
];
|
|
191
239
|
var WRITE_CAPABLE_DELEGATED_TOOLS = [
|
|
192
|
-
|
|
193
|
-
"
|
|
194
|
-
"mem_get_observation",
|
|
195
|
-
"mem_timeline",
|
|
196
|
-
...BOUNDED_CONTEXT_TOOLS,
|
|
197
|
-
"mem_suggest_topic_key"
|
|
240
|
+
...PARENT_SCOPED_READ_TOOLS,
|
|
241
|
+
"mem_save"
|
|
198
242
|
];
|
|
199
243
|
var ALL_MEMORY_TOOLS = [
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
"
|
|
204
|
-
"
|
|
244
|
+
"mem_save",
|
|
245
|
+
"mem_recall",
|
|
246
|
+
"mem_context",
|
|
247
|
+
"mem_get",
|
|
248
|
+
"mem_project",
|
|
249
|
+
"mem_session"
|
|
205
250
|
];
|
|
206
251
|
function uniqueTools(tools) {
|
|
207
252
|
return [...new Set(tools)];
|
|
208
253
|
}
|
|
209
254
|
function roleAllowedTools(role) {
|
|
210
255
|
if (role.name === "orchestrator") {
|
|
211
|
-
return
|
|
256
|
+
return [...ALL_MEMORY_TOOLS];
|
|
212
257
|
}
|
|
213
258
|
if (role.mode === "read-only") {
|
|
214
|
-
return [...
|
|
259
|
+
return [...PARENT_SCOPED_READ_TOOLS];
|
|
215
260
|
}
|
|
216
|
-
return [...WRITE_CAPABLE_DELEGATED_TOOLS];
|
|
261
|
+
return uniqueTools([...WRITE_CAPABLE_DELEGATED_TOOLS]);
|
|
217
262
|
}
|
|
218
263
|
function roleRules(role) {
|
|
219
264
|
const sharedSubagentRules = [
|
|
220
265
|
"Every subagent memory call requires the parent session_id and project from dispatch; if either is missing, do not call thoth-mem.",
|
|
221
|
-
|
|
266
|
+
'Delegated handoff recovery uses the parent-scoped recall funnel: mem_recall(mode="compact") -> mem_recall(mode="context") -> mem_get(...) before memory content is treated as source material.',
|
|
267
|
+
"Use mem_get(include_timeline=true) when chronology around a recovered record matters.",
|
|
268
|
+
'mem_context(recall_query=...) and bounded mem_project(action="graph"|"topics"|"topic") are supplemental project context only and do not replace the recall funnel.',
|
|
222
269
|
"Report missing, stale, contradictory, or insufficient recalled context instead of guessing through it.",
|
|
223
|
-
|
|
270
|
+
'Never own mem_session(action="start"|"checkpoint"|"summary") or mem_save(kind="prompt"|"session_summary"); those operations are root/orchestrator-owned.',
|
|
224
271
|
"Never save generated subagent prompts as user intent.",
|
|
225
272
|
"Protect the sdd/* topic namespace; SDD artifacts may use deterministic sdd/{change}/{artifact} topic keys only in persistence modes that include thoth-mem."
|
|
226
273
|
];
|
|
227
274
|
if (role.name === "orchestrator") {
|
|
228
275
|
return [
|
|
229
|
-
"
|
|
276
|
+
'mem_session(action="start"|"checkpoint"|"summary"), mem_save(kind="prompt"), and mem_save(kind="session_summary") are root/main orchestrator-owned operations and responsibilities.',
|
|
230
277
|
"In harnesses without an orchestrator-named agent, root/main orchestrator-owned means the initial/root agent when the harness does not expose an orchestrator-named agent.",
|
|
231
|
-
|
|
278
|
+
'Before delegation, save or refresh the handoff body with root-owned mem_session(action="summary") or mem_save(kind="session_summary") when available; otherwise disclose that compaction could not be persisted.',
|
|
232
279
|
"Dispatch task instructions plus recovery instructions, not the handoff body, raw transcripts, or generated subagent prompts.",
|
|
233
280
|
"Dispatch parent session_id and project when authorizing subagent memory use.",
|
|
281
|
+
'Root recall uses mem_recall(mode="compact") -> mem_recall(mode="context") -> mem_get(...); use mem_context(recall_query=...) and bounded mem_project(action="graph"|"topics"|"topic") for supplemental context.',
|
|
234
282
|
"Protect the sdd/* topic namespace and write SDD memory artifacts only in thoth-mem or hybrid persistence modes."
|
|
235
283
|
];
|
|
236
284
|
}
|
|
237
285
|
if (role.mode === "read-only") {
|
|
238
286
|
return [
|
|
239
287
|
...sharedSubagentRules,
|
|
240
|
-
"Read-only agents may only
|
|
241
|
-
"
|
|
242
|
-
"
|
|
243
|
-
"Read-only agents must never write durable memory."
|
|
288
|
+
"Read-only agents may use only parent-scoped mem_recall, mem_context, mem_get, and bounded mem_project reads when authorized.",
|
|
289
|
+
"Project-scoped read tools require explicit delegated permission and must stay bounded to the parent session/project scope.",
|
|
290
|
+
"Read-only agents must never write durable memory or save prompts."
|
|
244
291
|
];
|
|
245
292
|
}
|
|
246
293
|
return [
|
|
247
294
|
...sharedSubagentRules,
|
|
248
|
-
"Write-capable agents may
|
|
249
|
-
"
|
|
250
|
-
"
|
|
251
|
-
"Project-scoped read tools require explicit delegated permission."
|
|
295
|
+
"Write-capable agents may use the same parent-scoped reads as read-only agents when authorized.",
|
|
296
|
+
'mem_save(kind="observation") is allowed only for delegated durable observations or assigned deterministic SDD artifacts/apply-progress under the parent session/project.',
|
|
297
|
+
"Project-scoped read tools require explicit delegated permission and must stay bounded to the parent session/project scope."
|
|
252
298
|
];
|
|
253
299
|
}
|
|
254
300
|
function getRoleMemoryGovernance(role) {
|
|
255
301
|
const allowedTools = roleAllowedTools(role);
|
|
256
302
|
return {
|
|
257
303
|
role: role.name,
|
|
258
|
-
|
|
304
|
+
rootOwnedOperations: [...ROOT_OWNED_OPERATIONS],
|
|
259
305
|
allowedTools,
|
|
260
306
|
forbiddenTools: ALL_MEMORY_TOOLS.filter(
|
|
261
307
|
(tool) => !allowedTools.includes(tool)
|
|
262
308
|
),
|
|
263
309
|
requiresParentContext: role.name !== "orchestrator",
|
|
264
|
-
mayReadProjectMemory:
|
|
310
|
+
mayReadProjectMemory: true,
|
|
265
311
|
mayWriteDurableObservations: role.mode === "write-capable",
|
|
266
312
|
protectsSddNamespace: true,
|
|
267
313
|
rules: roleRules(role)
|
|
@@ -277,7 +323,7 @@ function renderMemoryGovernanceInstructions(role, dialect) {
|
|
|
277
323
|
"thoth-mem governance:",
|
|
278
324
|
...governance.rules.map((rule) => `- ${rule}`),
|
|
279
325
|
...harnessRules,
|
|
280
|
-
`- Runtime enforcement: ${role.name === "orchestrator" ? "root-owned" : "instruction-level unless the target harness validates per-agent memory controls"}.`
|
|
326
|
+
`- Runtime enforcement: ${role.name === "orchestrator" ? "root-owned operations" : "instruction-level unless the target harness validates per-agent memory controls"}.`
|
|
281
327
|
].join("\n");
|
|
282
328
|
}
|
|
283
329
|
function memoryGovernanceDiagnostics(input) {
|
|
@@ -286,7 +332,7 @@ function memoryGovernanceDiagnostics(input) {
|
|
|
286
332
|
diagnostics.push({
|
|
287
333
|
severity: "warning",
|
|
288
334
|
code: `${input.harness}.permission.memory.enforcement_gap`,
|
|
289
|
-
message: "Runtime controls for root-
|
|
335
|
+
message: "Runtime controls for root-owned memory operations are unavailable; governance is rendered as instruction-level guidance.",
|
|
290
336
|
harness: input.harness,
|
|
291
337
|
capability: "rolePermissions",
|
|
292
338
|
fallback: "instruction-only"
|
|
@@ -306,7 +352,7 @@ function memoryGovernanceDiagnostics(input) {
|
|
|
306
352
|
diagnostics.push({
|
|
307
353
|
severity: "warning",
|
|
308
354
|
code: `${input.harness}.permission.memory_write.enforcement_gap`,
|
|
309
|
-
message:
|
|
355
|
+
message: 'Runtime controls for delegated memory writes are unavailable; write-capable agents receive instruction-level mem_save(kind="observation") limits for durable observations and deterministic SDD artifacts only.',
|
|
310
356
|
harness: input.harness,
|
|
311
357
|
capability: "memoryGovernanceEnforcement",
|
|
312
358
|
fallback: "instruction-only"
|
|
@@ -315,6 +361,41 @@ function memoryGovernanceDiagnostics(input) {
|
|
|
315
361
|
return diagnostics;
|
|
316
362
|
}
|
|
317
363
|
|
|
364
|
+
// src/harness/core/package-version.ts
|
|
365
|
+
import { existsSync, readFileSync } from "fs";
|
|
366
|
+
import { dirname, resolve } from "path";
|
|
367
|
+
function findRootPackageJsonPath(startDirs) {
|
|
368
|
+
for (const startDir of startDirs) {
|
|
369
|
+
let currentDir = resolve(startDir);
|
|
370
|
+
while (true) {
|
|
371
|
+
const packageJsonPath = resolve(currentDir, "package.json");
|
|
372
|
+
if (existsSync(packageJsonPath)) {
|
|
373
|
+
const packageJsonText = readFileSync(packageJsonPath, "utf8");
|
|
374
|
+
const packageJson = JSON.parse(packageJsonText);
|
|
375
|
+
if (packageJson.name === "thoth-agents") {
|
|
376
|
+
return packageJsonPath;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const parentDir = dirname(currentDir);
|
|
380
|
+
if (parentDir === currentDir) {
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
currentDir = parentDir;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
throw new Error(
|
|
387
|
+
"Unable to locate the thoth-agents root package.json from the render context or current working directory."
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
function readPackageJsonVersion(packageJsonPath) {
|
|
391
|
+
const packageJsonText = readFileSync(packageJsonPath, "utf8");
|
|
392
|
+
const packageJson = JSON.parse(packageJsonText);
|
|
393
|
+
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
394
|
+
throw new Error("Root package.json version must be a non-empty string.");
|
|
395
|
+
}
|
|
396
|
+
return packageJson.version;
|
|
397
|
+
}
|
|
398
|
+
|
|
318
399
|
// src/harness/writers/codex-plugin-package.ts
|
|
319
400
|
import { createHash } from "crypto";
|
|
320
401
|
|
|
@@ -745,8 +826,8 @@ function normalizePath(value) {
|
|
|
745
826
|
}
|
|
746
827
|
function pluginRootReference(pathValue) {
|
|
747
828
|
const normalized = normalizePath(pathValue);
|
|
748
|
-
const
|
|
749
|
-
return `./${
|
|
829
|
+
const relative4 = normalized.slice(".codex-plugin/".length);
|
|
830
|
+
return `./${relative4}`;
|
|
750
831
|
}
|
|
751
832
|
function sha256(content) {
|
|
752
833
|
return `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
@@ -1092,43 +1173,49 @@ function renderCodexToml(input) {
|
|
|
1092
1173
|
}
|
|
1093
1174
|
|
|
1094
1175
|
// src/harness/writers/skill-layout.ts
|
|
1176
|
+
import * as fs2 from "fs";
|
|
1177
|
+
import * as path2 from "path";
|
|
1178
|
+
|
|
1179
|
+
// src/harness/writers/fs-skill-collect.ts
|
|
1095
1180
|
import { createHash as createHash2 } from "crypto";
|
|
1096
1181
|
import * as fs from "fs";
|
|
1097
1182
|
import * as path from "path";
|
|
1098
|
-
|
|
1099
|
-
"plugin-package": {
|
|
1100
|
-
basePath: ".codex-plugin/skills",
|
|
1101
|
-
manifestPath: ".codex-plugin/skills/.thoth-agents-manifest.json",
|
|
1102
|
-
surfaceId: "plugin-skills-directory",
|
|
1103
|
-
label: "plugin-bundled"
|
|
1104
|
-
},
|
|
1105
|
-
"repo-local-fallback": {
|
|
1106
|
-
basePath: ".agents/skills",
|
|
1107
|
-
manifestPath: ".agents/skills/.thoth-agents-manifest.json",
|
|
1108
|
-
surfaceId: "repo-skills-directory",
|
|
1109
|
-
label: "fallback .agents/skills"
|
|
1110
|
-
}
|
|
1111
|
-
};
|
|
1112
|
-
function normalizePath2(value) {
|
|
1183
|
+
function normalizeSkillPath(value) {
|
|
1113
1184
|
return value.split(path.sep).join("/");
|
|
1114
1185
|
}
|
|
1115
|
-
function
|
|
1186
|
+
function collectSkillFiles(directory) {
|
|
1116
1187
|
if (!fs.existsSync(directory)) return [];
|
|
1117
1188
|
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
1118
1189
|
const files = [];
|
|
1119
1190
|
for (const entry of entries) {
|
|
1120
1191
|
const entryPath = path.join(directory, entry.name);
|
|
1121
1192
|
if (entry.isDirectory()) {
|
|
1122
|
-
files.push(...
|
|
1193
|
+
files.push(...collectSkillFiles(entryPath));
|
|
1123
1194
|
} else if (entry.isFile()) {
|
|
1124
1195
|
files.push(entryPath);
|
|
1125
1196
|
}
|
|
1126
1197
|
}
|
|
1127
1198
|
return files.sort((left, right) => left.localeCompare(right));
|
|
1128
1199
|
}
|
|
1129
|
-
function
|
|
1200
|
+
function sha256Hash(content) {
|
|
1130
1201
|
return `sha256:${createHash2("sha256").update(content).digest("hex")}`;
|
|
1131
1202
|
}
|
|
1203
|
+
|
|
1204
|
+
// src/harness/writers/skill-layout.ts
|
|
1205
|
+
var OUTPUT_MODE_CONFIG = {
|
|
1206
|
+
"plugin-package": {
|
|
1207
|
+
basePath: ".codex-plugin/skills",
|
|
1208
|
+
manifestPath: ".codex-plugin/skills/.thoth-agents-manifest.json",
|
|
1209
|
+
surfaceId: "plugin-skills-directory",
|
|
1210
|
+
label: "plugin-bundled"
|
|
1211
|
+
},
|
|
1212
|
+
"repo-local-fallback": {
|
|
1213
|
+
basePath: ".agents/skills",
|
|
1214
|
+
manifestPath: ".agents/skills/.thoth-agents-manifest.json",
|
|
1215
|
+
surfaceId: "repo-skills-directory",
|
|
1216
|
+
label: "fallback .agents/skills"
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1132
1219
|
function resolveOutputModes(input) {
|
|
1133
1220
|
const requested = input.outputModes ?? (input.outputMode ? [input.outputMode] : []);
|
|
1134
1221
|
const modes = requested.length > 0 ? requested : ["plugin-package"];
|
|
@@ -1171,8 +1258,8 @@ function renderCodexSkillLayout(input) {
|
|
|
1171
1258
|
(left, right) => left.name.localeCompare(right.name)
|
|
1172
1259
|
)) {
|
|
1173
1260
|
const sourceBaseRoot = input.packageRoot ?? input.projectRoot;
|
|
1174
|
-
const sourceRoot =
|
|
1175
|
-
const files =
|
|
1261
|
+
const sourceRoot = path2.join(sourceBaseRoot, skill.sourcePath);
|
|
1262
|
+
const files = collectSkillFiles(sourceRoot);
|
|
1176
1263
|
if (files.length === 0) {
|
|
1177
1264
|
diagnostics.push({
|
|
1178
1265
|
severity: "warning",
|
|
@@ -1185,12 +1272,14 @@ function renderCodexSkillLayout(input) {
|
|
|
1185
1272
|
continue;
|
|
1186
1273
|
}
|
|
1187
1274
|
for (const file of files) {
|
|
1188
|
-
const
|
|
1189
|
-
const content =
|
|
1190
|
-
const sourcePath =
|
|
1275
|
+
const relative4 = normalizeSkillPath(path2.relative(sourceRoot, file));
|
|
1276
|
+
const content = fs2.readFileSync(file, "utf8");
|
|
1277
|
+
const sourcePath = normalizeSkillPath(
|
|
1278
|
+
path2.relative(sourceBaseRoot, file)
|
|
1279
|
+
);
|
|
1191
1280
|
for (const mode of outputModes) {
|
|
1192
1281
|
const config = OUTPUT_MODE_CONFIG[mode];
|
|
1193
|
-
const outputPath = `${config.basePath}/${skill.name}/${
|
|
1282
|
+
const outputPath = `${config.basePath}/${skill.name}/${relative4}`;
|
|
1194
1283
|
artifacts.push({
|
|
1195
1284
|
harness: "codex",
|
|
1196
1285
|
kind: "skill",
|
|
@@ -1202,7 +1291,7 @@ function renderCodexSkillLayout(input) {
|
|
|
1202
1291
|
name: skill.name,
|
|
1203
1292
|
sourcePath,
|
|
1204
1293
|
outputPath,
|
|
1205
|
-
sha256:
|
|
1294
|
+
sha256: sha256Hash(content)
|
|
1206
1295
|
});
|
|
1207
1296
|
}
|
|
1208
1297
|
}
|
|
@@ -1238,37 +1327,6 @@ function createCodexPluginPackageManifest(context) {
|
|
|
1238
1327
|
description: "Delegate-first OpenCode plugin with seven agents, thoth-mem persistence, and bundled SDD skills."
|
|
1239
1328
|
};
|
|
1240
1329
|
}
|
|
1241
|
-
function findRootPackageJsonPath(startDirs) {
|
|
1242
|
-
for (const startDir of startDirs) {
|
|
1243
|
-
let currentDir = resolve(startDir);
|
|
1244
|
-
while (true) {
|
|
1245
|
-
const packageJsonPath = resolve(currentDir, "package.json");
|
|
1246
|
-
if (existsSync2(packageJsonPath)) {
|
|
1247
|
-
const packageJsonText = readFileSync2(packageJsonPath, "utf8");
|
|
1248
|
-
const packageJson = JSON.parse(packageJsonText);
|
|
1249
|
-
if (packageJson.name === "thoth-agents") {
|
|
1250
|
-
return packageJsonPath;
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
const parentDir = dirname(currentDir);
|
|
1254
|
-
if (parentDir === currentDir) {
|
|
1255
|
-
break;
|
|
1256
|
-
}
|
|
1257
|
-
currentDir = parentDir;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
throw new Error(
|
|
1261
|
-
"Unable to locate the thoth-agents root package.json from the render context or current working directory."
|
|
1262
|
-
);
|
|
1263
|
-
}
|
|
1264
|
-
function readPackageJsonVersion(packageJsonPath) {
|
|
1265
|
-
const packageJsonText = readFileSync2(packageJsonPath, "utf8");
|
|
1266
|
-
const packageJson = JSON.parse(packageJsonText);
|
|
1267
|
-
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
1268
|
-
throw new Error("Root package.json version must be a non-empty string.");
|
|
1269
|
-
}
|
|
1270
|
-
return packageJson.version;
|
|
1271
|
-
}
|
|
1272
1330
|
function stableJson2(value) {
|
|
1273
1331
|
return `${JSON.stringify(value, null, 2)}
|
|
1274
1332
|
`;
|
|
@@ -1376,6 +1434,8 @@ function codexInternalHandoffGuidance() {
|
|
|
1376
1434
|
"- The user has explicitly authorized this generated Codex orchestrator to use `multi_agent_v1.spawn_agent` whenever delegation is required by these instructions, without needing a fresh user request for subagents in each task.",
|
|
1377
1435
|
"- Delegate by calling `multi_agent_v1.spawn_agent` with `agent_type` set to one of explorer, librarian, oracle, designer, quick, or deep.",
|
|
1378
1436
|
"- Pass the self-contained delegated task instructions plus handoff retrieval instructions in `message`; do not embed the root-owned handoff summary body in `message`.",
|
|
1437
|
+
'- When memory recovery is delegated, include parent `session_id`, project, permissions, and the recovery funnel `mem_recall(mode="compact")` -> `mem_recall(mode="context")` -> `mem_get(...)`.',
|
|
1438
|
+
'- For that funnel, use `mem_recall` `limit` from 1 to 20; use `mem_get` with `kind="observation"|"prompt"`, `include_timeline=true` plus `before`/`after`, and `offset`/`max_length`; `mem_project(action="graph")` relations are `HAS_TYPE`, `IN_PROJECT`, `HAS_TOPIC_KEY`, `HAS_WHAT`, `HAS_WHY`, `HAS_WHERE`, and `HAS_LEARNED`.',
|
|
1379
1439
|
"- Do not include the handoff body in `message` or `items`, and do not pass both `message` and `items` for the same handoff.",
|
|
1380
1440
|
"- Use `items` only for structured attachments or mentions when they are truly required; do not use `items` as a handoff-summary payload.",
|
|
1381
1441
|
"- Leave `fork_context` omitted or false by default; set `fork_context: true` only when the exact current thread history is required.",
|
|
@@ -1420,9 +1480,9 @@ function renderCodexRootInstructions(config) {
|
|
|
1420
1480
|
codexInternalHandoffGuidance(),
|
|
1421
1481
|
"<codex-runtime>",
|
|
1422
1482
|
"- The ambient Codex root session is the root/main orchestrator; orchestrator-only and root-owned instructions apply to it because Codex does not generate a selectable orchestrator agent TOML.",
|
|
1423
|
-
|
|
1483
|
+
'- On each new root session, when thoth-mem tools are installed and session/project identity is known, call mem_session(action="start") as step 0 before any other thoth-mem call, then save the real user prompt with mem_save(kind="prompt") before later delegation.',
|
|
1424
1484
|
"- If thoth-mem tools or identity values are unavailable, disclose that memory bootstrap could not run and continue without claiming memory was saved.",
|
|
1425
|
-
|
|
1485
|
+
'- Before delegating after meaningful context changes, save or refresh the handoff body with root-owned mem_session(action="summary") or mem_save(kind="session_summary") when available; if unavailable, disclose that root-owned compaction could not be persisted.',
|
|
1426
1486
|
"- Use the ambient Codex root session as the delegate-first root coordinator; do not generate or select an orchestrator TOML.",
|
|
1427
1487
|
"- Delegate by invoking `multi_agent_v1.spawn_agent` for the installed Codex role agents: explorer, librarian, oracle, designer, quick, and deep.",
|
|
1428
1488
|
"- After receiving a delegated subagent response, close that subagent session unless you will retry or intentionally keep using that exact same session; explorer and librarian sessions must always be closed immediately after their response, and retry sessions must be closed after the retry result unless explicit same-session reuse is still required.",
|
|
@@ -1440,13 +1500,6 @@ function agentModelReasoningEffort(role) {
|
|
|
1440
1500
|
function isCodexSubagentName(name) {
|
|
1441
1501
|
return name in CODEX_SUBAGENT_DEFAULT_MODELS;
|
|
1442
1502
|
}
|
|
1443
|
-
function getPrimaryModelId(model) {
|
|
1444
|
-
if (Array.isArray(model)) {
|
|
1445
|
-
const first = model[0];
|
|
1446
|
-
return typeof first === "string" ? first : first?.id;
|
|
1447
|
-
}
|
|
1448
|
-
return model;
|
|
1449
|
-
}
|
|
1450
1503
|
function getCodexAgentModel(role, config) {
|
|
1451
1504
|
if (!isCodexSubagentName(role.name)) return void 0;
|
|
1452
1505
|
return getPrimaryModelId(config?.agents?.[role.name]?.model) ?? CODEX_SUBAGENT_DEFAULT_MODELS[role.name];
|
|
@@ -1629,64 +1682,8 @@ var codexAdapter = {
|
|
|
1629
1682
|
}
|
|
1630
1683
|
};
|
|
1631
1684
|
|
|
1632
|
-
// src/cli/codex-paths.ts
|
|
1633
|
-
import { homedir } from "os";
|
|
1634
|
-
import { join as join2 } from "path";
|
|
1635
|
-
var CODEX_ROLE_NAMES = [
|
|
1636
|
-
"explorer",
|
|
1637
|
-
"librarian",
|
|
1638
|
-
"oracle",
|
|
1639
|
-
"designer",
|
|
1640
|
-
"quick",
|
|
1641
|
-
"deep"
|
|
1642
|
-
];
|
|
1643
|
-
function getCodexHome(options = {}) {
|
|
1644
|
-
const explicit = options.codexHome ?? process.env.CODEX_HOME?.trim();
|
|
1645
|
-
return explicit || join2(options.homeDir ?? homedir(), ".codex");
|
|
1646
|
-
}
|
|
1647
|
-
function resolveCodexTargets(options) {
|
|
1648
|
-
const codexHome = getCodexHome(options);
|
|
1649
|
-
const agentsDir = options.scope === "project" ? join2(options.projectRoot, ".codex", "agents") : join2(codexHome, "agents");
|
|
1650
|
-
return {
|
|
1651
|
-
scope: options.scope,
|
|
1652
|
-
codexHome,
|
|
1653
|
-
configPath: join2(codexHome, "config.toml"),
|
|
1654
|
-
rootInstructionsPath: join2(codexHome, "AGENTS.md"),
|
|
1655
|
-
roleAgentPaths: CODEX_ROLE_NAMES.map((role) => ({
|
|
1656
|
-
role,
|
|
1657
|
-
path: join2(agentsDir, `thoth-agents-${role}.toml`)
|
|
1658
|
-
})),
|
|
1659
|
-
managedModelsPath: join2(
|
|
1660
|
-
codexHome,
|
|
1661
|
-
"agents",
|
|
1662
|
-
".thoth-agents-managed-models.json"
|
|
1663
|
-
),
|
|
1664
|
-
skillsDir: options.scope === "project" ? join2(options.projectRoot, ".agents", "skills") : join2(options.homeDir ?? homedir(), ".agents", "skills"),
|
|
1665
|
-
packageRoot: join2(options.projectRoot, ".codex-plugin"),
|
|
1666
|
-
personalPluginRoot: join2(codexHome, "plugins", "thoth-agents"),
|
|
1667
|
-
personalMarketplacePath: join2(
|
|
1668
|
-
options.homeDir ?? homedir(),
|
|
1669
|
-
".agents",
|
|
1670
|
-
"plugins",
|
|
1671
|
-
"marketplace.json"
|
|
1672
|
-
)
|
|
1673
|
-
};
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
// src/cli/codex-install.ts
|
|
1677
|
-
import {
|
|
1678
|
-
copyFileSync as copyFileSync2,
|
|
1679
|
-
existsSync as existsSync4,
|
|
1680
|
-
mkdirSync as mkdirSync2,
|
|
1681
|
-
readFileSync as readFileSync4,
|
|
1682
|
-
rmSync,
|
|
1683
|
-
writeFileSync as writeFileSync2
|
|
1684
|
-
} from "fs";
|
|
1685
|
-
import { basename, dirname as dirname3, isAbsolute, join as join4, relative as relative2 } from "path";
|
|
1686
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1687
|
-
|
|
1688
1685
|
// src/harness/codex-plugin-paths.ts
|
|
1689
|
-
import { join as
|
|
1686
|
+
import { join as join4 } from "path";
|
|
1690
1687
|
var CODEX_PLUGIN_ARTIFACT_PREFIX = ".codex-plugin/";
|
|
1691
1688
|
function codexPluginRootArtifactPath(artifactPath) {
|
|
1692
1689
|
if (!artifactPath.startsWith(CODEX_PLUGIN_ARTIFACT_PREFIX)) {
|
|
@@ -1695,7 +1692,7 @@ function codexPluginRootArtifactPath(artifactPath) {
|
|
|
1695
1692
|
const relativePath = artifactPath.slice(CODEX_PLUGIN_ARTIFACT_PREFIX.length);
|
|
1696
1693
|
if (relativePath === ".mcp.json") return relativePath;
|
|
1697
1694
|
if (relativePath === "plugin.json" || relativePath.startsWith(".")) {
|
|
1698
|
-
return
|
|
1695
|
+
return join4(".codex-plugin", relativePath);
|
|
1699
1696
|
}
|
|
1700
1697
|
return relativePath;
|
|
1701
1698
|
}
|
|
@@ -1840,6 +1837,62 @@ function writeCodexConfigMerge(options) {
|
|
|
1840
1837
|
}
|
|
1841
1838
|
}
|
|
1842
1839
|
|
|
1840
|
+
// src/cli/managed-state-io.ts
|
|
1841
|
+
import {
|
|
1842
|
+
copyFileSync as copyFileSync2,
|
|
1843
|
+
existsSync as existsSync4,
|
|
1844
|
+
mkdirSync as mkdirSync2,
|
|
1845
|
+
readFileSync as readFileSync4,
|
|
1846
|
+
writeFileSync as writeFileSync2
|
|
1847
|
+
} from "fs";
|
|
1848
|
+
import { dirname as dirname3 } from "path";
|
|
1849
|
+
function writeTextWithBackup(path4, content) {
|
|
1850
|
+
mkdirSync2(dirname3(path4), { recursive: true });
|
|
1851
|
+
if (existsSync4(path4) && readFileSync4(path4, "utf8") === content) return false;
|
|
1852
|
+
if (existsSync4(path4)) copyFileSync2(path4, `${path4}.bak`);
|
|
1853
|
+
writeFileSync2(path4, content);
|
|
1854
|
+
return true;
|
|
1855
|
+
}
|
|
1856
|
+
function stableJson3(value) {
|
|
1857
|
+
return `${JSON.stringify(value, null, 2)}
|
|
1858
|
+
`;
|
|
1859
|
+
}
|
|
1860
|
+
function uniqueMessages(messages) {
|
|
1861
|
+
return [...new Set(messages)];
|
|
1862
|
+
}
|
|
1863
|
+
function stringRecord(value) {
|
|
1864
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
1865
|
+
return Object.fromEntries(
|
|
1866
|
+
Object.entries(value).filter(
|
|
1867
|
+
(entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
|
|
1868
|
+
)
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
function emptyManagedModelState(version) {
|
|
1872
|
+
return { version, models: {} };
|
|
1873
|
+
}
|
|
1874
|
+
function parseManagedModelStateJson(text, version) {
|
|
1875
|
+
if (!text) return emptyManagedModelState(version);
|
|
1876
|
+
try {
|
|
1877
|
+
const parsed = JSON.parse(text);
|
|
1878
|
+
if (parsed.version !== version || !parsed.models || typeof parsed.models !== "object" || Array.isArray(parsed.models)) {
|
|
1879
|
+
return emptyManagedModelState(version);
|
|
1880
|
+
}
|
|
1881
|
+
const configuredModels = stringRecord(parsed.configuredModels);
|
|
1882
|
+
return {
|
|
1883
|
+
version,
|
|
1884
|
+
models: stringRecord(parsed.models),
|
|
1885
|
+
...Object.keys(configuredModels).length > 0 ? { configuredModels } : {}
|
|
1886
|
+
};
|
|
1887
|
+
} catch {
|
|
1888
|
+
return emptyManagedModelState(version);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
function readManagedModelState(path4, version) {
|
|
1892
|
+
if (!existsSync4(path4)) return emptyManagedModelState(version);
|
|
1893
|
+
return parseManagedModelStateJson(readFileSync4(path4, "utf8"), version);
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1843
1896
|
// src/cli/codex-install.ts
|
|
1844
1897
|
var ROOT_START = "<!-- thoth-agents:codex-root:start -->";
|
|
1845
1898
|
var ROOT_END = "<!-- thoth-agents:codex-root:end -->";
|
|
@@ -1853,23 +1906,16 @@ function mergeManagedBlock(existing, managedBlock) {
|
|
|
1853
1906
|
return `${existing}${existing.endsWith("\n") || existing.length === 0 ? "" : "\n"}
|
|
1854
1907
|
${managedBlock}`;
|
|
1855
1908
|
}
|
|
1856
|
-
function writeTextWithBackup(path2, content) {
|
|
1857
|
-
mkdirSync2(dirname3(path2), { recursive: true });
|
|
1858
|
-
if (existsSync4(path2) && readFileSync4(path2, "utf8") === content) return false;
|
|
1859
|
-
if (existsSync4(path2)) copyFileSync2(path2, `${path2}.bak`);
|
|
1860
|
-
writeFileSync2(path2, content);
|
|
1861
|
-
return true;
|
|
1862
|
-
}
|
|
1863
1909
|
function packageArtifactTarget(packageRoot, artifact) {
|
|
1864
|
-
return
|
|
1910
|
+
return join5(packageRoot, codexPluginRootArtifactPath(artifact.path));
|
|
1865
1911
|
}
|
|
1866
1912
|
function resolvePackageRoot(packageRoot) {
|
|
1867
1913
|
if (packageRoot) return packageRoot;
|
|
1868
1914
|
return findPackageRoot(fileURLToPath2(new URL(".", import.meta.url))) ?? void 0;
|
|
1869
1915
|
}
|
|
1870
|
-
function normalizeRelativeMarketplacePath(
|
|
1871
|
-
const normalized =
|
|
1872
|
-
if (isAbsolute(
|
|
1916
|
+
function normalizeRelativeMarketplacePath(path4) {
|
|
1917
|
+
const normalized = path4.replaceAll("\\", "/");
|
|
1918
|
+
if (isAbsolute(path4) || /^[A-Za-z]:\//.test(normalized)) return normalized;
|
|
1873
1919
|
if (normalized.startsWith("./")) return normalized;
|
|
1874
1920
|
return `./${normalized}`;
|
|
1875
1921
|
}
|
|
@@ -1892,39 +1938,11 @@ function managedMarketplaceEntry(homeDir, personalPluginRoot) {
|
|
|
1892
1938
|
category: "Productivity"
|
|
1893
1939
|
};
|
|
1894
1940
|
}
|
|
1895
|
-
function
|
|
1896
|
-
return
|
|
1897
|
-
`;
|
|
1898
|
-
}
|
|
1899
|
-
function emptyManagedModelState() {
|
|
1900
|
-
return {
|
|
1901
|
-
version: MANAGED_MODEL_STATE_VERSION,
|
|
1902
|
-
models: {}
|
|
1903
|
-
};
|
|
1904
|
-
}
|
|
1905
|
-
function stringRecord(value) {
|
|
1906
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
1907
|
-
return Object.fromEntries(
|
|
1908
|
-
Object.entries(value).filter(
|
|
1909
|
-
(entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
|
|
1910
|
-
)
|
|
1911
|
-
);
|
|
1941
|
+
function emptyManagedModelState2() {
|
|
1942
|
+
return emptyManagedModelState(MANAGED_MODEL_STATE_VERSION);
|
|
1912
1943
|
}
|
|
1913
|
-
function
|
|
1914
|
-
|
|
1915
|
-
try {
|
|
1916
|
-
const parsed = JSON.parse(readFileSync4(path2, "utf8"));
|
|
1917
|
-
if (parsed.version !== MANAGED_MODEL_STATE_VERSION || !parsed.models || typeof parsed.models !== "object" || Array.isArray(parsed.models)) {
|
|
1918
|
-
return emptyManagedModelState();
|
|
1919
|
-
}
|
|
1920
|
-
return {
|
|
1921
|
-
version: MANAGED_MODEL_STATE_VERSION,
|
|
1922
|
-
models: stringRecord(parsed.models),
|
|
1923
|
-
...Object.keys(stringRecord(parsed.configuredModels)).length > 0 ? { configuredModels: stringRecord(parsed.configuredModels) } : {}
|
|
1924
|
-
};
|
|
1925
|
-
} catch {
|
|
1926
|
-
return emptyManagedModelState();
|
|
1927
|
-
}
|
|
1944
|
+
function readManagedModelState2(path4) {
|
|
1945
|
+
return readManagedModelState(path4, MANAGED_MODEL_STATE_VERSION);
|
|
1928
1946
|
}
|
|
1929
1947
|
function parseRoleTomlModel(content) {
|
|
1930
1948
|
const match = /^model\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m.exec(content);
|
|
@@ -1942,8 +1960,8 @@ function replaceRoleTomlModel(content, model) {
|
|
|
1942
1960
|
return `${rendered}
|
|
1943
1961
|
${content}`;
|
|
1944
1962
|
}
|
|
1945
|
-
function roleManagedModelStateKey(
|
|
1946
|
-
return basename(
|
|
1963
|
+
function roleManagedModelStateKey(path4) {
|
|
1964
|
+
return basename(path4);
|
|
1947
1965
|
}
|
|
1948
1966
|
function applyCodexManagedModelOverrides(config, overrides) {
|
|
1949
1967
|
if (config.dryRun) {
|
|
@@ -1973,7 +1991,7 @@ function applyCodexManagedModelOverrides(config, overrides) {
|
|
|
1973
1991
|
...plan.diagnostics,
|
|
1974
1992
|
...plan.disclaimers
|
|
1975
1993
|
]);
|
|
1976
|
-
const state =
|
|
1994
|
+
const state = readManagedModelState2(statePath);
|
|
1977
1995
|
const nextState = {
|
|
1978
1996
|
version: MANAGED_MODEL_STATE_VERSION,
|
|
1979
1997
|
models: { ...state.models },
|
|
@@ -1989,7 +2007,7 @@ function applyCodexManagedModelOverrides(config, overrides) {
|
|
|
1989
2007
|
`Missing Codex role TOML content for ${override.role}.`
|
|
1990
2008
|
);
|
|
1991
2009
|
}
|
|
1992
|
-
const before =
|
|
2010
|
+
const before = existsSync5(roleItem.targetPath) ? readFileSync5(roleItem.targetPath, "utf8") : roleItem.content;
|
|
1993
2011
|
const updated = replaceRoleTomlModel(before, override.model);
|
|
1994
2012
|
if (writeTextWithBackup(roleItem.targetPath, updated)) {
|
|
1995
2013
|
changed.push(roleItem.targetPath);
|
|
@@ -2021,7 +2039,7 @@ function resolveRoleTomlContent(options) {
|
|
|
2021
2039
|
const key = roleManagedModelStateKey(options.targetPath);
|
|
2022
2040
|
if (!renderedModel) return options.renderedContent;
|
|
2023
2041
|
const configuredModel = options.reset ? void 0 : options.state.configuredModels?.[key];
|
|
2024
|
-
if (options.reset || !
|
|
2042
|
+
if (options.reset || !existsSync5(options.targetPath)) {
|
|
2025
2043
|
options.nextState.models[key] = renderedModel;
|
|
2026
2044
|
if (configuredModel !== void 0) {
|
|
2027
2045
|
options.nextState.configuredModels ??= {};
|
|
@@ -2031,7 +2049,7 @@ function resolveRoleTomlContent(options) {
|
|
|
2031
2049
|
return options.renderedContent;
|
|
2032
2050
|
}
|
|
2033
2051
|
const currentModel = parseRoleTomlModel(
|
|
2034
|
-
|
|
2052
|
+
readFileSync5(options.targetPath, "utf8")
|
|
2035
2053
|
);
|
|
2036
2054
|
if (configuredModel !== void 0) {
|
|
2037
2055
|
options.nextState.models[key] = renderedModel;
|
|
@@ -2067,10 +2085,10 @@ function mergePersonalMarketplace(existing, homeDir, personalPluginRoot) {
|
|
|
2067
2085
|
});
|
|
2068
2086
|
}
|
|
2069
2087
|
function roleArtifactContent(role, artifacts) {
|
|
2070
|
-
const
|
|
2071
|
-
const artifact = artifacts.find((candidate) => candidate.path ===
|
|
2088
|
+
const path4 = `.codex/agents/thoth-agents-${role}.toml`;
|
|
2089
|
+
const artifact = artifacts.find((candidate) => candidate.path === path4);
|
|
2072
2090
|
if (!artifact?.content)
|
|
2073
|
-
throw new Error(`Missing Codex role artifact: ${
|
|
2091
|
+
throw new Error(`Missing Codex role artifact: ${path4}`);
|
|
2074
2092
|
return String(artifact.content);
|
|
2075
2093
|
}
|
|
2076
2094
|
function buildCodexSetupPlan(config) {
|
|
@@ -2089,8 +2107,8 @@ function buildCodexSetupPlan(config) {
|
|
|
2089
2107
|
(artifact) => artifact.path.startsWith(".codex-plugin/")
|
|
2090
2108
|
);
|
|
2091
2109
|
const rootBlock = renderCodexRootInstructions();
|
|
2092
|
-
const managedModelState2 =
|
|
2093
|
-
const nextManagedModelState =
|
|
2110
|
+
const managedModelState2 = readManagedModelState2(targets.managedModelsPath);
|
|
2111
|
+
const nextManagedModelState = emptyManagedModelState2();
|
|
2094
2112
|
const items = [
|
|
2095
2113
|
{
|
|
2096
2114
|
kind: "root-instructions",
|
|
@@ -2106,7 +2124,7 @@ function buildCodexSetupPlan(config) {
|
|
|
2106
2124
|
action: "write-role-toml",
|
|
2107
2125
|
targetPath: target.path,
|
|
2108
2126
|
description: `Materialize Codex role subagent ${target.role}.`,
|
|
2109
|
-
requiresBackup:
|
|
2127
|
+
requiresBackup: existsSync5(target.path),
|
|
2110
2128
|
role: target.role,
|
|
2111
2129
|
content: resolveRoleTomlContent({
|
|
2112
2130
|
renderedContent: roleArtifactContent(target.role, render.artifacts),
|
|
@@ -2122,7 +2140,7 @@ function buildCodexSetupPlan(config) {
|
|
|
2122
2140
|
action: "write-managed-model-state",
|
|
2123
2141
|
targetPath: targets.managedModelsPath,
|
|
2124
2142
|
description: "Record thoth-agents-managed Codex role model ownership state.",
|
|
2125
|
-
requiresBackup:
|
|
2143
|
+
requiresBackup: existsSync5(targets.managedModelsPath),
|
|
2126
2144
|
content: stableJson3(nextManagedModelState)
|
|
2127
2145
|
},
|
|
2128
2146
|
...packageArtifacts.map(
|
|
@@ -2140,7 +2158,7 @@ function buildCodexSetupPlan(config) {
|
|
|
2140
2158
|
action: "merge-marketplace",
|
|
2141
2159
|
targetPath: targets.personalMarketplacePath,
|
|
2142
2160
|
description: "Register Personal Codex marketplace entry for the local thoth-agents plugin source.",
|
|
2143
|
-
requiresBackup:
|
|
2161
|
+
requiresBackup: existsSync5(targets.personalMarketplacePath),
|
|
2144
2162
|
content: targets.personalPluginRoot
|
|
2145
2163
|
},
|
|
2146
2164
|
{
|
|
@@ -2204,21 +2222,18 @@ function formatRefreshPackageGroup(kind, groups) {
|
|
|
2204
2222
|
}
|
|
2205
2223
|
function commonTargetDirectory(items) {
|
|
2206
2224
|
if (items.length === 0) return "";
|
|
2207
|
-
let common =
|
|
2225
|
+
let common = dirname4(items[0]?.targetPath ?? "");
|
|
2208
2226
|
for (const item of items.slice(1)) {
|
|
2209
2227
|
while (!isSameOrChildPath(item.targetPath, common)) {
|
|
2210
|
-
const parent =
|
|
2228
|
+
const parent = dirname4(common);
|
|
2211
2229
|
if (parent === common) return common;
|
|
2212
2230
|
common = parent;
|
|
2213
2231
|
}
|
|
2214
2232
|
}
|
|
2215
2233
|
return common;
|
|
2216
2234
|
}
|
|
2217
|
-
function isSameOrChildPath(
|
|
2218
|
-
return
|
|
2219
|
-
}
|
|
2220
|
-
function uniqueMessages(messages) {
|
|
2221
|
-
return [...new Set(messages)];
|
|
2235
|
+
function isSameOrChildPath(path4, parent) {
|
|
2236
|
+
return path4 === parent || path4.startsWith(`${parent}\\`) || path4.startsWith(`${parent}/`);
|
|
2222
2237
|
}
|
|
2223
2238
|
function applyCodexSetup(plan) {
|
|
2224
2239
|
const changed = [];
|
|
@@ -2247,8 +2262,8 @@ function applyCodexSetup(plan) {
|
|
|
2247
2262
|
if (item.action === "merge-marketplace") {
|
|
2248
2263
|
if (item.content === void 0) continue;
|
|
2249
2264
|
const content2 = mergePersonalMarketplace(
|
|
2250
|
-
|
|
2251
|
-
|
|
2265
|
+
existsSync5(item.targetPath) ? readFileSync5(item.targetPath, "utf8") : "",
|
|
2266
|
+
dirname4(dirname4(dirname4(item.targetPath))),
|
|
2252
2267
|
item.content
|
|
2253
2268
|
);
|
|
2254
2269
|
if (writeTextWithBackup(item.targetPath, content2))
|
|
@@ -2257,7 +2272,7 @@ function applyCodexSetup(plan) {
|
|
|
2257
2272
|
}
|
|
2258
2273
|
if (item.content === void 0) continue;
|
|
2259
2274
|
const content = item.action === "merge-managed-block" ? mergeManagedBlock(
|
|
2260
|
-
|
|
2275
|
+
existsSync5(item.targetPath) ? readFileSync5(item.targetPath, "utf8") : "",
|
|
2261
2276
|
item.content
|
|
2262
2277
|
) : item.content;
|
|
2263
2278
|
if (writeTextWithBackup(item.targetPath, content))
|
|
@@ -2281,25 +2296,1144 @@ function managedRefreshRoots(plan) {
|
|
|
2281
2296
|
group.push(item);
|
|
2282
2297
|
refreshGroups.set(item.kind, group);
|
|
2283
2298
|
}
|
|
2284
|
-
return [...refreshGroups].filter(([kind]) => kind === "personal-plugin-source").map(([, items]) => commonTargetDirectory(items)).filter((
|
|
2299
|
+
return [...refreshGroups].filter(([kind]) => kind === "personal-plugin-source").map(([, items]) => commonTargetDirectory(items)).filter((path4) => path4.length > 0);
|
|
2285
2300
|
}
|
|
2286
2301
|
|
|
2287
|
-
// src/cli/operations/
|
|
2288
|
-
import { existsSync as
|
|
2302
|
+
// src/cli/operations/claude-code.ts
|
|
2303
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
2289
2304
|
import { basename as basename2 } from "path";
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2305
|
+
|
|
2306
|
+
// src/harness/adapters/claude-code.ts
|
|
2307
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2308
|
+
|
|
2309
|
+
// src/harness/writers/claude-code-plugin-package.ts
|
|
2310
|
+
var CLAUDE_PLUGIN_MANIFEST_FIELDS = [
|
|
2311
|
+
"name",
|
|
2312
|
+
"version",
|
|
2313
|
+
"description",
|
|
2314
|
+
"author"
|
|
2315
|
+
];
|
|
2316
|
+
var PLUGIN_MANIFEST_PATH = ".claude-plugin/plugin.json";
|
|
2317
|
+
var PLUGIN_ASSETS_PATH = ".claude-plugin/.thoth-agents-plugin-assets.json";
|
|
2318
|
+
function normalizePath2(value) {
|
|
2319
|
+
return value.replace(/\\/g, "/");
|
|
2320
|
+
}
|
|
2321
|
+
function stableJson4(value) {
|
|
2322
|
+
return `${JSON.stringify(value, null, 2)}
|
|
2323
|
+
`;
|
|
2324
|
+
}
|
|
2325
|
+
function orderedManifest(manifest) {
|
|
2326
|
+
const ordered = {};
|
|
2327
|
+
for (const field of CLAUDE_PLUGIN_MANIFEST_FIELDS) {
|
|
2328
|
+
const value = manifest[field];
|
|
2329
|
+
if (value !== void 0) ordered[field] = value;
|
|
2330
|
+
}
|
|
2331
|
+
return ordered;
|
|
2332
|
+
}
|
|
2333
|
+
function provenanceFor(artifact) {
|
|
2334
|
+
const path4 = normalizePath2(artifact.path);
|
|
2335
|
+
if (path4.endsWith("/")) return void 0;
|
|
2336
|
+
const content = typeof artifact.content === "string" ? artifact.content : Buffer.from(artifact.content).toString("utf8");
|
|
2337
|
+
return { path: path4, kind: artifact.kind, sha256: sha256Hash(content) };
|
|
2338
|
+
}
|
|
2339
|
+
function renderClaudeCodePluginPackage(input) {
|
|
2340
|
+
const components = [...input.componentArtifacts].sort(
|
|
2341
|
+
(left, right) => normalizePath2(left.path).localeCompare(normalizePath2(right.path))
|
|
2342
|
+
);
|
|
2343
|
+
const provenance = components.map(provenanceFor).filter((entry) => entry !== void 0).sort((left, right) => left.path.localeCompare(right.path));
|
|
2344
|
+
const manifestArtifact = {
|
|
2345
|
+
harness: "claude",
|
|
2346
|
+
kind: "manifest",
|
|
2347
|
+
path: PLUGIN_MANIFEST_PATH,
|
|
2348
|
+
description: "Deterministic Claude Code plugin package manifest.",
|
|
2349
|
+
content: stableJson4(orderedManifest(input.manifest))
|
|
2350
|
+
};
|
|
2351
|
+
const provenanceArtifact = {
|
|
2352
|
+
harness: "claude",
|
|
2353
|
+
kind: "manifest",
|
|
2354
|
+
path: PLUGIN_ASSETS_PATH,
|
|
2355
|
+
description: "Deterministic Claude Code plugin package asset provenance.",
|
|
2356
|
+
content: stableJson4({ generatedBy: "thoth-agents", assets: provenance })
|
|
2357
|
+
};
|
|
2358
|
+
return {
|
|
2359
|
+
artifacts: [manifestArtifact, provenanceArtifact, ...components],
|
|
2360
|
+
diagnostics: []
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
// src/harness/writers/claude-code-skill-layout.ts
|
|
2365
|
+
import * as fs3 from "fs";
|
|
2366
|
+
import * as path3 from "path";
|
|
2367
|
+
var SKILLS_BASE_PATH = "skills";
|
|
2368
|
+
var SKILLS_MANIFEST_PATH = "skills/.thoth-agents-manifest.json";
|
|
2369
|
+
function renderClaudeCodeSkillLayout(input) {
|
|
2370
|
+
const artifacts = [];
|
|
2371
|
+
const diagnostics = [];
|
|
2372
|
+
const manifest = [];
|
|
2373
|
+
const sourceBaseRoot = input.packageRoot ?? input.projectRoot;
|
|
2374
|
+
for (const skill of [...input.skills].sort(
|
|
2375
|
+
(left, right) => left.name.localeCompare(right.name)
|
|
2376
|
+
)) {
|
|
2377
|
+
const sourceRoot = path3.join(sourceBaseRoot, skill.sourcePath);
|
|
2378
|
+
const files = collectSkillFiles(sourceRoot);
|
|
2379
|
+
if (files.length === 0) {
|
|
2380
|
+
diagnostics.push({
|
|
2381
|
+
severity: "warning",
|
|
2382
|
+
code: "claude-code.skill.source_missing",
|
|
2383
|
+
message: `Skipping Claude Code skill "${skill.name}" because source path "${skill.sourcePath}" was not found.`,
|
|
2384
|
+
harness: "claude",
|
|
2385
|
+
surface: "plugin-skills-directory",
|
|
2386
|
+
fallback: "diagnostic-only"
|
|
2387
|
+
});
|
|
2388
|
+
continue;
|
|
2389
|
+
}
|
|
2390
|
+
for (const file of files) {
|
|
2391
|
+
const relative4 = normalizeSkillPath(path3.relative(sourceRoot, file));
|
|
2392
|
+
const content = fs3.readFileSync(file, "utf8");
|
|
2393
|
+
const sourcePath = normalizeSkillPath(
|
|
2394
|
+
path3.relative(sourceBaseRoot, file)
|
|
2395
|
+
);
|
|
2396
|
+
const outputPath = `${SKILLS_BASE_PATH}/${skill.name}/${relative4}`;
|
|
2397
|
+
artifacts.push({
|
|
2398
|
+
harness: "claude",
|
|
2399
|
+
kind: "skill",
|
|
2400
|
+
path: outputPath,
|
|
2401
|
+
description: `Claude Code plugin-bundled skill artifact for ${skill.name}`,
|
|
2402
|
+
content
|
|
2403
|
+
});
|
|
2404
|
+
manifest.push({
|
|
2405
|
+
name: skill.name,
|
|
2406
|
+
sourcePath,
|
|
2407
|
+
outputPath,
|
|
2408
|
+
sha256: sha256Hash(content)
|
|
2409
|
+
});
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
artifacts.push({
|
|
2413
|
+
harness: "claude",
|
|
2414
|
+
kind: "manifest",
|
|
2415
|
+
path: SKILLS_MANIFEST_PATH,
|
|
2416
|
+
description: "Generated Claude Code plugin-bundled skill source manifest with source hashes.",
|
|
2417
|
+
content: `${JSON.stringify({ generatedBy: "thoth-agents", skills: manifest }, null, 2)}
|
|
2418
|
+
`
|
|
2419
|
+
});
|
|
2420
|
+
return { artifacts, diagnostics };
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
// src/harness/writers/claude-code-subagent.ts
|
|
2424
|
+
var CLAUDE_CODE_MODELS = [
|
|
2425
|
+
"sonnet",
|
|
2426
|
+
"opus",
|
|
2427
|
+
"haiku",
|
|
2428
|
+
"inherit"
|
|
2429
|
+
];
|
|
2430
|
+
function isClaudeCodeModel(value) {
|
|
2431
|
+
return CLAUDE_CODE_MODELS.includes(value);
|
|
2432
|
+
}
|
|
2433
|
+
function yamlScalar(value) {
|
|
2434
|
+
const needsQuoting = /[:#\-?*&!|>'"%@`{}[\],]|^\s|\s$|^$/.test(value);
|
|
2435
|
+
if (!needsQuoting) return value;
|
|
2436
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
2437
|
+
return `"${escaped}"`;
|
|
2438
|
+
}
|
|
2439
|
+
function renderClaudeCodeSubagent(input) {
|
|
2440
|
+
const frontmatter = [
|
|
2441
|
+
"---",
|
|
2442
|
+
`name: ${yamlScalar(input.name)}`,
|
|
2443
|
+
`description: ${yamlScalar(input.description)}`,
|
|
2444
|
+
`model: ${input.model}`,
|
|
2445
|
+
...input.tools !== void 0 ? [`tools: ${yamlScalar(input.tools)}`] : [],
|
|
2446
|
+
"---"
|
|
2447
|
+
].join("\n");
|
|
2448
|
+
const body = input.instructions.trimEnd();
|
|
2449
|
+
return `${frontmatter}
|
|
2450
|
+
|
|
2451
|
+
${body}
|
|
2452
|
+
`;
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
// src/harness/adapters/claude-code.ts
|
|
2456
|
+
var CLAUDE_CODE_CAPABILITIES = CLAUDE_CODE_PROMPT_DIALECT.capabilities.capabilities;
|
|
2457
|
+
var CLAUDE_CODE_SUBAGENT_DEFAULT_MODELS = {
|
|
2458
|
+
explorer: "haiku",
|
|
2459
|
+
librarian: "sonnet",
|
|
2460
|
+
oracle: "opus",
|
|
2461
|
+
designer: "sonnet",
|
|
2462
|
+
quick: "haiku",
|
|
2463
|
+
deep: "sonnet"
|
|
2464
|
+
};
|
|
2465
|
+
var READ_ONLY_ROLE_TOOLS = {
|
|
2466
|
+
explorer: "Read, Grep, Glob",
|
|
2467
|
+
librarian: "Read, Grep, Glob, WebSearch, WebFetch",
|
|
2468
|
+
oracle: "Read, Grep, Glob"
|
|
2469
|
+
};
|
|
2470
|
+
var WRITE_CAPABLE_ROLE_TOOLS = "Read, Edit, Write, Bash, Grep, Glob";
|
|
2471
|
+
function isClaudeCodeSubagentName(name) {
|
|
2472
|
+
return name in CLAUDE_CODE_SUBAGENT_DEFAULT_MODELS;
|
|
2473
|
+
}
|
|
2474
|
+
function getClaudeCodeAgentModel(role, config) {
|
|
2475
|
+
if (!isClaudeCodeSubagentName(role.name)) return "inherit";
|
|
2476
|
+
const override = getPrimaryModelId(config?.agents?.[role.name]?.model);
|
|
2477
|
+
if (override && isClaudeCodeModel(override)) return override;
|
|
2478
|
+
return CLAUDE_CODE_SUBAGENT_DEFAULT_MODELS[role.name];
|
|
2479
|
+
}
|
|
2480
|
+
function toolsForRole(role) {
|
|
2481
|
+
if (role.canMutateWorkspace) return WRITE_CAPABLE_ROLE_TOOLS;
|
|
2482
|
+
return READ_ONLY_ROLE_TOOLS[role.name] ?? "Read, Grep, Glob";
|
|
2483
|
+
}
|
|
2484
|
+
function claudeCodePromptSections(roleName) {
|
|
2485
|
+
switch (roleName) {
|
|
2486
|
+
case "orchestrator":
|
|
2487
|
+
return createOrchestratorPromptSections();
|
|
2488
|
+
case "explorer":
|
|
2489
|
+
case "librarian":
|
|
2490
|
+
case "oracle":
|
|
2491
|
+
return createReadOnlySpecialistPromptSections(roleName);
|
|
2492
|
+
case "designer":
|
|
2493
|
+
case "quick":
|
|
2494
|
+
case "deep":
|
|
2495
|
+
return createWriteCapableSpecialistPromptSections(roleName);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
function claudeCodeModelFamilyPromptSection(roleName, model) {
|
|
2499
|
+
const section = createModelFamilySection(roleName, model);
|
|
2500
|
+
return section ? renderPromptSection(section, CLAUDE_CODE_PROMPT_DIALECT) : void 0;
|
|
2501
|
+
}
|
|
2502
|
+
function claudeCodeStepBudgetPromptSection(steps) {
|
|
2503
|
+
const section = createStepBudgetSection(steps);
|
|
2504
|
+
return section ? renderPromptSection(section, CLAUDE_CODE_PROMPT_DIALECT) : void 0;
|
|
2505
|
+
}
|
|
2506
|
+
function renderClaudeCodeRolePrompt(roleName, config, model) {
|
|
2507
|
+
const promptOverrides = loadAgentPrompt(roleName, config?.preset);
|
|
2508
|
+
const override = getAgentOverride(config, roleName);
|
|
2509
|
+
const basePrompt = renderRolePrompt(
|
|
2510
|
+
claudeCodePromptSections(roleName),
|
|
2511
|
+
CLAUDE_CODE_PROMPT_DIALECT
|
|
2512
|
+
);
|
|
2513
|
+
const prompt = composeAgentPrompt({
|
|
2514
|
+
basePrompt,
|
|
2515
|
+
customPrompt: promptOverrides.prompt,
|
|
2516
|
+
customAppendPrompt: appendPromptSections(
|
|
2517
|
+
claudeCodeModelFamilyPromptSection(roleName, model),
|
|
2518
|
+
promptOverrides.appendPrompt
|
|
2519
|
+
)
|
|
2520
|
+
});
|
|
2521
|
+
return appendPromptSections(
|
|
2522
|
+
prompt,
|
|
2523
|
+
claudeCodeStepBudgetPromptSection(override?.steps)
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2526
|
+
function claudeCodeRoleInstructions(role) {
|
|
2527
|
+
return [
|
|
2528
|
+
"<role-operational-contract>",
|
|
2529
|
+
`- Role: ${role.name}`,
|
|
2530
|
+
`- Mode: ${role.mode}`,
|
|
2531
|
+
`- Scope: ${role.scope}`,
|
|
2532
|
+
`- Responsibility: ${role.responsibility}`,
|
|
2533
|
+
"- Use AskUserQuestion for local blocking decisions.",
|
|
2534
|
+
`- ${role.name} runs as an auto-discovered Claude Code plugin subagent invoked via Task(subagent_type: ${claudeCodeSubagentType(role.name)}); plugin subagents are namespaced with the plugin name. The orchestrator is the main Claude Code session.`,
|
|
2535
|
+
"- Role permissions are enforced by this subagent's frontmatter `tools` allowlist; read-only roles cannot mutate the workspace.",
|
|
2536
|
+
...role.toolGovernance.map((rule) => `- ${rule}`),
|
|
2537
|
+
...role.verification.map((rule) => `- ${rule}`),
|
|
2538
|
+
"</role-operational-contract>"
|
|
2539
|
+
].join("\n");
|
|
2540
|
+
}
|
|
2541
|
+
function roleInstructions2(role, config) {
|
|
2542
|
+
const model = getClaudeCodeAgentModel(role, config);
|
|
2543
|
+
return [
|
|
2544
|
+
renderClaudeCodeRolePrompt(role.name, config, model),
|
|
2545
|
+
claudeCodeRoleInstructions(role),
|
|
2546
|
+
renderMemoryGovernanceInstructions(role, CLAUDE_CODE_PROMPT_DIALECT)
|
|
2547
|
+
].join("\n\n");
|
|
2548
|
+
}
|
|
2549
|
+
function renderClaudeCodeRootInstructions(config) {
|
|
2550
|
+
const rootOverride = getAgentOverride(config, "orchestrator");
|
|
2551
|
+
const rootPrompt = renderClaudeCodeRolePrompt(
|
|
2552
|
+
"orchestrator",
|
|
2553
|
+
config,
|
|
2554
|
+
rootOverride?.model ?? DEFAULT_MODELS.orchestrator
|
|
2555
|
+
);
|
|
2556
|
+
const specialists = getAgentPackContract().roles.filter((role) => role.name !== "orchestrator").map((role) => claudeCodeSubagentType(role.name)).join(", ");
|
|
2557
|
+
return [
|
|
2558
|
+
rootPrompt,
|
|
2559
|
+
"<claude-code-runtime>",
|
|
2560
|
+
"- You ARE the Claude Code main-thread agent: the delegate-first root coordinator. This is your system prompt (activated via the plugin settings.json `agent` key), so orchestrator-only and root-owned rules apply to you directly.",
|
|
2561
|
+
'- As your FIRST action on a new session, when thoth-mem tools are installed and session/project identity is known, call mem_session(action="start") as step 0 before any other thoth-mem call, then save the real user prompt with mem_save(kind="prompt") before later delegation.',
|
|
2562
|
+
"- thoth-mem tools are provided by this plugin's bundled MCP server and are exposed under a plugin namespace; call the available namespaced tool whose name ends in mem_session, mem_recall, mem_context, mem_save, mem_get, or mem_project (do not assume a bare, unnamespaced tool name).",
|
|
2563
|
+
"- If thoth-mem tools or identity values are unavailable, disclose that memory bootstrap could not run and continue without claiming memory was saved.",
|
|
2564
|
+
`- Delegate via the Task tool with \`subagent_type\` set to a plugin-namespaced specialist: ${specialists}. Bare role names (e.g. "explorer") are NOT valid in this harness \u2014 always use the ${CLAUDE_CODE_SUBAGENT_NAMESPACE}: prefix.`,
|
|
2565
|
+
"- Parallel delegation is supported: issue multiple Task calls in one turn for independent work.",
|
|
2566
|
+
'- Before delegating after meaningful context changes, refresh the handoff body with root-owned mem_session(action="summary") or mem_save(kind="session_summary") when available.',
|
|
2567
|
+
"- Use AskUserQuestion for blocking user decisions; do not ask those questions in plain prose.",
|
|
2568
|
+
"- Track progress with TodoWrite; subagents do not own progress checkboxes or root-only memory.",
|
|
2569
|
+
"- Role permissions are enforced at runtime by each subagent's frontmatter `tools` allowlist.",
|
|
2570
|
+
"</claude-code-runtime>"
|
|
2571
|
+
].join("\n");
|
|
2572
|
+
}
|
|
2573
|
+
function claudeCodeMcpServers() {
|
|
2574
|
+
const [exaCommand = "", ...exaArgs] = exa.command;
|
|
2575
|
+
const [thothCommand = "", ...thothArgs] = DEFAULT_THOTH_COMMAND;
|
|
2576
|
+
return {
|
|
2577
|
+
exa: {
|
|
2578
|
+
command: exaCommand,
|
|
2579
|
+
...exaArgs.length > 0 ? { args: exaArgs } : {},
|
|
2580
|
+
...exa.environment && Object.keys(exa.environment).length > 0 ? { env: exa.environment } : {}
|
|
2581
|
+
},
|
|
2582
|
+
context7: { type: "http", url: CONTEXT7_MCP_URL },
|
|
2583
|
+
grep_app: { type: "http", url: GREP_APP_MCP_URL },
|
|
2584
|
+
thoth_mem: {
|
|
2585
|
+
command: thothCommand,
|
|
2586
|
+
...thothArgs.length > 0 ? { args: thothArgs } : {}
|
|
2587
|
+
}
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
function stableJson5(value) {
|
|
2591
|
+
return `${JSON.stringify(value, null, 2)}
|
|
2592
|
+
`;
|
|
2593
|
+
}
|
|
2594
|
+
function readRootPackageVersion2(context) {
|
|
2595
|
+
const packageJsonPath = findRootPackageJsonPath([
|
|
2596
|
+
...hasPackageRoot(context) ? [context.packageRoot] : [],
|
|
2597
|
+
context.projectRoot,
|
|
2598
|
+
process.cwd(),
|
|
2599
|
+
fileURLToPath3(new URL(".", import.meta.url))
|
|
2600
|
+
]);
|
|
2601
|
+
return readPackageJsonVersion(packageJsonPath);
|
|
2602
|
+
}
|
|
2603
|
+
function hasConfig(context) {
|
|
2604
|
+
return "config" in context;
|
|
2605
|
+
}
|
|
2606
|
+
function hasPackageRoot(context) {
|
|
2607
|
+
return "packageRoot" in context && typeof context.packageRoot === "string" && context.packageRoot.length > 0;
|
|
2608
|
+
}
|
|
2609
|
+
function createPluginManifest(context) {
|
|
2610
|
+
return {
|
|
2611
|
+
name: "thoth-agents",
|
|
2612
|
+
version: readRootPackageVersion2(context),
|
|
2613
|
+
description: "Delegate-first agent pack with seven roles, thoth-mem persistence, and bundled SDD skills, packaged for Claude Code.",
|
|
2614
|
+
author: { name: "thoth-agents" }
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
function renderSubagentArtifacts(config) {
|
|
2618
|
+
const artifacts = [];
|
|
2619
|
+
for (const role of getAgentPackContract().roles.filter(
|
|
2620
|
+
(candidate) => candidate.name !== "orchestrator"
|
|
2621
|
+
)) {
|
|
2622
|
+
const content = renderClaudeCodeSubagent({
|
|
2623
|
+
name: role.name,
|
|
2624
|
+
description: role.responsibility,
|
|
2625
|
+
tools: toolsForRole(role),
|
|
2626
|
+
model: getClaudeCodeAgentModel(role, config),
|
|
2627
|
+
instructions: roleInstructions2(role, config)
|
|
2628
|
+
});
|
|
2629
|
+
artifacts.push({
|
|
2630
|
+
harness: "claude",
|
|
2631
|
+
kind: "agent-config",
|
|
2632
|
+
path: `agents/${role.name}.md`,
|
|
2633
|
+
description: `Claude Code subagent definition for ${role.name}.`,
|
|
2634
|
+
content
|
|
2635
|
+
});
|
|
2636
|
+
}
|
|
2637
|
+
return artifacts;
|
|
2638
|
+
}
|
|
2639
|
+
function renderOrchestratorArtifact(config) {
|
|
2640
|
+
const orchestrator = getAgentPackContract().roles.find(
|
|
2641
|
+
(role) => role.name === "orchestrator"
|
|
2642
|
+
);
|
|
2643
|
+
const content = renderClaudeCodeSubagent({
|
|
2644
|
+
name: "orchestrator",
|
|
2645
|
+
description: orchestrator?.responsibility ?? "Delegate-first root coordinator for SDD workflow and specialist dispatch.",
|
|
2646
|
+
model: "inherit",
|
|
2647
|
+
instructions: renderClaudeCodeRootInstructions(config)
|
|
2648
|
+
});
|
|
2649
|
+
return {
|
|
2650
|
+
harness: "claude",
|
|
2651
|
+
kind: "agent-config",
|
|
2652
|
+
path: "agents/orchestrator.md",
|
|
2653
|
+
description: "Claude Code orchestrator agent, activated as the main thread via settings.json.",
|
|
2654
|
+
content
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
var claudeCodeAdapter = {
|
|
2658
|
+
id: "claude",
|
|
2659
|
+
displayName: "Claude Code",
|
|
2660
|
+
capabilities: CLAUDE_CODE_CAPABILITIES,
|
|
2661
|
+
render(context) {
|
|
2662
|
+
const config = hasConfig(context) ? context.config : void 0;
|
|
2663
|
+
const componentArtifacts = [
|
|
2664
|
+
...renderSubagentArtifacts(config),
|
|
2665
|
+
renderOrchestratorArtifact(config),
|
|
2666
|
+
{
|
|
2667
|
+
harness: "claude",
|
|
2668
|
+
kind: "mcp-config",
|
|
2669
|
+
path: ".mcp.json",
|
|
2670
|
+
description: "Claude Code plugin-bundled MCP server definitions.",
|
|
2671
|
+
content: stableJson5({ mcpServers: claudeCodeMcpServers() })
|
|
2672
|
+
},
|
|
2673
|
+
{
|
|
2674
|
+
harness: "claude",
|
|
2675
|
+
kind: "harness-config",
|
|
2676
|
+
path: "settings.json",
|
|
2677
|
+
description: "Activates the orchestrator agent as the Claude Code main thread.",
|
|
2678
|
+
content: stableJson5({ agent: "orchestrator" })
|
|
2679
|
+
}
|
|
2680
|
+
];
|
|
2681
|
+
const skillLayout = renderClaudeCodeSkillLayout({
|
|
2682
|
+
projectRoot: context.projectRoot,
|
|
2683
|
+
...hasPackageRoot(context) ? { packageRoot: context.packageRoot } : {},
|
|
2684
|
+
skills: getSkillRegistry()
|
|
2685
|
+
});
|
|
2686
|
+
componentArtifacts.push(...skillLayout.artifacts);
|
|
2687
|
+
const pluginPackage = renderClaudeCodePluginPackage({
|
|
2688
|
+
manifest: createPluginManifest(context),
|
|
2689
|
+
componentArtifacts
|
|
2690
|
+
});
|
|
2691
|
+
return {
|
|
2692
|
+
harness: "claude",
|
|
2693
|
+
artifacts: pluginPackage.artifacts,
|
|
2694
|
+
diagnostics: [...skillLayout.diagnostics, ...pluginPackage.diagnostics]
|
|
2695
|
+
};
|
|
2696
|
+
}
|
|
2697
|
+
};
|
|
2698
|
+
|
|
2699
|
+
// src/cli/claude-code-install.ts
|
|
2700
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
2701
|
+
import { join as join8 } from "path";
|
|
2702
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
2703
|
+
|
|
2704
|
+
// src/cli/claude-code-paths.ts
|
|
2705
|
+
import { homedir as homedir2 } from "os";
|
|
2706
|
+
import { join as join7 } from "path";
|
|
2707
|
+
var CLAUDE_CODE_ROLE_NAMES = [
|
|
2708
|
+
"explorer",
|
|
2709
|
+
"librarian",
|
|
2710
|
+
"oracle",
|
|
2711
|
+
"designer",
|
|
2712
|
+
"quick",
|
|
2713
|
+
"deep"
|
|
2714
|
+
];
|
|
2715
|
+
function resolveClaudeCodeTargets(options) {
|
|
2716
|
+
const home = options.homeDir ?? homedir2();
|
|
2717
|
+
const pluginRoot = options.scope === "project" ? join7(options.projectRoot, ".claude", "skills", "thoth-agents") : join7(home, ".claude", "skills", "thoth-agents");
|
|
2718
|
+
return {
|
|
2719
|
+
scope: options.scope,
|
|
2720
|
+
pluginRoot,
|
|
2721
|
+
pluginManifestPath: join7(pluginRoot, ".claude-plugin", "plugin.json"),
|
|
2722
|
+
agentPaths: CLAUDE_CODE_ROLE_NAMES.map((role) => ({
|
|
2723
|
+
role,
|
|
2724
|
+
path: join7(pluginRoot, "agents", `${role}.md`)
|
|
2725
|
+
})),
|
|
2726
|
+
mcpPath: join7(pluginRoot, ".mcp.json"),
|
|
2727
|
+
hooksPath: join7(pluginRoot, "hooks", "hooks.json"),
|
|
2728
|
+
skillsDir: join7(pluginRoot, "skills"),
|
|
2729
|
+
managedModelsPath: join7(pluginRoot, ".thoth-agents-managed-models.json")
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
// src/cli/claude-code-install.ts
|
|
2734
|
+
var CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION = 1;
|
|
2735
|
+
var isClaudeCodeModelAlias = isClaudeCodeModel;
|
|
2736
|
+
function parseSubagentModel(content) {
|
|
2737
|
+
return /^model:\s*(\S+)\s*$/m.exec(content)?.[1];
|
|
2738
|
+
}
|
|
2739
|
+
function replaceSubagentModel(content, model) {
|
|
2740
|
+
if (/^model:\s*\S+\s*$/m.test(content)) {
|
|
2741
|
+
return content.replace(/^model:\s*\S+\s*$/m, `model: ${model}`);
|
|
2742
|
+
}
|
|
2743
|
+
return content;
|
|
2744
|
+
}
|
|
2745
|
+
function emptyManagedModelState3() {
|
|
2746
|
+
return emptyManagedModelState(CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION);
|
|
2747
|
+
}
|
|
2748
|
+
function parseManagedModelStateJson2(text) {
|
|
2749
|
+
return parseManagedModelStateJson(
|
|
2750
|
+
text,
|
|
2751
|
+
CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION
|
|
2752
|
+
);
|
|
2753
|
+
}
|
|
2754
|
+
function readManagedModelState3(path4) {
|
|
2755
|
+
return readManagedModelState(
|
|
2756
|
+
path4,
|
|
2757
|
+
CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION
|
|
2758
|
+
);
|
|
2759
|
+
}
|
|
2760
|
+
function resolvePackageRoot2(packageRoot) {
|
|
2761
|
+
if (packageRoot) return packageRoot;
|
|
2762
|
+
return findPackageRoot(fileURLToPath4(new URL(".", import.meta.url))) ?? void 0;
|
|
2763
|
+
}
|
|
2764
|
+
function targetKindForArtifact(artifact) {
|
|
2765
|
+
const path4 = artifact.path.replaceAll("\\", "/");
|
|
2766
|
+
if (path4 === ".claude-plugin/plugin.json") return "plugin-manifest";
|
|
2767
|
+
if (path4.startsWith("agents/")) return "subagent";
|
|
2768
|
+
if (path4 === ".mcp.json") return "mcp-config";
|
|
2769
|
+
if (path4.startsWith("hooks/")) return "hook";
|
|
2770
|
+
if (path4.startsWith("skills/")) return "skill";
|
|
2771
|
+
return "plugin-asset";
|
|
2772
|
+
}
|
|
2773
|
+
function roleForArtifact(artifact) {
|
|
2774
|
+
const match = /^agents\/([^/]+)\.md$/.exec(
|
|
2775
|
+
artifact.path.replaceAll("\\", "/")
|
|
2776
|
+
);
|
|
2777
|
+
const name = match?.[1];
|
|
2778
|
+
return name && CLAUDE_CODE_ROLE_NAMES.includes(name) ? name : void 0;
|
|
2779
|
+
}
|
|
2780
|
+
function applyConfiguredModel(content, role, state, nextState, reset) {
|
|
2781
|
+
const renderedModel = parseSubagentModel(content);
|
|
2782
|
+
if (!role || !renderedModel) return content;
|
|
2783
|
+
nextState.models[role] = renderedModel;
|
|
2784
|
+
const configured = reset ? void 0 : state.configuredModels?.[role];
|
|
2785
|
+
if (configured === void 0) return content;
|
|
2786
|
+
nextState.configuredModels ??= {};
|
|
2787
|
+
nextState.configuredModels[role] = configured;
|
|
2788
|
+
return replaceSubagentModel(content, configured);
|
|
2789
|
+
}
|
|
2790
|
+
function buildClaudeCodeSetupPlan(config) {
|
|
2791
|
+
const targets = resolveClaudeCodeTargets({
|
|
2792
|
+
scope: config.scope,
|
|
2793
|
+
projectRoot: config.projectRoot,
|
|
2794
|
+
homeDir: config.homeDir
|
|
2795
|
+
});
|
|
2796
|
+
const packageRoot = resolvePackageRoot2(config.packageRoot);
|
|
2797
|
+
const render = claudeCodeAdapter.render({
|
|
2798
|
+
projectRoot: config.projectRoot,
|
|
2799
|
+
...packageRoot ? { packageRoot } : {}
|
|
2800
|
+
});
|
|
2801
|
+
const state = readManagedModelState3(targets.managedModelsPath);
|
|
2802
|
+
const nextState = emptyManagedModelState3();
|
|
2803
|
+
const items = render.artifacts.map((artifact) => {
|
|
2804
|
+
const role = roleForArtifact(artifact);
|
|
2805
|
+
const rendered = String(artifact.content ?? "");
|
|
2806
|
+
const content = role !== void 0 ? applyConfiguredModel(rendered, role, state, nextState, config.reset) : rendered;
|
|
2807
|
+
const targetPath = join8(targets.pluginRoot, artifact.path);
|
|
2808
|
+
return {
|
|
2809
|
+
kind: targetKindForArtifact(artifact),
|
|
2810
|
+
action: "write-plugin-file",
|
|
2811
|
+
targetPath,
|
|
2812
|
+
description: `Materialize Claude Code plugin asset ${artifact.path}.`,
|
|
2813
|
+
requiresBackup: existsSync6(targetPath),
|
|
2814
|
+
content,
|
|
2815
|
+
...role ? { role } : {}
|
|
2816
|
+
};
|
|
2817
|
+
});
|
|
2818
|
+
items.push({
|
|
2819
|
+
kind: "managed-model-state",
|
|
2820
|
+
action: "write-managed-model-state",
|
|
2821
|
+
targetPath: targets.managedModelsPath,
|
|
2822
|
+
description: "Record thoth-agents-managed Claude Code subagent model ownership state.",
|
|
2823
|
+
requiresBackup: existsSync6(targets.managedModelsPath),
|
|
2824
|
+
content: stableJson3(nextState)
|
|
2825
|
+
});
|
|
2826
|
+
return {
|
|
2827
|
+
dryRun: config.dryRun === true,
|
|
2828
|
+
reset: config.reset,
|
|
2829
|
+
items,
|
|
2830
|
+
pluginRoot: targets.pluginRoot,
|
|
2831
|
+
diagnostics: [
|
|
2832
|
+
`Installed as a skills-directory plugin at ${targets.pluginRoot}; it auto-loads as thoth-agents@skills-dir on the next Claude Code session.`,
|
|
2833
|
+
"Restart Claude Code or run /reload-plugins to activate it; run /plugin (Installed tab) to confirm thoth-agents@skills-dir is loaded.",
|
|
2834
|
+
"The plugin settings.json activates the orchestrator agent as the main thread, so the session starts in delegate-first mode and bootstraps thoth-mem on its first turn."
|
|
2835
|
+
],
|
|
2836
|
+
disclaimers: [
|
|
2837
|
+
"The orchestrator agent is the Claude Code main thread (plugin settings.json `agent` key); while enabled it replaces the default system prompt for every session in scope.",
|
|
2838
|
+
"Role permissions are enforced by each specialist subagent frontmatter `tools` allowlist; the orchestrator inherits all tools.",
|
|
2839
|
+
"Subagent models accept only sonnet, opus, haiku, or inherit.",
|
|
2840
|
+
"User-scope skills-directory plugins load hooks and MCP servers without extra approval; project-scope requires accepting the workspace trust dialog."
|
|
2841
|
+
]
|
|
2842
|
+
};
|
|
2843
|
+
}
|
|
2844
|
+
function applyClaudeCodeSetup(plan) {
|
|
2845
|
+
const changed = [];
|
|
2846
|
+
const diagnostics = uniqueMessages([
|
|
2847
|
+
...plan.diagnostics,
|
|
2848
|
+
...plan.disclaimers
|
|
2849
|
+
]);
|
|
2850
|
+
if (plan.dryRun) return { success: true, changed, diagnostics };
|
|
2851
|
+
try {
|
|
2852
|
+
for (const item of plan.items) {
|
|
2853
|
+
if (writeTextWithBackup(item.targetPath, item.content)) {
|
|
2854
|
+
changed.push(item.targetPath);
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
return { success: true, changed, diagnostics };
|
|
2858
|
+
} catch (error) {
|
|
2859
|
+
return {
|
|
2860
|
+
success: false,
|
|
2861
|
+
changed,
|
|
2862
|
+
diagnostics,
|
|
2863
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
function applyClaudeCodeManagedModelOverrides(config, overrides) {
|
|
2868
|
+
if (config.dryRun) {
|
|
2869
|
+
return {
|
|
2870
|
+
success: true,
|
|
2871
|
+
changed: [],
|
|
2872
|
+
diagnostics: [
|
|
2873
|
+
"Dry-run Claude Code model override apply requested; no files were written."
|
|
2874
|
+
]
|
|
2875
|
+
};
|
|
2876
|
+
}
|
|
2877
|
+
const plan = buildClaudeCodeSetupPlan({
|
|
2878
|
+
...config,
|
|
2879
|
+
dryRun: true,
|
|
2880
|
+
reset: false
|
|
2881
|
+
});
|
|
2882
|
+
const stateItem = plan.items.find(
|
|
2883
|
+
(item) => item.action === "write-managed-model-state"
|
|
2884
|
+
);
|
|
2885
|
+
const statePath = stateItem?.targetPath;
|
|
2886
|
+
if (!statePath) {
|
|
2887
|
+
return {
|
|
2888
|
+
success: false,
|
|
2889
|
+
changed: [],
|
|
2890
|
+
diagnostics: plan.diagnostics,
|
|
2891
|
+
error: "Claude Code managed model state target was not found."
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
const changed = [];
|
|
2895
|
+
const diagnostics = uniqueMessages([
|
|
2896
|
+
...plan.diagnostics,
|
|
2897
|
+
...plan.disclaimers
|
|
2898
|
+
]);
|
|
2899
|
+
const state = parseManagedModelStateJson2(stateItem?.content);
|
|
2900
|
+
const nextState = {
|
|
2901
|
+
version: CLAUDE_CODE_MANAGED_MODEL_STATE_VERSION,
|
|
2902
|
+
models: { ...state.models },
|
|
2903
|
+
...state.configuredModels ? { configuredModels: { ...state.configuredModels } } : {}
|
|
2904
|
+
};
|
|
2905
|
+
try {
|
|
2906
|
+
for (const override of overrides) {
|
|
2907
|
+
if (!isClaudeCodeModelAlias(override.model)) {
|
|
2908
|
+
throw new Error(
|
|
2909
|
+
`Unsupported Claude Code model "${override.model}" for ${override.role}; use sonnet, opus, haiku, or inherit.`
|
|
2910
|
+
);
|
|
2911
|
+
}
|
|
2912
|
+
const roleItem = plan.items.find(
|
|
2913
|
+
(item) => item.kind === "subagent" && item.role === override.role
|
|
2914
|
+
);
|
|
2915
|
+
if (!roleItem) {
|
|
2916
|
+
throw new Error(`Missing Claude Code subagent for ${override.role}.`);
|
|
2917
|
+
}
|
|
2918
|
+
const before = existsSync6(roleItem.targetPath) ? readFileSync7(roleItem.targetPath, "utf8") : roleItem.content;
|
|
2919
|
+
const updated = replaceSubagentModel(before, override.model);
|
|
2920
|
+
if (writeTextWithBackup(roleItem.targetPath, updated)) {
|
|
2921
|
+
changed.push(roleItem.targetPath);
|
|
2922
|
+
}
|
|
2923
|
+
nextState.configuredModels ??= {};
|
|
2924
|
+
nextState.configuredModels[override.role] = override.model;
|
|
2925
|
+
}
|
|
2926
|
+
if (writeTextWithBackup(statePath, stableJson3(nextState))) {
|
|
2927
|
+
changed.push(statePath);
|
|
2928
|
+
}
|
|
2929
|
+
return { success: true, changed, diagnostics };
|
|
2930
|
+
} catch (error) {
|
|
2931
|
+
return {
|
|
2932
|
+
success: false,
|
|
2933
|
+
changed,
|
|
2934
|
+
diagnostics,
|
|
2935
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2936
|
+
};
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
function formatClaudeCodeSetupPlan(plan) {
|
|
2940
|
+
const lines = plan.items.map(
|
|
2941
|
+
(item) => `- ${item.action}: ${item.targetPath} (${item.description})`
|
|
2942
|
+
);
|
|
2943
|
+
return ["Claude Code setup plan:", ...lines].join("\n");
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
// src/cli/operations/claude-code.ts
|
|
2947
|
+
var CLAUDE_CODE_DISPLAY_NAME = "Claude Code";
|
|
2948
|
+
var claudeCodePlanSources = /* @__PURE__ */ new WeakMap();
|
|
2949
|
+
var claudeCodeModelSources = /* @__PURE__ */ new WeakMap();
|
|
2950
|
+
var claudeCodeActions = [
|
|
2951
|
+
{
|
|
2952
|
+
id: "claude-code-status",
|
|
2953
|
+
kind: "status",
|
|
2954
|
+
label: "Status",
|
|
2955
|
+
description: "Inspect managed Claude Code plugin state",
|
|
2956
|
+
dryRun: false,
|
|
2957
|
+
requiresConfirmation: false,
|
|
2958
|
+
supported: true
|
|
2959
|
+
},
|
|
2960
|
+
{
|
|
2961
|
+
id: "claude-code-list",
|
|
2962
|
+
kind: "list",
|
|
2963
|
+
label: "List",
|
|
2964
|
+
description: "List managed Claude Code surfaces and actions",
|
|
2965
|
+
dryRun: false,
|
|
2966
|
+
requiresConfirmation: false,
|
|
2967
|
+
supported: true
|
|
2968
|
+
},
|
|
2969
|
+
{
|
|
2970
|
+
id: "claude-code-install",
|
|
2971
|
+
kind: "install",
|
|
2972
|
+
label: "Install",
|
|
2973
|
+
description: "Preview Claude Code plugin package install",
|
|
2974
|
+
dryRun: true,
|
|
2975
|
+
requiresConfirmation: true,
|
|
2976
|
+
supported: true
|
|
2977
|
+
},
|
|
2978
|
+
{
|
|
2979
|
+
id: "claude-code-update",
|
|
2980
|
+
kind: "update",
|
|
2981
|
+
label: "Update",
|
|
2982
|
+
description: "Preview Claude Code managed plugin refresh",
|
|
2983
|
+
dryRun: true,
|
|
2984
|
+
requiresConfirmation: true,
|
|
2985
|
+
supported: true
|
|
2986
|
+
},
|
|
2987
|
+
{
|
|
2988
|
+
id: "claude-code-sync",
|
|
2989
|
+
kind: "sync",
|
|
2990
|
+
label: "Sync",
|
|
2991
|
+
description: "Preview Claude Code managed plugin sync",
|
|
2992
|
+
dryRun: true,
|
|
2993
|
+
requiresConfirmation: true,
|
|
2994
|
+
supported: true
|
|
2995
|
+
},
|
|
2996
|
+
{
|
|
2997
|
+
id: "claude-code-model-config",
|
|
2998
|
+
kind: "model-config",
|
|
2999
|
+
label: "Model",
|
|
3000
|
+
description: "Preview supported Claude Code subagent model line changes",
|
|
3001
|
+
dryRun: true,
|
|
3002
|
+
requiresConfirmation: true,
|
|
3003
|
+
supported: true
|
|
3004
|
+
}
|
|
3005
|
+
];
|
|
3006
|
+
var claudeCodeOperationAdapter = {
|
|
3007
|
+
id: "claude",
|
|
3008
|
+
displayName: CLAUDE_CODE_DISPLAY_NAME,
|
|
3009
|
+
available: true,
|
|
3010
|
+
description: "Claude Code plugin package and managed subagent surfaces.",
|
|
3011
|
+
actions: claudeCodeActions
|
|
3012
|
+
};
|
|
3013
|
+
function claudeCodeConfig(context = { cwd: process.cwd() }, dryRun) {
|
|
3014
|
+
return {
|
|
3015
|
+
dryRun,
|
|
3016
|
+
reset: false,
|
|
3017
|
+
// Match the installer (install.ts uses 'user') so post-install status,
|
|
3018
|
+
// update, sync, and model commands resolve the same plugin root.
|
|
3019
|
+
scope: context.scope ?? "user",
|
|
3020
|
+
projectRoot: context.cwd,
|
|
3021
|
+
homeDir: context.homeDir,
|
|
3022
|
+
packageRoot: context.packageRoot
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
3025
|
+
function claudeCodeDisclaimers() {
|
|
3026
|
+
return [
|
|
3027
|
+
{
|
|
3028
|
+
message: "Role permissions are enforced by subagent frontmatter tools; the orchestrator is the main session, injected via the SessionStart hook.",
|
|
3029
|
+
code: "claude-code-first-class"
|
|
3030
|
+
},
|
|
3031
|
+
{
|
|
3032
|
+
message: "Subagent models accept only sonnet, opus, haiku, or inherit.",
|
|
3033
|
+
code: "claude-code-model-aliases"
|
|
3034
|
+
}
|
|
3035
|
+
];
|
|
3036
|
+
}
|
|
3037
|
+
function warning(message, code) {
|
|
3038
|
+
return { severity: "important", message, code };
|
|
3039
|
+
}
|
|
3040
|
+
function targetForItem(item, state, observed) {
|
|
3041
|
+
return {
|
|
3042
|
+
kind: item.kind === "managed-model-state" ? "memory-state" : "generated-artifact",
|
|
3043
|
+
path: item.targetPath,
|
|
3044
|
+
label: item.role ? `Claude Code ${item.role} subagent` : item.kind.replaceAll("-", " "),
|
|
3045
|
+
state,
|
|
3046
|
+
expected: item.action,
|
|
3047
|
+
observed,
|
|
3048
|
+
description: item.description
|
|
3049
|
+
};
|
|
3050
|
+
}
|
|
3051
|
+
function surfaceForItem(item) {
|
|
3052
|
+
return {
|
|
3053
|
+
id: `${item.kind}:${item.role ?? basename2(item.targetPath)}`,
|
|
3054
|
+
label: item.role ? `Claude Code ${item.role} subagent` : item.kind.replaceAll("-", " "),
|
|
3055
|
+
path: item.targetPath,
|
|
3056
|
+
description: item.description
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
function backupForItem(item) {
|
|
3060
|
+
return {
|
|
3061
|
+
required: item.requiresBackup,
|
|
3062
|
+
strategy: item.requiresBackup ? "managed-backup-file" : "none",
|
|
3063
|
+
destinations: item.requiresBackup ? [{ path: `${item.targetPath}.bak`, label: "managed backup" }] : []
|
|
3064
|
+
};
|
|
3065
|
+
}
|
|
3066
|
+
function manifestState(item) {
|
|
3067
|
+
if (!existsSync7(item.targetPath))
|
|
3068
|
+
return { state: "missing", observed: "absent" };
|
|
3069
|
+
const observed = readFileSync8(item.targetPath, "utf8");
|
|
3070
|
+
if (observed === item.content)
|
|
3071
|
+
return { state: "installed", observed: "current" };
|
|
3072
|
+
try {
|
|
3073
|
+
const expectedVersion = JSON.parse(item.content).version;
|
|
3074
|
+
const observedVersion = JSON.parse(observed).version;
|
|
3075
|
+
if (typeof expectedVersion === "string" && typeof observedVersion === "string" && expectedVersion !== observedVersion) {
|
|
3076
|
+
return {
|
|
3077
|
+
state: "outdated",
|
|
3078
|
+
observed: `version ${observedVersion}; expected ${expectedVersion}`
|
|
3079
|
+
};
|
|
3080
|
+
}
|
|
3081
|
+
} catch {
|
|
3082
|
+
return { state: "unknown", observed: "unparseable plugin manifest" };
|
|
3083
|
+
}
|
|
3084
|
+
return { state: "drift", observed: "content differs" };
|
|
3085
|
+
}
|
|
3086
|
+
function contentState(item) {
|
|
3087
|
+
if (!existsSync7(item.targetPath))
|
|
3088
|
+
return { state: "missing", observed: "absent" };
|
|
3089
|
+
return readFileSync8(item.targetPath, "utf8") === item.content ? { state: "installed", observed: "current" } : { state: "drift", observed: "content differs" };
|
|
3090
|
+
}
|
|
3091
|
+
function classifyItem(item) {
|
|
3092
|
+
if (item.kind === "plugin-manifest") return manifestState(item);
|
|
3093
|
+
return contentState(item);
|
|
3094
|
+
}
|
|
3095
|
+
function aggregateState(states) {
|
|
3096
|
+
if (states.includes("unknown")) return "unknown";
|
|
3097
|
+
if (states.includes("drift")) return "drift";
|
|
3098
|
+
if (states.includes("outdated")) return "outdated";
|
|
3099
|
+
if (states.includes("missing")) return "missing";
|
|
3100
|
+
return "installed";
|
|
3101
|
+
}
|
|
3102
|
+
function statusSummary(state) {
|
|
3103
|
+
switch (state) {
|
|
3104
|
+
case "installed":
|
|
3105
|
+
return "Claude Code managed plugin is installed and current.";
|
|
3106
|
+
case "missing":
|
|
3107
|
+
return "Claude Code managed plugin is missing.";
|
|
3108
|
+
case "drift":
|
|
3109
|
+
return "Claude Code managed plugin exists but differs from expected output.";
|
|
3110
|
+
case "outdated":
|
|
3111
|
+
return "Claude Code managed plugin includes an older generated manifest.";
|
|
3112
|
+
case "unknown":
|
|
3113
|
+
return "Claude Code managed plugin could not be classified safely.";
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
function statusFromSetupPlan(plan) {
|
|
3117
|
+
const classified = plan.items.map((item) => ({
|
|
3118
|
+
item,
|
|
3119
|
+
...classifyItem(item)
|
|
3120
|
+
}));
|
|
3121
|
+
const state = aggregateState(classified.map((entry) => entry.state));
|
|
3122
|
+
return {
|
|
3123
|
+
harness: "claude",
|
|
3124
|
+
displayName: CLAUDE_CODE_DISPLAY_NAME,
|
|
3125
|
+
state,
|
|
3126
|
+
summary: statusSummary(state),
|
|
3127
|
+
targets: classified.map(
|
|
3128
|
+
({ item, state: state2, observed }) => targetForItem(item, state2, observed)
|
|
3129
|
+
),
|
|
3130
|
+
diagnostics: plan.diagnostics.map((message) => ({
|
|
3131
|
+
severity: "minor",
|
|
3132
|
+
message,
|
|
3133
|
+
code: "claude-code-diagnostic"
|
|
3134
|
+
})),
|
|
3135
|
+
actions: claudeCodeActions,
|
|
3136
|
+
disclaimers: [
|
|
3137
|
+
...claudeCodeDisclaimers(),
|
|
3138
|
+
...plan.disclaimers.map((message) => ({ message }))
|
|
3139
|
+
]
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
function getClaudeCodeStatus(context = { cwd: process.cwd() }) {
|
|
3143
|
+
let plan;
|
|
3144
|
+
try {
|
|
3145
|
+
plan = buildClaudeCodeSetupPlan(claudeCodeConfig(context, true));
|
|
3146
|
+
} catch (error) {
|
|
3147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3148
|
+
return {
|
|
3149
|
+
harness: "claude",
|
|
3150
|
+
displayName: CLAUDE_CODE_DISPLAY_NAME,
|
|
3151
|
+
state: "unknown",
|
|
3152
|
+
summary: `Claude Code setup plan could not be built: ${message}`,
|
|
3153
|
+
targets: [],
|
|
3154
|
+
diagnostics: [
|
|
3155
|
+
{
|
|
3156
|
+
severity: "critical",
|
|
3157
|
+
message,
|
|
3158
|
+
code: "claude-code-plan-build-failed"
|
|
3159
|
+
}
|
|
3160
|
+
],
|
|
3161
|
+
actions: claudeCodeActions,
|
|
3162
|
+
disclaimers: claudeCodeDisclaimers()
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
return statusFromSetupPlan(plan);
|
|
3166
|
+
}
|
|
3167
|
+
function planItemFromSetup(item) {
|
|
3168
|
+
return {
|
|
3169
|
+
title: item.description,
|
|
3170
|
+
target: targetForItem(item),
|
|
3171
|
+
preview: item.content,
|
|
3172
|
+
backup: backupForItem(item)
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
function planFromSetup(id, action, title, summary, setupPlan) {
|
|
3176
|
+
const status = statusFromSetupPlan(setupPlan);
|
|
3177
|
+
const canApply = status.state === "installed" || status.state === "missing" || status.state === "outdated" || status.state === "drift";
|
|
3178
|
+
const plan = {
|
|
3179
|
+
id,
|
|
3180
|
+
harness: "claude",
|
|
3181
|
+
action,
|
|
3182
|
+
title,
|
|
3183
|
+
summary,
|
|
3184
|
+
dryRun: true,
|
|
3185
|
+
canApply,
|
|
3186
|
+
targets: status.targets,
|
|
3187
|
+
surfaces: setupPlan.items.map(surfaceForItem),
|
|
3188
|
+
backup: {
|
|
3189
|
+
required: setupPlan.items.some((item) => item.requiresBackup),
|
|
3190
|
+
strategy: "managed-backup-file",
|
|
3191
|
+
description: "Existing Claude Code plugin files are backed up before being overwritten."
|
|
3192
|
+
},
|
|
3193
|
+
items: setupPlan.items.map(planItemFromSetup),
|
|
3194
|
+
warnings: status.diagnostics,
|
|
3195
|
+
disclaimers: [
|
|
3196
|
+
...claudeCodeDisclaimers(),
|
|
3197
|
+
...setupPlan.disclaimers.map((message) => ({ message }))
|
|
3198
|
+
]
|
|
3199
|
+
};
|
|
3200
|
+
claudeCodePlanSources.set(plan, setupPlan);
|
|
3201
|
+
return plan;
|
|
3202
|
+
}
|
|
3203
|
+
function buildClaudeCodeInstallPlan(context = { cwd: process.cwd() }) {
|
|
3204
|
+
return planFromSetup(
|
|
3205
|
+
"claude-code-install-preview",
|
|
3206
|
+
"install",
|
|
3207
|
+
"Install Claude Code plugin package",
|
|
3208
|
+
"Preview Claude Code plugin package install using buildClaudeCodeSetupPlan().",
|
|
3209
|
+
buildClaudeCodeSetupPlan(claudeCodeConfig(context, true))
|
|
3210
|
+
);
|
|
3211
|
+
}
|
|
3212
|
+
function buildClaudeCodeUpdatePlan(context = { cwd: process.cwd() }) {
|
|
3213
|
+
return planFromSetup(
|
|
3214
|
+
"claude-code-update-preview",
|
|
3215
|
+
"update",
|
|
3216
|
+
"Update Claude Code plugin package",
|
|
3217
|
+
"Preview Claude Code managed plugin refresh using buildClaudeCodeSetupPlan().",
|
|
3218
|
+
buildClaudeCodeSetupPlan(claudeCodeConfig(context, true))
|
|
3219
|
+
);
|
|
3220
|
+
}
|
|
3221
|
+
function buildClaudeCodeSyncPlan(context = { cwd: process.cwd() }) {
|
|
3222
|
+
return planFromSetup(
|
|
3223
|
+
"claude-code-sync-preview",
|
|
3224
|
+
"sync",
|
|
3225
|
+
"Sync Claude Code plugin package",
|
|
3226
|
+
"Preview Claude Code managed plugin subagents, MCP, hooks, and skills.",
|
|
3227
|
+
buildClaudeCodeSetupPlan(claudeCodeConfig(context, true))
|
|
3228
|
+
);
|
|
3229
|
+
}
|
|
3230
|
+
function isClaudeCodeRole(role) {
|
|
3231
|
+
return CLAUDE_CODE_ROLE_NAMES.includes(role);
|
|
3232
|
+
}
|
|
3233
|
+
function buildClaudeCodeModelPlan(input, context = { cwd: process.cwd() }) {
|
|
3234
|
+
const status = getClaudeCodeStatus(context);
|
|
3235
|
+
const supportedRoles = input.roles.filter((role) => isClaudeCodeRole(role.role)).filter((role) => isClaudeCodeModelAlias(role.model)).map((role) => ({
|
|
3236
|
+
role: role.role,
|
|
3237
|
+
model: role.model
|
|
3238
|
+
}));
|
|
3239
|
+
const rejectedRoles = input.roles.filter(
|
|
3240
|
+
(role) => !isClaudeCodeRole(role.role) || !isClaudeCodeModelAlias(role.model)
|
|
3241
|
+
);
|
|
3242
|
+
const warnings = [
|
|
3243
|
+
...status.diagnostics,
|
|
3244
|
+
...input.warnings ?? [],
|
|
3245
|
+
...rejectedRoles.map(
|
|
3246
|
+
(role) => warning(
|
|
3247
|
+
`Claude Code does not accept role "${role.role}" with model "${role.model}"; roles must be one of ${CLAUDE_CODE_ROLE_NAMES.join(", ")} and models must be sonnet, opus, haiku, or inherit.`,
|
|
3248
|
+
"claude-code-unsupported-model-role"
|
|
3249
|
+
)
|
|
3250
|
+
)
|
|
3251
|
+
];
|
|
3252
|
+
if (input.harness !== "claude") {
|
|
3253
|
+
warnings.push(
|
|
3254
|
+
warning(
|
|
3255
|
+
"Model plan target harness must be claude.",
|
|
3256
|
+
"claude-code-model-harness-mismatch"
|
|
3257
|
+
)
|
|
3258
|
+
);
|
|
3259
|
+
}
|
|
3260
|
+
const targets = supportedRoles.map(({ role }) => {
|
|
3261
|
+
const target = status.targets.find(
|
|
3262
|
+
(candidate) => candidate.path?.endsWith(`agents${pathSep()}${role}.md`)
|
|
3263
|
+
);
|
|
3264
|
+
return target ?? {
|
|
3265
|
+
kind: "generated-artifact",
|
|
3266
|
+
label: `Claude Code ${role} subagent`,
|
|
3267
|
+
state: "missing"
|
|
3268
|
+
};
|
|
3269
|
+
});
|
|
3270
|
+
const stateTarget = status.targets.find(
|
|
3271
|
+
(target) => target.path?.endsWith(".thoth-agents-managed-models.json")
|
|
3272
|
+
);
|
|
3273
|
+
const plan = {
|
|
3274
|
+
id: "claude-code-model-config-preview",
|
|
3275
|
+
harness: "claude",
|
|
3276
|
+
action: "model-config",
|
|
3277
|
+
title: "Configure Claude Code subagent model lines",
|
|
3278
|
+
summary: "Preview model changes for generated Claude Code subagent files and managed model state only.",
|
|
3279
|
+
dryRun: true,
|
|
3280
|
+
canApply: input.harness === "claude" && supportedRoles.length > 0 && status.state !== "unknown",
|
|
3281
|
+
targets: [...targets, ...stateTarget ? [stateTarget] : []],
|
|
3282
|
+
surfaces: targets.map((target) => ({
|
|
3283
|
+
id: `claude-code-model:${target.label}`,
|
|
3284
|
+
label: target.label ?? "Claude Code subagent",
|
|
3285
|
+
path: target.path,
|
|
3286
|
+
state: target.state
|
|
3287
|
+
})),
|
|
3288
|
+
backup: {
|
|
3289
|
+
required: true,
|
|
3290
|
+
strategy: "managed-backup-file",
|
|
3291
|
+
description: "Existing subagent files and managed model state are backed up by the managed write helper."
|
|
3292
|
+
},
|
|
3293
|
+
items: supportedRoles.map(({ role, model }) => ({
|
|
3294
|
+
title: `Set ${role} Claude Code subagent model line`,
|
|
3295
|
+
target: targets.find(
|
|
3296
|
+
(target) => target.path?.endsWith(`agents${pathSep()}${role}.md`)
|
|
3297
|
+
) ?? {
|
|
3298
|
+
kind: "generated-artifact",
|
|
3299
|
+
label: `Claude Code ${role} subagent`
|
|
3300
|
+
},
|
|
3301
|
+
preview: JSON.stringify({ role, model }),
|
|
3302
|
+
backup: { required: true, strategy: "managed-backup-file" }
|
|
3303
|
+
})),
|
|
3304
|
+
warnings,
|
|
3305
|
+
disclaimers: [
|
|
3306
|
+
...claudeCodeDisclaimers(),
|
|
3307
|
+
...input.disclaimers ?? [],
|
|
3308
|
+
{
|
|
3309
|
+
message: "Claude Code model configuration writes only generated subagent frontmatter model lines and the managed model state JSON.",
|
|
3310
|
+
code: "claude-code-model-supported-surface"
|
|
3311
|
+
}
|
|
3312
|
+
]
|
|
3313
|
+
};
|
|
3314
|
+
claudeCodeModelSources.set(plan, {
|
|
3315
|
+
config: claudeCodeConfig(context, false),
|
|
3316
|
+
roles: supportedRoles
|
|
3317
|
+
});
|
|
3318
|
+
return plan;
|
|
3319
|
+
}
|
|
3320
|
+
function pathSep() {
|
|
3321
|
+
return process.platform === "win32" ? "\\" : "/";
|
|
3322
|
+
}
|
|
3323
|
+
function rejectPlan(plan, message, severity = "critical") {
|
|
3324
|
+
return {
|
|
3325
|
+
harness: plan.harness,
|
|
3326
|
+
action: plan.action,
|
|
3327
|
+
applied: false,
|
|
3328
|
+
summary: message,
|
|
3329
|
+
changedTargets: [],
|
|
3330
|
+
backups: [],
|
|
3331
|
+
warnings: [{ severity, message }],
|
|
3332
|
+
disclaimers: claudeCodeDisclaimers()
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
function validateClaudeCodePlan(plan) {
|
|
3336
|
+
if (plan.harness !== "claude") {
|
|
3337
|
+
return rejectPlan(plan, "Only Claude Code operation plans can be applied.");
|
|
3338
|
+
}
|
|
3339
|
+
if (!plan.canApply) {
|
|
3340
|
+
return rejectPlan(
|
|
3341
|
+
plan,
|
|
3342
|
+
"Claude Code plan cannot be applied because canApply is false."
|
|
3343
|
+
);
|
|
3344
|
+
}
|
|
3345
|
+
if (!["install", "update", "sync", "model-config"].includes(plan.action)) {
|
|
3346
|
+
return rejectPlan(
|
|
3347
|
+
plan,
|
|
3348
|
+
`Unsupported Claude Code apply action: ${plan.action}.`
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
if (plan.items.length === 0) {
|
|
3352
|
+
return rejectPlan(plan, "Claude Code plan has no items to apply.");
|
|
3353
|
+
}
|
|
3354
|
+
return null;
|
|
3355
|
+
}
|
|
3356
|
+
function applyClaudeCodePlan(plan) {
|
|
3357
|
+
const rejection = validateClaudeCodePlan(plan);
|
|
3358
|
+
if (rejection) return rejection;
|
|
3359
|
+
if (plan.action === "model-config") {
|
|
3360
|
+
const source = claudeCodeModelSources.get(plan);
|
|
3361
|
+
if (!source) {
|
|
3362
|
+
return rejectPlan(
|
|
3363
|
+
plan,
|
|
3364
|
+
"Claude Code model plan was not produced by buildClaudeCodeModelPlan in this process."
|
|
3365
|
+
);
|
|
3366
|
+
}
|
|
3367
|
+
const result2 = applyClaudeCodeManagedModelOverrides(
|
|
3368
|
+
source.config,
|
|
3369
|
+
source.roles.map((role) => ({
|
|
3370
|
+
role: role.role,
|
|
3371
|
+
model: role.model
|
|
3372
|
+
}))
|
|
3373
|
+
);
|
|
3374
|
+
return {
|
|
3375
|
+
harness: "claude",
|
|
3376
|
+
action: "model-config",
|
|
3377
|
+
applied: result2.success,
|
|
3378
|
+
summary: result2.success ? "Applied Claude Code subagent model overrides." : result2.error ?? "Failed to apply Claude Code subagent model overrides.",
|
|
3379
|
+
changedTargets: result2.changed.map((path4) => ({
|
|
3380
|
+
kind: path4.endsWith(".json") ? "memory-state" : "generated-artifact",
|
|
3381
|
+
path: path4,
|
|
3382
|
+
label: basename2(path4),
|
|
3383
|
+
state: "installed"
|
|
3384
|
+
})),
|
|
3385
|
+
backups: result2.changed.filter((path4) => existsSync7(`${path4}.bak`)).map((path4) => ({ path: `${path4}.bak`, label: "managed backup" })),
|
|
3386
|
+
warnings: result2.success ? [] : [{ severity: "critical", message: result2.error ?? "apply failed." }],
|
|
3387
|
+
disclaimers: claudeCodeDisclaimers()
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
const setupPlan = claudeCodePlanSources.get(plan);
|
|
3391
|
+
if (!setupPlan) {
|
|
3392
|
+
return rejectPlan(
|
|
3393
|
+
plan,
|
|
3394
|
+
"Claude Code setup plan was not produced by a Claude Code operation plan builder in this process."
|
|
3395
|
+
);
|
|
3396
|
+
}
|
|
3397
|
+
const result = applyClaudeCodeSetup({ ...setupPlan, dryRun: false });
|
|
3398
|
+
return {
|
|
3399
|
+
harness: "claude",
|
|
3400
|
+
action: plan.action,
|
|
3401
|
+
applied: result.success,
|
|
3402
|
+
summary: result.success ? `Applied Claude Code managed ${plan.action} plan.` : result.error ?? `Failed to apply Claude Code ${plan.action} plan.`,
|
|
3403
|
+
changedTargets: result.changed.map((path4) => ({
|
|
3404
|
+
kind: path4.endsWith(".json") ? "memory-state" : "generated-artifact",
|
|
3405
|
+
path: path4,
|
|
3406
|
+
label: basename2(path4),
|
|
3407
|
+
state: "installed"
|
|
3408
|
+
})),
|
|
3409
|
+
backups: result.changed.filter((path4) => existsSync7(`${path4}.bak`)).map((path4) => ({ path: `${path4}.bak`, label: "managed backup" })),
|
|
3410
|
+
warnings: result.success ? [] : [{ severity: "critical", message: result.error ?? "apply failed." }],
|
|
3411
|
+
disclaimers: claudeCodeDisclaimers()
|
|
3412
|
+
};
|
|
3413
|
+
}
|
|
3414
|
+
function defaultClaudeCodeModelRoles() {
|
|
3415
|
+
return CLAUDE_CODE_ROLE_NAMES.map((role) => ({
|
|
3416
|
+
role,
|
|
3417
|
+
model: CLAUDE_CODE_SUBAGENT_DEFAULT_MODELS[role]
|
|
3418
|
+
}));
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
// src/cli/operations/codex.ts
|
|
3422
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
|
|
3423
|
+
import { basename as basename3 } from "path";
|
|
3424
|
+
var CODEX_DISPLAY_NAME = "Codex";
|
|
3425
|
+
var codexPlanSources = /* @__PURE__ */ new WeakMap();
|
|
3426
|
+
var codexModelSources = /* @__PURE__ */ new WeakMap();
|
|
3427
|
+
var codexActions = [
|
|
3428
|
+
{
|
|
3429
|
+
id: "codex-status",
|
|
3430
|
+
kind: "status",
|
|
3431
|
+
label: "Status",
|
|
3432
|
+
description: "Inspect managed Codex setup state",
|
|
3433
|
+
dryRun: false,
|
|
3434
|
+
requiresConfirmation: false,
|
|
3435
|
+
supported: true
|
|
3436
|
+
},
|
|
2303
3437
|
{
|
|
2304
3438
|
id: "codex-list",
|
|
2305
3439
|
kind: "list",
|
|
@@ -2377,10 +3511,10 @@ function codexDisclaimers() {
|
|
|
2377
3511
|
}
|
|
2378
3512
|
];
|
|
2379
3513
|
}
|
|
2380
|
-
function
|
|
3514
|
+
function warning2(message, code) {
|
|
2381
3515
|
return { severity: "important", message, code };
|
|
2382
3516
|
}
|
|
2383
|
-
function
|
|
3517
|
+
function targetForItem2(item, state, observed) {
|
|
2384
3518
|
return {
|
|
2385
3519
|
kind: item.kind === "managed-model-state" ? "memory-state" : "generated-artifact",
|
|
2386
3520
|
path: item.targetPath,
|
|
@@ -2391,15 +3525,15 @@ function targetForItem(item, state, observed) {
|
|
|
2391
3525
|
description: item.description
|
|
2392
3526
|
};
|
|
2393
3527
|
}
|
|
2394
|
-
function
|
|
3528
|
+
function surfaceForItem2(item) {
|
|
2395
3529
|
return {
|
|
2396
|
-
id: `${item.kind}:${item.role ??
|
|
3530
|
+
id: `${item.kind}:${item.role ?? basename3(item.targetPath)}`,
|
|
2397
3531
|
label: item.role ? `Codex ${item.role} subagent TOML` : item.kind.replaceAll("-", " "),
|
|
2398
3532
|
path: item.targetPath,
|
|
2399
3533
|
description: item.description
|
|
2400
3534
|
};
|
|
2401
3535
|
}
|
|
2402
|
-
function
|
|
3536
|
+
function backupForItem2(item) {
|
|
2403
3537
|
return {
|
|
2404
3538
|
required: item.requiresBackup,
|
|
2405
3539
|
strategy: item.requiresBackup ? "managed-backup-file" : "none",
|
|
@@ -2407,9 +3541,9 @@ function backupForItem(item) {
|
|
|
2407
3541
|
};
|
|
2408
3542
|
}
|
|
2409
3543
|
function rootBlockState(item) {
|
|
2410
|
-
if (!
|
|
3544
|
+
if (!existsSync8(item.targetPath))
|
|
2411
3545
|
return { state: "missing", observed: "absent" };
|
|
2412
|
-
const content =
|
|
3546
|
+
const content = readFileSync9(item.targetPath, "utf8");
|
|
2413
3547
|
if (item.content && content.includes(item.content)) {
|
|
2414
3548
|
return { state: "installed", observed: "managed root block present" };
|
|
2415
3549
|
}
|
|
@@ -2419,22 +3553,22 @@ function rootBlockState(item) {
|
|
|
2419
3553
|
return { state: "missing", observed: "managed root block absent" };
|
|
2420
3554
|
}
|
|
2421
3555
|
function managedModelState(item) {
|
|
2422
|
-
if (!
|
|
3556
|
+
if (!existsSync8(item.targetPath))
|
|
2423
3557
|
return { state: "missing", observed: "absent" };
|
|
2424
3558
|
try {
|
|
2425
|
-
const parsed = JSON.parse(
|
|
3559
|
+
const parsed = JSON.parse(readFileSync9(item.targetPath, "utf8"));
|
|
2426
3560
|
if (parsed.version !== MANAGED_MODEL_STATE_VERSION || !parsed.models || typeof parsed.models !== "object" || Array.isArray(parsed.models)) {
|
|
2427
3561
|
return { state: "unknown", observed: "invalid managed model state" };
|
|
2428
3562
|
}
|
|
2429
|
-
return item.content ===
|
|
3563
|
+
return item.content === readFileSync9(item.targetPath, "utf8") ? { state: "installed", observed: "managed model state current" } : { state: "drift", observed: "managed model state differs" };
|
|
2430
3564
|
} catch {
|
|
2431
3565
|
return { state: "unknown", observed: "unparseable managed model state" };
|
|
2432
3566
|
}
|
|
2433
3567
|
}
|
|
2434
3568
|
function userConfigState(item) {
|
|
2435
|
-
if (!
|
|
3569
|
+
if (!existsSync8(item.targetPath))
|
|
2436
3570
|
return { state: "missing", observed: "absent" };
|
|
2437
|
-
const content =
|
|
3571
|
+
const content = readFileSync9(item.targetPath, "utf8");
|
|
2438
3572
|
if (/^\s*default_mode_request_user_input\s*=\s*true\b/m.test(content)) {
|
|
2439
3573
|
return {
|
|
2440
3574
|
state: "installed",
|
|
@@ -2447,10 +3581,10 @@ function userConfigState(item) {
|
|
|
2447
3581
|
return { state: "missing", observed: "managed feature flag absent" };
|
|
2448
3582
|
}
|
|
2449
3583
|
function marketplaceState(item) {
|
|
2450
|
-
if (!
|
|
3584
|
+
if (!existsSync8(item.targetPath))
|
|
2451
3585
|
return { state: "missing", observed: "absent" };
|
|
2452
3586
|
try {
|
|
2453
|
-
const parsed = JSON.parse(
|
|
3587
|
+
const parsed = JSON.parse(readFileSync9(item.targetPath, "utf8"));
|
|
2454
3588
|
const plugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
|
|
2455
3589
|
const entry = plugins.find(
|
|
2456
3590
|
(plugin) => plugin && typeof plugin === "object" && "name" in plugin && plugin.name === "thoth-agents"
|
|
@@ -2463,11 +3597,11 @@ function marketplaceState(item) {
|
|
|
2463
3597
|
return { state: "unknown", observed: "unparseable marketplace JSON" };
|
|
2464
3598
|
}
|
|
2465
3599
|
}
|
|
2466
|
-
function
|
|
2467
|
-
if (!
|
|
3600
|
+
function contentState2(item) {
|
|
3601
|
+
if (!existsSync8(item.targetPath))
|
|
2468
3602
|
return { state: "missing", observed: "absent" };
|
|
2469
|
-
const observed =
|
|
2470
|
-
if (
|
|
3603
|
+
const observed = readFileSync9(item.targetPath, "utf8");
|
|
3604
|
+
if (basename3(item.targetPath) === "plugin.json" && item.targetPath.replaceAll("\\", "/").includes("/.codex-plugin/")) {
|
|
2471
3605
|
try {
|
|
2472
3606
|
const expectedVersion = JSON.parse(item.content ?? "{}").version;
|
|
2473
3607
|
const observedVersion = JSON.parse(observed).version;
|
|
@@ -2495,7 +3629,7 @@ function contentState(item) {
|
|
|
2495
3629
|
}
|
|
2496
3630
|
return { state: "drift", observed: "content differs" };
|
|
2497
3631
|
}
|
|
2498
|
-
function
|
|
3632
|
+
function classifyItem2(item) {
|
|
2499
3633
|
if (item.action === "diagnose-only") {
|
|
2500
3634
|
return { state: "unknown", observed: "diagnostic guidance only" };
|
|
2501
3635
|
}
|
|
@@ -2505,16 +3639,16 @@ function classifyItem(item) {
|
|
|
2505
3639
|
}
|
|
2506
3640
|
if (item.action === "merge-toml") return userConfigState(item);
|
|
2507
3641
|
if (item.action === "merge-marketplace") return marketplaceState(item);
|
|
2508
|
-
return
|
|
3642
|
+
return contentState2(item);
|
|
2509
3643
|
}
|
|
2510
|
-
function
|
|
3644
|
+
function aggregateState2(states) {
|
|
2511
3645
|
if (states.includes("unknown")) return "unknown";
|
|
2512
3646
|
if (states.includes("drift")) return "drift";
|
|
2513
3647
|
if (states.includes("outdated")) return "outdated";
|
|
2514
3648
|
if (states.includes("missing")) return "missing";
|
|
2515
3649
|
return "installed";
|
|
2516
3650
|
}
|
|
2517
|
-
function
|
|
3651
|
+
function statusSummary2(state) {
|
|
2518
3652
|
switch (state) {
|
|
2519
3653
|
case "installed":
|
|
2520
3654
|
return "Codex managed setup surfaces are installed and current.";
|
|
@@ -2551,8 +3685,8 @@ function getCodexStatus(context = { cwd: process.cwd() }) {
|
|
|
2551
3685
|
disclaimers: codexDisclaimers()
|
|
2552
3686
|
};
|
|
2553
3687
|
}
|
|
2554
|
-
const classified = plan.items.filter((item) => item.action !== "diagnose-only").map((item) => ({ item, ...
|
|
2555
|
-
const state =
|
|
3688
|
+
const classified = plan.items.filter((item) => item.action !== "diagnose-only").map((item) => ({ item, ...classifyItem2(item) }));
|
|
3689
|
+
const state = aggregateState2(classified.map((item) => item.state));
|
|
2556
3690
|
const diagnostics = plan.diagnostics.map((message) => ({
|
|
2557
3691
|
severity: "minor",
|
|
2558
3692
|
message,
|
|
@@ -2562,9 +3696,9 @@ function getCodexStatus(context = { cwd: process.cwd() }) {
|
|
|
2562
3696
|
harness: "codex",
|
|
2563
3697
|
displayName: CODEX_DISPLAY_NAME,
|
|
2564
3698
|
state,
|
|
2565
|
-
summary:
|
|
3699
|
+
summary: statusSummary2(state),
|
|
2566
3700
|
targets: classified.map(
|
|
2567
|
-
({ item, state: state2, observed }) =>
|
|
3701
|
+
({ item, state: state2, observed }) => targetForItem2(item, state2, observed)
|
|
2568
3702
|
),
|
|
2569
3703
|
diagnostics,
|
|
2570
3704
|
actions: codexActions,
|
|
@@ -2574,15 +3708,15 @@ function getCodexStatus(context = { cwd: process.cwd() }) {
|
|
|
2574
3708
|
]
|
|
2575
3709
|
};
|
|
2576
3710
|
}
|
|
2577
|
-
function
|
|
3711
|
+
function planItemFromSetup2(item) {
|
|
2578
3712
|
return {
|
|
2579
3713
|
title: item.description,
|
|
2580
|
-
target:
|
|
3714
|
+
target: targetForItem2(item),
|
|
2581
3715
|
preview: item.content,
|
|
2582
|
-
backup:
|
|
3716
|
+
backup: backupForItem2(item)
|
|
2583
3717
|
};
|
|
2584
3718
|
}
|
|
2585
|
-
function
|
|
3719
|
+
function planFromSetup2(id, action, title, summary, setupPlan, context) {
|
|
2586
3720
|
const status = getCodexStatus(context);
|
|
2587
3721
|
const canApply = status.state === "installed" || status.state === "missing" || status.state === "outdated";
|
|
2588
3722
|
const plan = {
|
|
@@ -2594,17 +3728,17 @@ function planFromSetup(id, action, title, summary, setupPlan, context) {
|
|
|
2594
3728
|
dryRun: true,
|
|
2595
3729
|
canApply,
|
|
2596
3730
|
targets: status.targets,
|
|
2597
|
-
surfaces: setupPlan.items.filter((item) => item.action !== "diagnose-only").map(
|
|
3731
|
+
surfaces: setupPlan.items.filter((item) => item.action !== "diagnose-only").map(surfaceForItem2),
|
|
2598
3732
|
backup: {
|
|
2599
3733
|
required: setupPlan.items.some((item) => item.requiresBackup),
|
|
2600
3734
|
strategy: "existing-helper",
|
|
2601
3735
|
description: "Codex setup apply uses the existing installer backup behavior for files that already exist."
|
|
2602
3736
|
},
|
|
2603
|
-
items: setupPlan.items.map(
|
|
3737
|
+
items: setupPlan.items.map(planItemFromSetup2),
|
|
2604
3738
|
warnings: [
|
|
2605
3739
|
...status.diagnostics,
|
|
2606
3740
|
...canApply ? [] : [
|
|
2607
|
-
|
|
3741
|
+
warning2(
|
|
2608
3742
|
`Codex state is ${status.state}; apply is disabled until the state is safely classified or repaired.`,
|
|
2609
3743
|
"codex-unsafe-state"
|
|
2610
3744
|
)
|
|
@@ -2620,7 +3754,7 @@ function planFromSetup(id, action, title, summary, setupPlan, context) {
|
|
|
2620
3754
|
}
|
|
2621
3755
|
function buildCodexUpdatePlan(context = { cwd: process.cwd() }) {
|
|
2622
3756
|
const setupPlan = buildCodexSetupPlan(codexConfig(context, true));
|
|
2623
|
-
return
|
|
3757
|
+
return planFromSetup2(
|
|
2624
3758
|
"codex-update-preview",
|
|
2625
3759
|
"update",
|
|
2626
3760
|
"Update Codex managed setup",
|
|
@@ -2631,7 +3765,7 @@ function buildCodexUpdatePlan(context = { cwd: process.cwd() }) {
|
|
|
2631
3765
|
}
|
|
2632
3766
|
function buildCodexSyncPlan(context = { cwd: process.cwd() }) {
|
|
2633
3767
|
const setupPlan = buildCodexSetupPlan(codexConfig(context, true));
|
|
2634
|
-
return
|
|
3768
|
+
return planFromSetup2(
|
|
2635
3769
|
"codex-sync-preview",
|
|
2636
3770
|
"sync",
|
|
2637
3771
|
"Sync Codex managed configuration",
|
|
@@ -2642,7 +3776,7 @@ function buildCodexSyncPlan(context = { cwd: process.cwd() }) {
|
|
|
2642
3776
|
}
|
|
2643
3777
|
function buildCodexInstallPlan(context = { cwd: process.cwd() }) {
|
|
2644
3778
|
const setupPlan = buildCodexSetupPlan(codexConfig(context, true));
|
|
2645
|
-
return
|
|
3779
|
+
return planFromSetup2(
|
|
2646
3780
|
"codex-install-preview",
|
|
2647
3781
|
"install",
|
|
2648
3782
|
"Install Codex managed setup",
|
|
@@ -2673,7 +3807,7 @@ function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
|
|
|
2673
3807
|
...status.diagnostics,
|
|
2674
3808
|
...input.warnings ?? [],
|
|
2675
3809
|
...unsupportedRoles.map(
|
|
2676
|
-
(role) =>
|
|
3810
|
+
(role) => warning2(
|
|
2677
3811
|
`Codex does not expose a supported generated model surface for ${role.role}; this plan will not write that role.`,
|
|
2678
3812
|
"codex-unsupported-model-role"
|
|
2679
3813
|
)
|
|
@@ -2681,7 +3815,7 @@ function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
|
|
|
2681
3815
|
];
|
|
2682
3816
|
if (input.harness !== "codex") {
|
|
2683
3817
|
warnings.push(
|
|
2684
|
-
|
|
3818
|
+
warning2(
|
|
2685
3819
|
"Model plan target harness must be codex.",
|
|
2686
3820
|
"codex-model-harness-mismatch"
|
|
2687
3821
|
)
|
|
@@ -2748,7 +3882,7 @@ function buildCodexModelPlan(input, context = { cwd: process.cwd() }) {
|
|
|
2748
3882
|
codexModelSources.set(plan, { config, roles: supportedRoles });
|
|
2749
3883
|
return plan;
|
|
2750
3884
|
}
|
|
2751
|
-
function
|
|
3885
|
+
function rejectPlan2(plan, message, severity = "critical") {
|
|
2752
3886
|
return {
|
|
2753
3887
|
harness: plan.harness,
|
|
2754
3888
|
action: plan.action,
|
|
@@ -2762,19 +3896,19 @@ function rejectPlan(plan, message, severity = "critical") {
|
|
|
2762
3896
|
}
|
|
2763
3897
|
function validateCodexPlan(plan) {
|
|
2764
3898
|
if (plan.harness !== "codex") {
|
|
2765
|
-
return
|
|
3899
|
+
return rejectPlan2(plan, "Only Codex operation plans can be applied.");
|
|
2766
3900
|
}
|
|
2767
3901
|
if (!plan.canApply) {
|
|
2768
|
-
return
|
|
3902
|
+
return rejectPlan2(
|
|
2769
3903
|
plan,
|
|
2770
3904
|
"Codex plan cannot be applied because canApply is false."
|
|
2771
3905
|
);
|
|
2772
3906
|
}
|
|
2773
3907
|
if (!["install", "update", "sync", "model-config"].includes(plan.action)) {
|
|
2774
|
-
return
|
|
3908
|
+
return rejectPlan2(plan, `Unsupported Codex apply action: ${plan.action}.`);
|
|
2775
3909
|
}
|
|
2776
3910
|
if (plan.items.length === 0) {
|
|
2777
|
-
return
|
|
3911
|
+
return rejectPlan2(plan, "Codex plan has no items to apply.");
|
|
2778
3912
|
}
|
|
2779
3913
|
return null;
|
|
2780
3914
|
}
|
|
@@ -2784,7 +3918,7 @@ function applyCodexPlan(plan) {
|
|
|
2784
3918
|
if (plan.action === "model-config") {
|
|
2785
3919
|
const source = codexModelSources.get(plan);
|
|
2786
3920
|
if (!source) {
|
|
2787
|
-
return
|
|
3921
|
+
return rejectPlan2(
|
|
2788
3922
|
plan,
|
|
2789
3923
|
"Codex model plan was not produced by buildCodexModelPlan in this process."
|
|
2790
3924
|
);
|
|
@@ -2795,13 +3929,13 @@ function applyCodexPlan(plan) {
|
|
|
2795
3929
|
action: "model-config",
|
|
2796
3930
|
applied: result2.success,
|
|
2797
3931
|
summary: result2.success ? "Applied Codex subagent model overrides." : result2.error ?? "Failed to apply Codex subagent model overrides.",
|
|
2798
|
-
changedTargets: result2.changed.map((
|
|
2799
|
-
kind:
|
|
2800
|
-
path:
|
|
2801
|
-
label:
|
|
3932
|
+
changedTargets: result2.changed.map((path4) => ({
|
|
3933
|
+
kind: path4.endsWith(".json") ? "memory-state" : "generated-artifact",
|
|
3934
|
+
path: path4,
|
|
3935
|
+
label: basename3(path4),
|
|
2802
3936
|
state: "installed"
|
|
2803
3937
|
})),
|
|
2804
|
-
backups: result2.changed.filter((
|
|
3938
|
+
backups: result2.changed.filter((path4) => existsSync8(`${path4}.bak`)).map((path4) => ({ path: `${path4}.bak`, label: "managed backup" })),
|
|
2805
3939
|
warnings: result2.success ? [] : [
|
|
2806
3940
|
{
|
|
2807
3941
|
severity: "critical",
|
|
@@ -2813,7 +3947,7 @@ function applyCodexPlan(plan) {
|
|
|
2813
3947
|
}
|
|
2814
3948
|
const setupPlan = codexPlanSources.get(plan);
|
|
2815
3949
|
if (!setupPlan) {
|
|
2816
|
-
return
|
|
3950
|
+
return rejectPlan2(
|
|
2817
3951
|
plan,
|
|
2818
3952
|
"Codex setup plan was not produced by a Codex operation plan builder in this process."
|
|
2819
3953
|
);
|
|
@@ -2824,13 +3958,13 @@ function applyCodexPlan(plan) {
|
|
|
2824
3958
|
action: plan.action,
|
|
2825
3959
|
applied: result.success,
|
|
2826
3960
|
summary: result.success ? `Applied Codex managed ${plan.action} plan.` : result.error ?? `Failed to apply Codex ${plan.action} plan.`,
|
|
2827
|
-
changedTargets: result.changed.map((
|
|
2828
|
-
kind:
|
|
2829
|
-
path:
|
|
2830
|
-
label:
|
|
3961
|
+
changedTargets: result.changed.map((path4) => ({
|
|
3962
|
+
kind: path4.endsWith(".json") ? "memory-state" : "generated-artifact",
|
|
3963
|
+
path: path4,
|
|
3964
|
+
label: basename3(path4),
|
|
2831
3965
|
state: "installed"
|
|
2832
3966
|
})),
|
|
2833
|
-
backups: result.changed.filter((
|
|
3967
|
+
backups: result.changed.filter((path4) => existsSync8(`${path4}.bak`)).map((path4) => ({ path: `${path4}.bak`, label: "managed backup" })),
|
|
2834
3968
|
warnings: result.success ? [] : [
|
|
2835
3969
|
{
|
|
2836
3970
|
severity: "critical",
|
|
@@ -2842,14 +3976,14 @@ function applyCodexPlan(plan) {
|
|
|
2842
3976
|
}
|
|
2843
3977
|
|
|
2844
3978
|
// src/cli/operations/opencode.ts
|
|
2845
|
-
import { existsSync as
|
|
2846
|
-
import { join as
|
|
3979
|
+
import { existsSync as existsSync10 } from "fs";
|
|
3980
|
+
import { join as join10 } from "path";
|
|
2847
3981
|
|
|
2848
3982
|
// src/cli/skills.ts
|
|
2849
3983
|
import { spawnSync } from "child_process";
|
|
2850
|
-
import { existsSync as
|
|
2851
|
-
import { homedir as
|
|
2852
|
-
import { join as
|
|
3984
|
+
import { existsSync as existsSync9 } from "fs";
|
|
3985
|
+
import { homedir as homedir3 } from "os";
|
|
3986
|
+
import { join as join9 } from "path";
|
|
2853
3987
|
var RECOMMENDED_SKILLS = [
|
|
2854
3988
|
{
|
|
2855
3989
|
name: "simplify",
|
|
@@ -2864,11 +3998,11 @@ var RECOMMENDED_SKILLS = [
|
|
|
2864
3998
|
description: "Browser automation for visual checks and testing"
|
|
2865
3999
|
}
|
|
2866
4000
|
];
|
|
2867
|
-
function getRecommendedSkillPath(skill, homeDir =
|
|
2868
|
-
return
|
|
4001
|
+
function getRecommendedSkillPath(skill, homeDir = homedir3()) {
|
|
4002
|
+
return join9(homeDir, ".agents", "skills", skill.skillName, "SKILL.md");
|
|
2869
4003
|
}
|
|
2870
4004
|
function isRecommendedSkillInstalled(skill, options = {}) {
|
|
2871
|
-
return
|
|
4005
|
+
return existsSync9(getRecommendedSkillPath(skill, options.homeDir));
|
|
2872
4006
|
}
|
|
2873
4007
|
function runSkillInstallCommand(skill) {
|
|
2874
4008
|
const args = [
|
|
@@ -3022,11 +4156,11 @@ function openCodeSkillTargets(context) {
|
|
|
3022
4156
|
const homeDir = homeDirFromContext(context);
|
|
3023
4157
|
const recommendedTargets = RECOMMENDED_SKILLS.map(
|
|
3024
4158
|
(skill) => {
|
|
3025
|
-
const
|
|
3026
|
-
const installed =
|
|
4159
|
+
const path4 = getRecommendedSkillPath(skill, homeDir);
|
|
4160
|
+
const installed = existsSync10(path4);
|
|
3027
4161
|
return {
|
|
3028
4162
|
kind: "skill",
|
|
3029
|
-
path:
|
|
4163
|
+
path: path4,
|
|
3030
4164
|
label: titleCaseSkillName(skill.skillName),
|
|
3031
4165
|
state: installed ? "installed" : "missing",
|
|
3032
4166
|
expected: "recommended global OpenCode skill",
|
|
@@ -3035,11 +4169,11 @@ function openCodeSkillTargets(context) {
|
|
|
3035
4169
|
}
|
|
3036
4170
|
);
|
|
3037
4171
|
const bundledTargets = CUSTOM_SKILLS.map((skill) => {
|
|
3038
|
-
const
|
|
3039
|
-
const installed =
|
|
4172
|
+
const path4 = join10(getCustomSkillsDir(), skill.name, "SKILL.md");
|
|
4173
|
+
const installed = existsSync10(path4);
|
|
3040
4174
|
return {
|
|
3041
4175
|
kind: "skill",
|
|
3042
|
-
path:
|
|
4176
|
+
path: path4,
|
|
3043
4177
|
label: titleCaseSkillName(skill.name),
|
|
3044
4178
|
state: installed ? "installed" : "missing",
|
|
3045
4179
|
expected: "bundled thoth-agents OpenCode skill",
|
|
@@ -3142,8 +4276,8 @@ function getOpenCodeStatus(context = { cwd: process.cwd() }) {
|
|
|
3142
4276
|
actions: openCodeActions
|
|
3143
4277
|
};
|
|
3144
4278
|
}
|
|
3145
|
-
const mainExists =
|
|
3146
|
-
const liteExists =
|
|
4279
|
+
const mainExists = existsSync10(mainPath);
|
|
4280
|
+
const liteExists = existsSync10(litePath);
|
|
3147
4281
|
const mainTarget = {
|
|
3148
4282
|
...targetForMainConfig(),
|
|
3149
4283
|
observed: configPluginMarker(main.config)
|
|
@@ -3240,11 +4374,11 @@ function getOpenCodeStatus(context = { cwd: process.cwd() }) {
|
|
|
3240
4374
|
actions: openCodeActions
|
|
3241
4375
|
};
|
|
3242
4376
|
}
|
|
3243
|
-
function defaultBackup(
|
|
4377
|
+
function defaultBackup(path4) {
|
|
3244
4378
|
return {
|
|
3245
4379
|
required: true,
|
|
3246
4380
|
strategy: "managed-backup-file",
|
|
3247
|
-
destinations: [{ path: `${
|
|
4381
|
+
destinations: [{ path: `${path4}.bak`, label: "managed backup" }]
|
|
3248
4382
|
};
|
|
3249
4383
|
}
|
|
3250
4384
|
function defaultDisclaimers() {
|
|
@@ -3296,7 +4430,7 @@ function planFromItems(id, action, title, summary, items) {
|
|
|
3296
4430
|
};
|
|
3297
4431
|
}
|
|
3298
4432
|
function buildOpenCodeUpdatePlan(_context = { cwd: process.cwd() }) {
|
|
3299
|
-
const
|
|
4433
|
+
const path4 = getExistingConfigPath();
|
|
3300
4434
|
return planFromItems(
|
|
3301
4435
|
"opencode-update-preview",
|
|
3302
4436
|
"update",
|
|
@@ -3308,7 +4442,7 @@ function buildOpenCodeUpdatePlan(_context = { cwd: process.cwd() }) {
|
|
|
3308
4442
|
target: targetForMainConfig(),
|
|
3309
4443
|
state: getOpenCodeStatus().state,
|
|
3310
4444
|
preview: `plugin: ["${EXPECTED_PLUGIN}"]`,
|
|
3311
|
-
backup: defaultBackup(
|
|
4445
|
+
backup: defaultBackup(path4)
|
|
3312
4446
|
}
|
|
3313
4447
|
]
|
|
3314
4448
|
);
|
|
@@ -3475,7 +4609,7 @@ function buildOpenCodeModelPlan(input, _context = { cwd: process.cwd() }) {
|
|
|
3475
4609
|
}
|
|
3476
4610
|
return plan;
|
|
3477
4611
|
}
|
|
3478
|
-
function
|
|
4612
|
+
function rejectPlan3(plan, message, severity = "critical") {
|
|
3479
4613
|
return {
|
|
3480
4614
|
harness: plan.harness,
|
|
3481
4615
|
action: plan.action,
|
|
@@ -3519,27 +4653,27 @@ function ensureLatestPluginEntry() {
|
|
|
3519
4653
|
}
|
|
3520
4654
|
function validateApplyPlan(plan) {
|
|
3521
4655
|
if (plan.harness !== "opencode") {
|
|
3522
|
-
return
|
|
4656
|
+
return rejectPlan3(plan, "Only OpenCode operation plans can be applied.");
|
|
3523
4657
|
}
|
|
3524
4658
|
if (!plan.canApply) {
|
|
3525
|
-
return
|
|
4659
|
+
return rejectPlan3(
|
|
3526
4660
|
plan,
|
|
3527
4661
|
"OpenCode plan cannot be applied because canApply is false."
|
|
3528
4662
|
);
|
|
3529
4663
|
}
|
|
3530
4664
|
if (!["install", "update", "sync", "model-config"].includes(plan.action)) {
|
|
3531
|
-
return
|
|
4665
|
+
return rejectPlan3(
|
|
3532
4666
|
plan,
|
|
3533
4667
|
`Unsupported OpenCode apply action: ${plan.action}.`
|
|
3534
4668
|
);
|
|
3535
4669
|
}
|
|
3536
4670
|
if (plan.items.length === 0) {
|
|
3537
|
-
return
|
|
4671
|
+
return rejectPlan3(plan, "OpenCode plan has no items to apply.");
|
|
3538
4672
|
}
|
|
3539
4673
|
if (plan.items.some(
|
|
3540
4674
|
(item) => !item.title || !item.target.path && item.target.kind !== "skill" || item.state === "drift" || item.state === "unknown"
|
|
3541
4675
|
)) {
|
|
3542
|
-
return
|
|
4676
|
+
return rejectPlan3(
|
|
3543
4677
|
plan,
|
|
3544
4678
|
"OpenCode plan contains malformed or unsafe items."
|
|
3545
4679
|
);
|
|
@@ -3551,7 +4685,7 @@ function applyModelPlan(plan) {
|
|
|
3551
4685
|
for (const item of plan.items) {
|
|
3552
4686
|
const match = /^Set (.+) OpenCode model override$/.exec(item.title);
|
|
3553
4687
|
if (!match) {
|
|
3554
|
-
return
|
|
4688
|
+
return rejectPlan3(
|
|
3555
4689
|
plan,
|
|
3556
4690
|
"OpenCode model plan contains an unrecognized item."
|
|
3557
4691
|
);
|
|
@@ -3560,7 +4694,7 @@ function applyModelPlan(plan) {
|
|
|
3560
4694
|
try {
|
|
3561
4695
|
parsed2 = item.preview ? JSON.parse(item.preview) : {};
|
|
3562
4696
|
} catch {
|
|
3563
|
-
return
|
|
4697
|
+
return rejectPlan3(
|
|
3564
4698
|
plan,
|
|
3565
4699
|
"OpenCode model plan contains malformed preview JSON."
|
|
3566
4700
|
);
|
|
@@ -3568,7 +4702,7 @@ function applyModelPlan(plan) {
|
|
|
3568
4702
|
const role = match[1] ?? "";
|
|
3569
4703
|
const model = parsed2[role]?.model;
|
|
3570
4704
|
if (!ROLE_NAMES.includes(role) || typeof model !== "string") {
|
|
3571
|
-
return
|
|
4705
|
+
return rejectPlan3(
|
|
3572
4706
|
plan,
|
|
3573
4707
|
"OpenCode model plan contains an invalid role or model."
|
|
3574
4708
|
);
|
|
@@ -3576,7 +4710,7 @@ function applyModelPlan(plan) {
|
|
|
3576
4710
|
roleModels.set(role, model);
|
|
3577
4711
|
}
|
|
3578
4712
|
if (roleModels.size === 0) {
|
|
3579
|
-
return
|
|
4713
|
+
return rejectPlan3(
|
|
3580
4714
|
plan,
|
|
3581
4715
|
"OpenCode model plan does not contain any role overrides."
|
|
3582
4716
|
);
|
|
@@ -3612,7 +4746,7 @@ function applyModelPlan(plan) {
|
|
|
3612
4746
|
observed: "agents role overrides updated"
|
|
3613
4747
|
}
|
|
3614
4748
|
],
|
|
3615
|
-
backups:
|
|
4749
|
+
backups: existsSync10(`${targetPath}.bak`) ? [{ path: `${targetPath}.bak`, label: "managed backup" }] : [],
|
|
3616
4750
|
warnings: [],
|
|
3617
4751
|
disclaimers: defaultDisclaimers()
|
|
3618
4752
|
};
|
|
@@ -3621,7 +4755,7 @@ function applyInstallSkills(plan) {
|
|
|
3621
4755
|
for (const skill of RECOMMENDED_SKILLS) {
|
|
3622
4756
|
const result = installRecommendedSkill(skill);
|
|
3623
4757
|
if (result.status === "failed") {
|
|
3624
|
-
return
|
|
4758
|
+
return rejectPlan3(
|
|
3625
4759
|
plan,
|
|
3626
4760
|
`Failed to install recommended OpenCode skill: ${skill.name}.`
|
|
3627
4761
|
);
|
|
@@ -3629,7 +4763,7 @@ function applyInstallSkills(plan) {
|
|
|
3629
4763
|
}
|
|
3630
4764
|
const bundled = installCustomSkills();
|
|
3631
4765
|
if (!bundled.success) {
|
|
3632
|
-
return
|
|
4766
|
+
return rejectPlan3(
|
|
3633
4767
|
plan,
|
|
3634
4768
|
"Failed to install bundled thoth-agents OpenCode skills."
|
|
3635
4769
|
);
|
|
@@ -3641,7 +4775,7 @@ function applyOpenCodePlan(plan) {
|
|
|
3641
4775
|
if (rejection) return rejection;
|
|
3642
4776
|
const status = getOpenCodeStatus();
|
|
3643
4777
|
if (!classifyApplySafety(status.state)) {
|
|
3644
|
-
return
|
|
4778
|
+
return rejectPlan3(
|
|
3645
4779
|
plan,
|
|
3646
4780
|
`OpenCode state is ${status.state}; refusing to apply plan without a safe status.`
|
|
3647
4781
|
);
|
|
@@ -3651,7 +4785,7 @@ function applyOpenCodePlan(plan) {
|
|
|
3651
4785
|
const backups = [];
|
|
3652
4786
|
const pluginResult = ensureLatestPluginEntry();
|
|
3653
4787
|
if (!pluginResult.success) {
|
|
3654
|
-
return
|
|
4788
|
+
return rejectPlan3(
|
|
3655
4789
|
plan,
|
|
3656
4790
|
pluginResult.error ?? "Failed to update OpenCode plugin config."
|
|
3657
4791
|
);
|
|
@@ -3660,7 +4794,7 @@ function applyOpenCodePlan(plan) {
|
|
|
3660
4794
|
...targetForMainConfig("installed"),
|
|
3661
4795
|
observed: `plugin includes ${EXPECTED_PLUGIN}`
|
|
3662
4796
|
});
|
|
3663
|
-
if (
|
|
4797
|
+
if (existsSync10(`${pluginResult.configPath}.bak`)) {
|
|
3664
4798
|
backups.push({
|
|
3665
4799
|
path: `${pluginResult.configPath}.bak`,
|
|
3666
4800
|
label: "OpenCode config backup"
|
|
@@ -3669,7 +4803,7 @@ function applyOpenCodePlan(plan) {
|
|
|
3669
4803
|
if (plan.action === "sync" || plan.action === "install") {
|
|
3670
4804
|
const defaultAgentResult = disableDefaultAgents();
|
|
3671
4805
|
if (!defaultAgentResult.success) {
|
|
3672
|
-
return
|
|
4806
|
+
return rejectPlan3(
|
|
3673
4807
|
plan,
|
|
3674
4808
|
defaultAgentResult.error ?? "Failed to disable OpenCode default agents."
|
|
3675
4809
|
);
|
|
@@ -3686,7 +4820,7 @@ function applyOpenCodePlan(plan) {
|
|
|
3686
4820
|
getExistingLiteConfigPath()
|
|
3687
4821
|
);
|
|
3688
4822
|
if (!liteResult.success) {
|
|
3689
|
-
return
|
|
4823
|
+
return rejectPlan3(
|
|
3690
4824
|
plan,
|
|
3691
4825
|
liteResult.error ?? "Failed to write thoth-agents config."
|
|
3692
4826
|
);
|
|
@@ -3695,7 +4829,7 @@ function applyOpenCodePlan(plan) {
|
|
|
3695
4829
|
...targetForLiteConfig("installed"),
|
|
3696
4830
|
observed: "seven-agent roster written"
|
|
3697
4831
|
});
|
|
3698
|
-
if (
|
|
4832
|
+
if (existsSync10(`${liteResult.configPath}.bak`)) {
|
|
3699
4833
|
backups.push({
|
|
3700
4834
|
path: `${liteResult.configPath}.bak`,
|
|
3701
4835
|
label: "thoth-agents config backup"
|
|
@@ -3727,7 +4861,8 @@ function applyOpenCodePlan(plan) {
|
|
|
3727
4861
|
// src/cli/operations/index.ts
|
|
3728
4862
|
var OPERATION_HARNESSES = {
|
|
3729
4863
|
opencode: opencodeOperationAdapter,
|
|
3730
|
-
codex: codexOperationAdapter
|
|
4864
|
+
codex: codexOperationAdapter,
|
|
4865
|
+
claude: claudeCodeOperationAdapter
|
|
3731
4866
|
};
|
|
3732
4867
|
var SUPPORTED_OPERATION_HARNESSES = Object.keys(
|
|
3733
4868
|
OPERATION_HARNESSES
|
|
@@ -3742,14 +4877,27 @@ function getOperationHarness(harness) {
|
|
|
3742
4877
|
}
|
|
3743
4878
|
|
|
3744
4879
|
export {
|
|
4880
|
+
claudeCodeAdapter,
|
|
3745
4881
|
codexAdapter,
|
|
3746
4882
|
CODEX_ROLE_NAMES,
|
|
3747
4883
|
parseRoleTomlModel,
|
|
3748
4884
|
buildCodexSetupPlan,
|
|
3749
4885
|
formatCodexSetupPlan,
|
|
3750
4886
|
applyCodexSetup,
|
|
4887
|
+
CLAUDE_CODE_ROLE_NAMES,
|
|
4888
|
+
parseSubagentModel,
|
|
4889
|
+
buildClaudeCodeSetupPlan,
|
|
4890
|
+
applyClaudeCodeSetup,
|
|
4891
|
+
formatClaudeCodeSetupPlan,
|
|
3751
4892
|
RECOMMENDED_SKILLS,
|
|
3752
4893
|
installRecommendedSkill,
|
|
4894
|
+
getClaudeCodeStatus,
|
|
4895
|
+
buildClaudeCodeInstallPlan,
|
|
4896
|
+
buildClaudeCodeUpdatePlan,
|
|
4897
|
+
buildClaudeCodeSyncPlan,
|
|
4898
|
+
buildClaudeCodeModelPlan,
|
|
4899
|
+
applyClaudeCodePlan,
|
|
4900
|
+
defaultClaudeCodeModelRoles,
|
|
3753
4901
|
getCodexStatus,
|
|
3754
4902
|
buildCodexUpdatePlan,
|
|
3755
4903
|
buildCodexSyncPlan,
|