thoth-agents 0.11.0 → 0.13.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.
Files changed (95) hide show
  1. package/README.md +16 -5
  2. package/dist/agents/agent-config.d.ts +13 -0
  3. package/dist/agents/index.d.ts +1 -1
  4. package/dist/agents/orchestrator.d.ts +1 -1
  5. package/dist/{chunk-I5TXGNSG.js → chunk-5MYXVRZZ.js} +634 -366
  6. package/dist/chunk-6O4GKDS7.js +207 -0
  7. package/dist/{chunk-RWQPMWEM.js → chunk-JLU3FSBT.js} +17 -2
  8. package/dist/chunk-MIDGZE34.js +107 -0
  9. package/dist/{chunk-HACUJFKI.js → chunk-NOLDFT7O.js} +4 -230
  10. package/dist/{chunk-5WPP6IY7.js → chunk-VVYAQTYE.js} +2 -2
  11. package/dist/{chunk-DNPKVFOB.js → chunk-YCWGTDFE.js} +604 -315
  12. package/dist/cli/config-io.d.ts +2 -2
  13. package/dist/cli/index.js +94 -41
  14. package/dist/cli/install.d.ts +1 -0
  15. package/dist/cli/opencode-profiles.d.ts +10 -0
  16. package/dist/cli/opencode-skill-consent.d.ts +5 -0
  17. package/dist/cli/operations/opencode.d.ts +2 -1
  18. package/dist/cli/operations/types.d.ts +1 -0
  19. package/dist/cli/owned-skills.d.ts +16 -0
  20. package/dist/cli/paths.d.ts +1 -1
  21. package/dist/cli/providers.d.ts +1 -1
  22. package/dist/cli/system.d.ts +3 -1
  23. package/dist/cli/thoth-mem-install.d.ts +10 -10
  24. package/dist/cli/tui/index.js +53 -5
  25. package/dist/cli/tui/operations.d.ts +2 -0
  26. package/dist/cli/types.d.ts +2 -4
  27. package/dist/config/loader.d.ts +2 -5
  28. package/dist/config/schema.d.ts +0 -53
  29. package/dist/harness/adapters/opencode.d.ts +1 -1
  30. package/dist/hooks/delegate-task-retry/hook.d.ts +8 -8
  31. package/dist/hooks/foreground-fallback/index.d.ts +14 -69
  32. package/dist/hooks/index.d.ts +0 -2
  33. package/dist/hooks/json-error-recovery/hook.d.ts +2 -15
  34. package/dist/hooks/oracle-gate-verdict/hook.d.ts +4 -11
  35. package/dist/index.d.ts +7 -4
  36. package/dist/index.js +1603 -1408
  37. package/dist/opencode/agents.d.ts +3 -0
  38. package/dist/opencode/command.d.ts +3 -0
  39. package/dist/opencode/context.d.ts +36 -0
  40. package/dist/opencode/delegation-hooks.d.ts +3 -0
  41. package/dist/opencode/mcp.d.ts +3 -0
  42. package/dist/opencode/permission-rules.d.ts +9 -0
  43. package/dist/opencode/permissions.d.ts +3 -0
  44. package/dist/opencode/profile-rpc-handlers.d.ts +3 -0
  45. package/dist/opencode/profile-rpc.d.ts +688 -0
  46. package/dist/opencode/profile-runtime.d.ts +3 -0
  47. package/dist/opencode/profile-sessions.d.ts +18 -0
  48. package/dist/opencode/profiles/editor.d.ts +44 -0
  49. package/dist/opencode/profiles/index.d.ts +18 -0
  50. package/dist/opencode/profiles/resolver.d.ts +12 -0
  51. package/dist/opencode/profiles/store.d.ts +63 -0
  52. package/dist/opencode/profiles/types.d.ts +26 -0
  53. package/dist/opencode/session-hooks.d.ts +3 -0
  54. package/dist/opencode/session-state.d.ts +39 -0
  55. package/dist/opencode/skills.d.ts +3 -0
  56. package/dist/opencode/tool-overrides/evaluator.d.ts +13 -0
  57. package/dist/opencode/tool-overrides/index.d.ts +5 -0
  58. package/dist/opencode/tool-overrides/store.d.ts +38 -0
  59. package/dist/opencode/tool-overrides/types.d.ts +32 -0
  60. package/dist/opencode/tool-overrides/validation.d.ts +5 -0
  61. package/dist/opencode/tool-overrides/wildcard.d.ts +3 -0
  62. package/dist/opencode/tool-rpc-handlers.d.ts +3 -0
  63. package/dist/opencode/tool-rpc.d.ts +428 -0
  64. package/dist/opencode/tools.d.ts +3 -0
  65. package/dist/pi.js +3 -3
  66. package/dist/tools/ast-grep/cli.d.ts +1 -0
  67. package/dist/tools/ast-grep/index.d.ts +108 -2
  68. package/dist/tools/ast-grep/tools.d.ts +107 -3
  69. package/dist/tools/define-tool.d.ts +23 -0
  70. package/dist/tools/lsp/tools.d.ts +97 -5
  71. package/dist/tui/fixtures.d.ts +2 -0
  72. package/dist/tui/flow.d.ts +16 -0
  73. package/dist/tui/index.d.ts +7 -0
  74. package/dist/tui/index.js +1384 -0
  75. package/dist/tui/model.d.ts +84 -0
  76. package/dist/tui/tools/fixtures.d.ts +3 -0
  77. package/dist/tui/tools/flow.d.ts +11 -0
  78. package/dist/tui/tools/model.d.ts +97 -0
  79. package/dist/utils/index.d.ts +0 -1
  80. package/package.json +18 -3
  81. package/plugin/.claude-plugin/plugin.json +1 -1
  82. package/plugin/.codex-plugin/plugin.json +1 -1
  83. package/plugin/skills/thoth-sdd/SKILL.md +8 -2
  84. package/plugin/skills/thoth-sdd/scripts/validate-result.mjs +28 -2
  85. package/skills/thoth-sdd/SKILL.md +8 -2
  86. package/skills/thoth-sdd/scripts/validate-result.mjs +28 -2
  87. package/thoth-agents.schema.json +0 -96
  88. package/dist/chunk-62L3ORXN.js +0 -30
  89. package/dist/delegation/project-id.d.ts +0 -10
  90. package/dist/hooks/auto-update-checker/checker.d.ts +0 -23
  91. package/dist/hooks/auto-update-checker/constants.d.ts +0 -11
  92. package/dist/hooks/auto-update-checker/index.d.ts +0 -17
  93. package/dist/hooks/auto-update-checker/types.d.ts +0 -22
  94. package/dist/utils/tmux-session-manager.d.ts +0 -63
  95. package/dist/utils/tmux.d.ts +0 -32
@@ -19,19 +19,107 @@ function findPackageRoot(startDir) {
19
19
  }
20
20
  }
21
21
 
22
- // src/config/loader.ts
23
- import * as fs from "fs";
24
- import * as path from "path";
22
+ // src/config/schema.ts
23
+ import { z } from "zod";
24
+ var AGENT_NAMES = [
25
+ "orchestrator",
26
+ "oracle",
27
+ "designer",
28
+ "explorer",
29
+ "librarian",
30
+ "worker"
31
+ ];
32
+ var FALLBACK_AGENT_NAMES = [...AGENT_NAMES];
33
+ var MANUAL_AGENT_NAMES = [...AGENT_NAMES];
34
+ var ProviderModelIdSchema = z.string().regex(
35
+ /^[^/\s]+\/[^\s]+$/,
36
+ "Expected provider/model format (provider/.../model)"
37
+ );
38
+ var ManualAgentPlanSchema = z.object({
39
+ primary: ProviderModelIdSchema,
40
+ fallback1: ProviderModelIdSchema,
41
+ fallback2: ProviderModelIdSchema,
42
+ fallback3: ProviderModelIdSchema
43
+ }).superRefine((value, ctx) => {
44
+ const unique = /* @__PURE__ */ new Set([
45
+ value.primary,
46
+ value.fallback1,
47
+ value.fallback2,
48
+ value.fallback3
49
+ ]);
50
+ if (unique.size !== 4) {
51
+ ctx.addIssue({
52
+ code: z.ZodIssueCode.custom,
53
+ message: "primary and fallbacks must be unique per agent"
54
+ });
55
+ }
56
+ });
57
+ var ManualPlanSchema = z.object({
58
+ orchestrator: ManualAgentPlanSchema,
59
+ oracle: ManualAgentPlanSchema,
60
+ designer: ManualAgentPlanSchema,
61
+ explorer: ManualAgentPlanSchema,
62
+ librarian: ManualAgentPlanSchema,
63
+ worker: ManualAgentPlanSchema
64
+ }).strict();
65
+ var AgentModelChainSchema = z.array(z.string()).min(1);
66
+ var SupportedAgentRecordKeySchema = z.string().regex(/^(?!quick$|deep$).+$/, "Unsupported removed agent role");
67
+ var FallbackChainsSchema = z.record(
68
+ SupportedAgentRecordKeySchema,
69
+ AgentModelChainSchema
70
+ );
71
+ var AgentOverrideConfigSchema = z.object({
72
+ model: z.union([
73
+ z.string(),
74
+ z.array(
75
+ z.union([
76
+ z.string(),
77
+ z.object({
78
+ id: z.string(),
79
+ variant: z.string().optional()
80
+ })
81
+ ])
82
+ )
83
+ ]).optional(),
84
+ /** Pi specialist catalog filter; omitted discovers all, [] discovers none. */
85
+ skills: z.array(z.string().trim().min(1)).optional(),
86
+ temperature: z.number().min(0).max(2).optional(),
87
+ steps: z.number().int().min(1).optional(),
88
+ variant: z.string().optional().catch(void 0)
89
+ });
90
+ var AgentNameSchema = z.enum(AGENT_NAMES);
91
+ var McpNameSchema = z.enum(["exa", "context7", "grep_app"]);
92
+ var CodexGenerationConfigSchema = z.object({
93
+ enabled: z.boolean().default(false),
94
+ outputRoot: z.string().optional(),
95
+ dryRun: z.boolean().default(true)
96
+ });
97
+ var ClaudeCodeGenerationConfigSchema = z.object({
98
+ enabled: z.boolean().default(false),
99
+ outputRoot: z.string().optional(),
100
+ dryRun: z.boolean().default(true)
101
+ });
102
+ var FailoverConfigSchema = z.object({
103
+ enabled: z.boolean().default(true),
104
+ timeoutMs: z.number().min(0).default(15e3),
105
+ retryDelayMs: z.number().min(0).default(500),
106
+ chains: FallbackChainsSchema.default({})
107
+ });
108
+ var PluginConfigSchema = z.object({
109
+ $schema: z.string().optional(),
110
+ setDefaultAgent: z.boolean().optional(),
111
+ scoringEngineVersion: z.enum(["v1", "v2-shadow", "v2"]).optional(),
112
+ balanceProviderUsage: z.boolean().optional(),
113
+ manualPlan: ManualPlanSchema.optional(),
114
+ agents: z.record(SupportedAgentRecordKeySchema, AgentOverrideConfigSchema).optional(),
115
+ disabled_mcps: z.array(z.string()).optional(),
116
+ fallback: FailoverConfigSchema.optional(),
117
+ codex: CodexGenerationConfigSchema.optional(),
118
+ claudeCode: ClaudeCodeGenerationConfigSchema.optional()
119
+ });
25
120
 
26
- // src/cli/config-io.ts
27
- import {
28
- copyFileSync,
29
- existsSync as existsSync3,
30
- readFileSync as readFileSync2,
31
- renameSync,
32
- statSync,
33
- writeFileSync
34
- } from "fs";
121
+ // src/opencode/profiles/types.ts
122
+ var PROFILE_ROLES = AgentNameSchema.options;
35
123
 
36
124
  // src/config/constants.ts
37
125
  var SUBAGENT_NAMES = [
@@ -99,10 +187,305 @@ function getDefaultOpenCodeVariant(name) {
99
187
  }
100
188
  return CONFIRMED_OPENAI_SUBAGENT_PRESET[name].effort;
101
189
  }
102
- var POLL_INTERVAL_BACKGROUND_MS = 2e3;
103
190
  var DEFAULT_TIMEOUT_MS = 2 * 60 * 1e3;
104
191
  var MAX_POLL_TIME_MS = 5 * 60 * 1e3;
105
192
 
193
+ // src/opencode/profiles/resolver.ts
194
+ function configuredModelChain(config) {
195
+ const models = config?.model;
196
+ return (typeof models === "string" ? [models] : models ?? []).map(
197
+ (entry) => {
198
+ const value = typeof entry === "string" ? { id: entry } : { ...entry };
199
+ const variant = Array.isArray(models) ? value.variant : config?.variant;
200
+ return { ...value, ...variant ? { variant } : {} };
201
+ }
202
+ );
203
+ }
204
+ function resolveModelPair(config, inherited = {}) {
205
+ const first = configuredModelChain(config)[0];
206
+ const model = first?.id ?? inherited.model;
207
+ const variant = config?.model !== void 0 ? first?.variant : config?.variant ?? inherited.variant;
208
+ return { ...model ? { model } : {}, ...variant ? { variant } : {} };
209
+ }
210
+ function resolveRoleProfile(role, profile, roster) {
211
+ return resolveModelPair(profile.agents[role], resolveModelPair(roster[role]));
212
+ }
213
+ function defaultModelProfile(roster) {
214
+ return {
215
+ agents: Object.fromEntries(
216
+ PROFILE_ROLES.map((role) => [
217
+ role,
218
+ resolveRoleProfile(role, { agents: {} }, roster)
219
+ ])
220
+ )
221
+ };
222
+ }
223
+ function mergeProfileModelChain(profile, configured) {
224
+ const entries = profile.model ? [
225
+ {
226
+ id: profile.model,
227
+ ...profile.variant ? { variant: profile.variant } : {}
228
+ },
229
+ ...configured
230
+ ] : configured;
231
+ const seen = /* @__PURE__ */ new Set();
232
+ return entries.filter(({ id }) => {
233
+ if (seen.has(id)) return false;
234
+ seen.add(id);
235
+ return true;
236
+ }).map((entry) => ({ ...entry }));
237
+ }
238
+ function formatModelString(profile) {
239
+ return profile.model ? `${profile.model}${profile.variant ? `#${profile.variant}` : ""}` : void 0;
240
+ }
241
+
242
+ // src/opencode/profiles/store.ts
243
+ import { randomUUID } from "crypto";
244
+ import fs from "fs";
245
+ import os from "os";
246
+ import path from "path";
247
+ import { isDeepStrictEqual } from "util";
248
+ var DEFAULT_PROFILE = "default";
249
+ function modelProfilePaths(options) {
250
+ if (options.paths) return { ...options.paths };
251
+ const env = options.env ?? process.env;
252
+ const configDir = options.configDir ?? (env.OPENCODE_CONFIG_DIR?.trim() || path.join(
253
+ env.XDG_CONFIG_HOME || path.join(options.homeDir ?? os.homedir(), ".config"),
254
+ "opencode"
255
+ ));
256
+ return {
257
+ global: path.join(configDir, "thoth", "profiles.json"),
258
+ project: path.join(options.cwd, ".opencode", "thoth", "profiles.json")
259
+ };
260
+ }
261
+ function object(value) {
262
+ return value !== null && typeof value === "object" && !Array.isArray(value);
263
+ }
264
+ function validateProfileName(value, writable = false) {
265
+ if (typeof value !== "string" || !value.trim())
266
+ throw new Error("Profile name must be non-empty");
267
+ const name = value.trim();
268
+ if (writable && name === DEFAULT_PROFILE)
269
+ throw new Error("default is a reserved read-only virtual profile");
270
+ return name;
271
+ }
272
+ function parseRoleProfile(value) {
273
+ if (!object(value)) throw new Error("Role override must be an object");
274
+ if (value.model !== void 0 && (typeof value.model !== "string" || !/^[^/\s#]+\/[^\s#]+$/.test(value.model)))
275
+ throw new Error("Invalid model; expected provider/id without #variant");
276
+ if (value.variant !== void 0 && (typeof value.variant !== "string" || !/^[^\s#]+$/.test(value.variant)))
277
+ throw new Error(
278
+ "Invalid variant; expected a non-empty string without whitespace or #"
279
+ );
280
+ return {
281
+ ...value.model !== void 0 ? { model: value.model } : {},
282
+ ...value.variant !== void 0 ? { variant: value.variant } : {}
283
+ };
284
+ }
285
+ function parseNamedModelProfile(value) {
286
+ if (!object(value) || !object(value.agents))
287
+ throw new Error("Profile must contain an agents object");
288
+ const agents = {};
289
+ for (const [role, override] of Object.entries(value.agents)) {
290
+ const canonical = PROFILE_ROLES.find((name) => name === role);
291
+ if (!canonical) throw new Error(`Unknown profile role: ${role}`);
292
+ agents[canonical] = parseRoleProfile(override);
293
+ }
294
+ return { agents };
295
+ }
296
+ function parseModelProfilesDocument(raw) {
297
+ if (!object(raw) || raw.version !== 1 || !object(raw.profiles))
298
+ throw new Error("Expected version 1 and a profiles object");
299
+ const profiles = /* @__PURE__ */ Object.create(null);
300
+ for (const [key, value] of Object.entries(raw.profiles)) {
301
+ const name = validateProfileName(key, true);
302
+ if (Object.hasOwn(profiles, name))
303
+ throw new Error(`Duplicate profile: ${name}`);
304
+ profiles[name] = parseNamedModelProfile(value);
305
+ }
306
+ return {
307
+ version: 1,
308
+ ...raw.active !== void 0 ? { active: validateProfileName(raw.active) } : {},
309
+ profiles
310
+ };
311
+ }
312
+ var emptyDocument = () => ({
313
+ version: 1,
314
+ profiles: /* @__PURE__ */ Object.create(null)
315
+ });
316
+ var ProfileConflictError = class extends Error {
317
+ constructor(scope, profileName) {
318
+ super(
319
+ `Profile changed on disk: ${scope}:${profileName}; reload before saving.`
320
+ );
321
+ this.scope = scope;
322
+ this.profileName = profileName;
323
+ this.name = "ProfileConflictError";
324
+ }
325
+ scope;
326
+ profileName;
327
+ code = "PROFILE_CONFLICT";
328
+ };
329
+ var ModelProfileStore = class {
330
+ constructor(options) {
331
+ this.options = options;
332
+ this.paths = modelProfilePaths(options);
333
+ }
334
+ options;
335
+ paths;
336
+ defaultProfile() {
337
+ return defaultModelProfile(this.options.roster ?? {});
338
+ }
339
+ load(scope) {
340
+ try {
341
+ return parseModelProfilesDocument(
342
+ JSON.parse(fs.readFileSync(this.paths[scope], "utf8"))
343
+ );
344
+ } catch (error) {
345
+ if (error.code === "ENOENT")
346
+ return emptyDocument();
347
+ throw new Error(
348
+ `Cannot read model profiles ${this.paths[scope]}: ${String(error)}`,
349
+ { cause: error }
350
+ );
351
+ }
352
+ }
353
+ read(scope, warnings = []) {
354
+ try {
355
+ return this.load(scope);
356
+ } catch (error) {
357
+ const warning = String(error);
358
+ warnings.push(warning);
359
+ this.options.warn?.(warning);
360
+ return emptyDocument();
361
+ }
362
+ }
363
+ lookup(name, scope) {
364
+ const key = validateProfileName(name);
365
+ if (key === DEFAULT_PROFILE) return { profile: this.defaultProfile() };
366
+ for (const source of scope ? [scope] : ["project", "global"]) {
367
+ const profiles = this.read(source).profiles;
368
+ if (Object.hasOwn(profiles, key))
369
+ return { profile: profiles[key], scope: source };
370
+ }
371
+ return void 0;
372
+ }
373
+ write(scope, document) {
374
+ const validated = parseModelProfilesDocument(document);
375
+ const file = this.paths[scope];
376
+ fs.mkdirSync(path.dirname(file), { recursive: true });
377
+ const temp = `${file}.${randomUUID()}.tmp`;
378
+ try {
379
+ fs.writeFileSync(temp, `${JSON.stringify(validated, null, 2)}
380
+ `, {
381
+ encoding: "utf8",
382
+ flag: "wx"
383
+ });
384
+ fs.renameSync(temp, file);
385
+ } finally {
386
+ if (fs.existsSync(temp)) fs.unlinkSync(temp);
387
+ }
388
+ }
389
+ /** Re-read before writing. An expected baseline enables same-profile conflict detection. */
390
+ save(scope, name, value, options = {}) {
391
+ const key = validateProfileName(name, true);
392
+ const next = parseNamedModelProfile(value);
393
+ const document = this.load(scope);
394
+ const existing = Object.hasOwn(document.profiles, key) ? document.profiles[key] : null;
395
+ if (Object.hasOwn(options, "expected") && !isDeepStrictEqual(existing, options.expected))
396
+ throw new ProfileConflictError(scope, key);
397
+ document.profiles[key] = next;
398
+ this.write(scope, document);
399
+ }
400
+ create(scope, name, value) {
401
+ const key = validateProfileName(name, true);
402
+ if (Object.hasOwn(this.load(scope).profiles, key))
403
+ throw new Error(`Profile already exists: ${key}`);
404
+ this.save(scope, key, value, { expected: null });
405
+ }
406
+ /** Pins that actually resolved to a global profile follow rename/delete. */
407
+ projectPointsToGlobal(name) {
408
+ const project = this.load("project");
409
+ return project.active === name && !Object.hasOwn(project.profiles, name);
410
+ }
411
+ rename(scope, source, target) {
412
+ const from = validateProfileName(source, true);
413
+ const to = validateProfileName(target, true);
414
+ const document = this.load(scope);
415
+ if (!Object.hasOwn(document.profiles, from))
416
+ throw new Error(`Profile not found: ${from}`);
417
+ if (Object.hasOwn(document.profiles, to))
418
+ throw new Error(`Profile already exists: ${to}`);
419
+ const followPin = scope === "global" && this.projectPointsToGlobal(from);
420
+ document.profiles[to] = document.profiles[from];
421
+ delete document.profiles[from];
422
+ if (document.active === from) document.active = to;
423
+ this.write(scope, document);
424
+ if (followPin) this.setProjectPin(to);
425
+ }
426
+ delete(scope, name) {
427
+ const key = validateProfileName(name, true);
428
+ const document = this.load(scope);
429
+ if (!Object.hasOwn(document.profiles, key))
430
+ throw new Error(`Profile not found: ${key}`);
431
+ const clearPin = scope === "global" && this.projectPointsToGlobal(key);
432
+ delete document.profiles[key];
433
+ const clearedActive = document.active === key;
434
+ if (clearedActive) delete document.active;
435
+ this.write(scope, document);
436
+ if (clearPin) this.setProjectPin();
437
+ return { clearedActive };
438
+ }
439
+ setActive(scope, name) {
440
+ const document = this.load(scope);
441
+ if (name === void 0) delete document.active;
442
+ else {
443
+ const key = validateProfileName(name);
444
+ if (key !== DEFAULT_PROFILE && !(scope === "global" ? Object.hasOwn(document.profiles, key) : this.lookup(key)))
445
+ throw new Error(`Profile not found: ${key}`);
446
+ document.active = key;
447
+ }
448
+ this.write(scope, document);
449
+ }
450
+ setGlobalActive(name) {
451
+ this.setActive("global", name);
452
+ }
453
+ setProjectPin(name) {
454
+ this.setActive("project", name);
455
+ }
456
+ resolve(sessionSelection) {
457
+ const warnings = [];
458
+ const documents = {
459
+ global: this.read("global", warnings),
460
+ project: this.read("project", warnings)
461
+ };
462
+ for (const [level, name] of [
463
+ ["session", sessionSelection],
464
+ ["project", documents.project.active],
465
+ ["global", documents.global.active]
466
+ ]) {
467
+ if (!name) continue;
468
+ if (name === DEFAULT_PROFILE)
469
+ return { name, profile: this.defaultProfile(), warnings };
470
+ const scope = Object.hasOwn(documents.project.profiles, name) ? "project" : "global";
471
+ if (Object.hasOwn(documents[scope].profiles, name) && (level !== "global" || Object.hasOwn(documents.global.profiles, name)))
472
+ return {
473
+ name,
474
+ scope,
475
+ profile: documents[scope].profiles[name],
476
+ warnings
477
+ };
478
+ const warning = `Missing ${level} model profile "${name}"; falling back.`;
479
+ warnings.push(warning);
480
+ this.options.warn?.(warning);
481
+ }
482
+ return { name: DEFAULT_PROFILE, profile: this.defaultProfile(), warnings };
483
+ }
484
+ };
485
+
486
+ // src/opencode/profiles/editor.ts
487
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
488
+
106
489
  // src/cli/package-version.ts
107
490
  import { readFileSync } from "fs";
108
491
  import { dirname as dirname2, join as join2 } from "path";
@@ -195,27 +578,41 @@ function resolveExecutingPackageVersion(options = {}) {
195
578
  return { ok: true, version, packageRoot };
196
579
  }
197
580
 
581
+ // src/config/loader.ts
582
+ import * as fs2 from "fs";
583
+ import * as path2 from "path";
584
+
585
+ // src/cli/config-io.ts
586
+ import {
587
+ copyFileSync,
588
+ existsSync as existsSync3,
589
+ readFileSync as readFileSync2,
590
+ renameSync,
591
+ statSync,
592
+ writeFileSync
593
+ } from "fs";
594
+
198
595
  // src/cli/paths.ts
199
596
  import { existsSync as existsSync2, mkdirSync } from "fs";
200
597
  import { homedir } from "os";
201
598
  import { dirname as dirname3, join as join3 } from "path";
202
- function getDefaultOpenCodeConfigDir() {
203
- const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join3(homedir(), ".config");
599
+ function getDefaultOpenCodeConfigDir(homeDir) {
600
+ const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join3(homeDir, ".config");
204
601
  return join3(userConfigDir, "opencode");
205
602
  }
206
603
  function getCustomOpenCodeConfigDir() {
207
604
  const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
208
605
  return configDir || void 0;
209
606
  }
210
- function getConfigDir() {
607
+ function getConfigDir(homeDir = homedir()) {
211
608
  const customConfigDir = getCustomOpenCodeConfigDir();
212
609
  if (customConfigDir) {
213
610
  return customConfigDir;
214
611
  }
215
- return getDefaultOpenCodeConfigDir();
612
+ return getDefaultOpenCodeConfigDir(homeDir);
216
613
  }
217
614
  function getOpenCodeConfigPaths() {
218
- const configDir = getDefaultOpenCodeConfigDir();
615
+ const configDir = getConfigDir();
219
616
  return [join3(configDir, "opencode.json"), join3(configDir, "opencode.jsonc")];
220
617
  }
221
618
  function getConfigJson() {
@@ -260,130 +657,6 @@ function ensureOpenCodeConfigDir() {
260
657
  }
261
658
  }
262
659
 
263
- // src/config/schema.ts
264
- import { z } from "zod";
265
- var AGENT_NAMES = [
266
- "orchestrator",
267
- "oracle",
268
- "designer",
269
- "explorer",
270
- "librarian",
271
- "worker"
272
- ];
273
- var FALLBACK_AGENT_NAMES = [...AGENT_NAMES];
274
- var MANUAL_AGENT_NAMES = [...AGENT_NAMES];
275
- var ProviderModelIdSchema = z.string().regex(
276
- /^[^/\s]+\/[^\s]+$/,
277
- "Expected provider/model format (provider/.../model)"
278
- );
279
- var ManualAgentPlanSchema = z.object({
280
- primary: ProviderModelIdSchema,
281
- fallback1: ProviderModelIdSchema,
282
- fallback2: ProviderModelIdSchema,
283
- fallback3: ProviderModelIdSchema
284
- }).superRefine((value, ctx) => {
285
- const unique = /* @__PURE__ */ new Set([
286
- value.primary,
287
- value.fallback1,
288
- value.fallback2,
289
- value.fallback3
290
- ]);
291
- if (unique.size !== 4) {
292
- ctx.addIssue({
293
- code: z.ZodIssueCode.custom,
294
- message: "primary and fallbacks must be unique per agent"
295
- });
296
- }
297
- });
298
- var ManualPlanSchema = z.object({
299
- orchestrator: ManualAgentPlanSchema,
300
- oracle: ManualAgentPlanSchema,
301
- designer: ManualAgentPlanSchema,
302
- explorer: ManualAgentPlanSchema,
303
- librarian: ManualAgentPlanSchema,
304
- worker: ManualAgentPlanSchema
305
- }).strict();
306
- var AgentModelChainSchema = z.array(z.string()).min(1);
307
- var SupportedAgentRecordKeySchema = z.string().regex(/^(?!quick$|deep$).+$/, "Unsupported removed agent role");
308
- var FallbackChainsSchema = z.record(
309
- SupportedAgentRecordKeySchema,
310
- AgentModelChainSchema
311
- );
312
- var AgentOverrideConfigSchema = z.object({
313
- model: z.union([
314
- z.string(),
315
- z.array(
316
- z.union([
317
- z.string(),
318
- z.object({
319
- id: z.string(),
320
- variant: z.string().optional()
321
- })
322
- ])
323
- )
324
- ]).optional(),
325
- /** Pi specialist catalog filter; omitted discovers all, [] discovers none. */
326
- skills: z.array(z.string().trim().min(1)).optional(),
327
- temperature: z.number().min(0).max(2).optional(),
328
- steps: z.number().int().min(1).optional(),
329
- variant: z.string().optional().catch(void 0)
330
- });
331
- var TmuxLayoutSchema = z.enum([
332
- "main-horizontal",
333
- // Main pane on top, agents stacked below
334
- "main-vertical",
335
- // Main pane on left, agents stacked on right
336
- "tiled",
337
- // All panes equal size grid
338
- "even-horizontal",
339
- // All panes side by side
340
- "even-vertical"
341
- // All panes stacked vertically
342
- ]);
343
- var TmuxConfigSchema = z.object({
344
- enabled: z.boolean().default(false),
345
- layout: TmuxLayoutSchema.default("main-vertical"),
346
- main_pane_size: z.number().min(20).max(80).default(60)
347
- // percentage for main pane
348
- });
349
- var PresetSchema = z.record(
350
- SupportedAgentRecordKeySchema,
351
- AgentOverrideConfigSchema
352
- );
353
- var AgentNameSchema = z.enum(AGENT_NAMES);
354
- var McpNameSchema = z.enum(["exa", "context7", "grep_app"]);
355
- var CodexGenerationConfigSchema = z.object({
356
- enabled: z.boolean().default(false),
357
- outputRoot: z.string().optional(),
358
- dryRun: z.boolean().default(true)
359
- });
360
- var ClaudeCodeGenerationConfigSchema = z.object({
361
- enabled: z.boolean().default(false),
362
- outputRoot: z.string().optional(),
363
- dryRun: z.boolean().default(true)
364
- });
365
- var FailoverConfigSchema = z.object({
366
- enabled: z.boolean().default(true),
367
- timeoutMs: z.number().min(0).default(15e3),
368
- retryDelayMs: z.number().min(0).default(500),
369
- chains: FallbackChainsSchema.default({})
370
- });
371
- var PluginConfigSchema = z.object({
372
- $schema: z.string().optional(),
373
- preset: z.string().optional(),
374
- setDefaultAgent: z.boolean().optional(),
375
- scoringEngineVersion: z.enum(["v1", "v2-shadow", "v2"]).optional(),
376
- balanceProviderUsage: z.boolean().optional(),
377
- manualPlan: ManualPlanSchema.optional(),
378
- presets: z.record(z.string(), PresetSchema).optional(),
379
- agents: z.record(SupportedAgentRecordKeySchema, AgentOverrideConfigSchema).optional(),
380
- disabled_mcps: z.array(z.string()).optional(),
381
- tmux: TmuxConfigSchema.optional(),
382
- fallback: FailoverConfigSchema.optional(),
383
- codex: CodexGenerationConfigSchema.optional(),
384
- claudeCode: ClaudeCodeGenerationConfigSchema.optional()
385
- });
386
-
387
660
  // src/config/utils.ts
388
661
  function getAgentOverride(config, name) {
389
662
  return config?.agents?.[name];
@@ -418,36 +691,75 @@ function buildOpenAIModelMappings() {
418
691
  var MODEL_MAPPINGS = {
419
692
  openai: buildOpenAIModelMappings()
420
693
  };
421
- function generateLiteConfig(installConfig) {
422
- const config = {
423
- $schema: THOTH_AGENTS_CONFIG_SCHEMA_URL,
424
- preset: "openai",
425
- presets: {}
426
- };
427
- const createAgentConfig = (modelInfo) => {
428
- return {
429
- model: modelInfo.model,
430
- variant: modelInfo.variant
431
- };
694
+ function generateLiteConfig(_installConfig) {
695
+ return { $schema: THOTH_AGENTS_CONFIG_SCHEMA_URL };
696
+ }
697
+
698
+ // src/cli/opencode-profiles.ts
699
+ function openCodeProfileStore(roster) {
700
+ return new ModelProfileStore({
701
+ cwd: process.cwd(),
702
+ configDir: getConfigDir(),
703
+ roster
704
+ });
705
+ }
706
+ function activeOpenCodeProfile() {
707
+ const store = openCodeProfileStore();
708
+ const document = store.read("global");
709
+ return {
710
+ name: document.active,
711
+ profile: document.active ? store.lookup(document.active, "global")?.profile : void 0
432
712
  };
433
- const buildPreset = (mappingName) => {
434
- const mapping = MODEL_MAPPINGS[mappingName];
435
- return Object.fromEntries(
436
- Object.entries(mapping).map(([agentName, modelInfo]) => [
437
- agentName,
438
- createAgentConfig(modelInfo)
713
+ }
714
+ function effectiveOpenCodeProfile(config) {
715
+ const root = config?.agents ?? {};
716
+ const roster = Object.fromEntries(
717
+ PROFILE_ROLES.map((role) => [
718
+ role,
719
+ resolveModelPair(root[role], MODEL_MAPPINGS.openai[role])
720
+ ])
721
+ );
722
+ const selected = activeOpenCodeProfile().profile ?? { agents: {} };
723
+ return {
724
+ agents: Object.fromEntries(
725
+ PROFILE_ROLES.map((role) => [
726
+ role,
727
+ resolveRoleProfile(role, selected, roster)
439
728
  ])
440
- );
729
+ )
441
730
  };
442
- config.presets.openai = buildPreset("openai");
443
- if (installConfig.hasTmux) {
444
- config.tmux = {
445
- enabled: true,
446
- layout: "main-vertical",
447
- main_pane_size: 60
448
- };
731
+ }
732
+ function syncOpenCodeProfile(reset = false) {
733
+ const store = openCodeProfileStore();
734
+ const document = store.read("global");
735
+ const names = ["openai"];
736
+ if (document.active === "agents" && document.profiles.agents)
737
+ names.push("agents");
738
+ for (const name of names) {
739
+ const previous = document.profiles[name]?.agents ?? {};
740
+ const agents = Object.fromEntries(
741
+ PROFILE_ROLES.map((role) => {
742
+ const current = previous[role];
743
+ const legacy = LEGACY_OPENAI_PRESET[role];
744
+ const advance = current?.model === `openai/${legacy.model}` && current?.variant === legacy.effort;
745
+ return [
746
+ role,
747
+ reset && name === "openai" || advance ? MODEL_MAPPINGS.openai[role] : resolveRoleProfile(
748
+ role,
749
+ { agents: previous },
750
+ MODEL_MAPPINGS.openai
751
+ )
752
+ ];
753
+ })
754
+ );
755
+ store.save("global", name, { agents });
449
756
  }
450
- return config;
757
+ if (!document.active || !store.lookup(document.active, "global"))
758
+ store.setGlobalActive("openai");
759
+ }
760
+ function withoutLegacyPresets(config) {
761
+ const { preset: _preset, presets: _presets, ...rest } = config;
762
+ return rest;
451
763
  }
452
764
 
453
765
  // src/cli/config-io.ts
@@ -463,23 +775,23 @@ function stripJsonComments(json) {
463
775
  (match, comma, closing) => comma ? closing : match
464
776
  );
465
777
  }
466
- function parseConfigFile(path2) {
778
+ function parseConfigFile(path3) {
467
779
  try {
468
- if (!existsSync3(path2)) return { config: null };
469
- const stat = statSync(path2);
780
+ if (!existsSync3(path3)) return { config: null };
781
+ const stat = statSync(path3);
470
782
  if (stat.size === 0) return { config: null };
471
- const content = readFileSync2(path2, "utf-8");
783
+ const content = readFileSync2(path3, "utf-8");
472
784
  if (content.trim().length === 0) return { config: null };
473
785
  return { config: JSON.parse(stripJsonComments(content)) };
474
786
  } catch (err) {
475
787
  return { config: null, error: String(err) };
476
788
  }
477
789
  }
478
- function parseConfig(path2) {
479
- const result = parseConfigFile(path2);
790
+ function parseConfig(path3) {
791
+ const result = parseConfigFile(path3);
480
792
  if (result.config || result.error) return result;
481
- if (path2.endsWith(".json")) {
482
- const jsoncPath = path2.replace(/\.json$/, ".jsonc");
793
+ if (path3.endsWith(".json")) {
794
+ const jsoncPath = path3.replace(/\.json$/, ".jsonc");
483
795
  return parseConfigFile(jsoncPath);
484
796
  }
485
797
  return { config: null };
@@ -500,24 +812,32 @@ function writeConfig(configPath, config) {
500
812
  writeFileSync(tmpPath, content);
501
813
  renameSync(tmpPath, configPath);
502
814
  }
815
+ function pluginEntries(value) {
816
+ return Array.isArray(value) ? value : [];
817
+ }
818
+ function isManagedOpenCodePlugin(entry) {
819
+ const name = typeof entry === "string" ? entry : Array.isArray(entry) ? entry[0] : entry && typeof entry === "object" && "package" in entry ? entry.package : void 0;
820
+ return typeof name === "string" && (name === PACKAGE_NAME || name.startsWith(`${PACKAGE_NAME}@`));
821
+ }
822
+ function hasManagedPluginOptions(entry) {
823
+ if (!isManagedOpenCodePlugin(entry)) return false;
824
+ const options = Array.isArray(entry) ? entry[1] : entry && typeof entry === "object" && "options" in entry ? entry.options : void 0;
825
+ return !!options && typeof options === "object" && Object.keys(options).length > 0;
826
+ }
503
827
  function mergeOpenCodeMainConfig(config, update) {
504
828
  if (update.ensurePlugin) {
505
- const plugins = Array.isArray(config.plugin) ? config.plugin : [];
506
- config.plugin = [
507
- ...plugins.filter(
508
- (plugin) => plugin !== PACKAGE_NAME && !plugin.startsWith(`${PACKAGE_NAME}@`)
829
+ const legacy = pluginEntries(config.plugin).filter(
830
+ (entry) => !isManagedOpenCodePlugin(entry)
831
+ );
832
+ if (legacy.length > 0) config.plugin = legacy;
833
+ else delete config.plugin;
834
+ config.plugins = [
835
+ ...pluginEntries(config.plugins).filter(
836
+ (entry) => !isManagedOpenCodePlugin(entry)
509
837
  ),
510
838
  `${PACKAGE_NAME}@${update.pluginVersion}`
511
839
  ];
512
840
  }
513
- if (update.disableDefaults) {
514
- const agent = config.agent && typeof config.agent === "object" ? { ...config.agent } : {};
515
- const explore = agent.explore && typeof agent.explore === "object" ? { ...agent.explore } : {};
516
- const general = agent.general && typeof agent.general === "object" ? { ...agent.general } : {};
517
- agent.explore = { ...explore, disable: true };
518
- agent.general = { ...general, disable: true };
519
- config.agent = agent;
520
- }
521
841
  return config;
522
842
  }
523
843
  function updateOpenCodeMainConfig(update) {
@@ -547,10 +867,19 @@ function updateOpenCodeMainConfig(update) {
547
867
  error: `Failed to parse config: ${error}`
548
868
  };
549
869
  }
870
+ const removedOptions = update.ensurePlugin && [
871
+ ...pluginEntries(parsedConfig?.plugin),
872
+ ...pluginEntries(parsedConfig?.plugins)
873
+ ].some(hasManagedPluginOptions);
550
874
  writeConfig(
551
875
  configPath,
552
876
  mergeOpenCodeMainConfig(parsedConfig ?? {}, update)
553
877
  );
878
+ if (removedOptions) {
879
+ console.warn(
880
+ `[config-manager] Removed non-empty thoth-agents plugin options while pinning the v2 plugin. Original entries are preserved in ${configPath}.bak; configure the plugin via thoth-agents.json instead.`
881
+ );
882
+ }
554
883
  return { success: true, configPath };
555
884
  } catch (err) {
556
885
  return {
@@ -566,43 +895,6 @@ function configObject(value) {
566
895
  throw new Error("Expected an object in managed lite configuration.");
567
896
  return value;
568
897
  }
569
- function mergeLiteConfig(defaults, installed) {
570
- const presets = { ...configObject(installed.presets) };
571
- const shipped = configObject(configObject(defaults.presets).openai);
572
- const activeName = typeof installed.preset === "string" ? installed.preset : "openai";
573
- const rootAgents = { ...configObject(installed.agents) };
574
- for (const name of /* @__PURE__ */ new Set(["openai", activeName])) {
575
- const roles = { ...configObject(presets[name]) };
576
- for (const [role, legacy] of Object.entries(LEGACY_OPENAI_PRESET)) {
577
- const rootRole = name === activeName ? rootAgents[role] : void 0;
578
- const current = {
579
- ...configObject(roles[role]),
580
- ...configObject(rootRole)
581
- };
582
- const advancesLegacy = current.model === `openai/${legacy.model}` && current.variant === legacy.effort;
583
- if (roles[role] === void 0 && rootRole === void 0) {
584
- roles[role] = shipped[role];
585
- } else if (advancesLegacy) {
586
- roles[role] = { ...current, ...configObject(shipped[role]) };
587
- if (rootRole !== void 0)
588
- rootAgents[role] = {
589
- ...configObject(rootRole),
590
- ...configObject(shipped[role])
591
- };
592
- } else {
593
- roles[role] = current;
594
- }
595
- }
596
- presets[name] = roles;
597
- }
598
- return {
599
- ...defaults,
600
- ...installed,
601
- preset: activeName,
602
- presets,
603
- ...installed.agents !== void 0 ? { agents: rootAgents } : {}
604
- };
605
- }
606
898
  function writeLiteConfig(installConfig, targetPath) {
607
899
  const configPath = targetPath ?? getLiteConfig();
608
900
  try {
@@ -611,8 +903,10 @@ function writeLiteConfig(installConfig, targetPath) {
611
903
  if (!installConfig.reset && existsSync3(configPath)) {
612
904
  const { config: installed, error } = parseConfigFile(configPath);
613
905
  if (error) throw new Error(`Failed to parse lite config: ${error}`);
614
- if (installed) config = mergeLiteConfig(config, configObject(installed));
906
+ if (installed) config = { ...config, ...configObject(installed) };
615
907
  }
908
+ config = withoutLegacyPresets(config);
909
+ syncOpenCodeProfile(installConfig.reset);
616
910
  const tmpPath = `${configPath}.tmp`;
617
911
  const bakPath = `${configPath}.bak`;
618
912
  const content = `${JSON.stringify(config, null, 2)}
@@ -634,37 +928,35 @@ function writeLiteConfig(installConfig, targetPath) {
634
928
  function detectCurrentConfig() {
635
929
  const result = {
636
930
  isInstalled: false,
637
- hasOpenAI: false,
638
- hasTmux: false
931
+ hasOpenAI: false
639
932
  };
640
933
  const { config } = parseConfig(getExistingConfigPath());
641
934
  if (!config) return result;
642
- const plugins = config.plugin ?? [];
643
- result.isInstalled = plugins.some((p) => p.startsWith(PACKAGE_NAME));
935
+ result.isInstalled = [
936
+ ...pluginEntries(config.plugin),
937
+ ...pluginEntries(config.plugins)
938
+ ].some(isManagedOpenCodePlugin);
644
939
  const { config: liteConfig } = parseConfig(getLiteConfig());
645
940
  if (liteConfig && typeof liteConfig === "object") {
646
941
  const configObj = liteConfig;
647
- const presetName = configObj.preset;
648
- const presets = configObj.presets;
649
- const agents = presets?.[presetName];
942
+ const agents = effectiveOpenCodeProfile(configObj).agents;
650
943
  if (agents) {
651
944
  const models = Object.values(agents).map((a) => a?.model).filter(Boolean);
652
945
  result.hasOpenAI = models.some((m) => m?.startsWith("openai/"));
653
946
  }
654
- if (configObj.tmux && typeof configObj.tmux === "object") {
655
- const tmuxConfig = configObj.tmux;
656
- result.hasTmux = tmuxConfig.enabled === true;
657
- }
658
947
  }
659
948
  return result;
660
949
  }
661
950
 
662
951
  // src/config/loader.ts
663
952
  var PROMPTS_DIR_NAME = "thoth-agents";
664
- function loadConfigFromPath(configPath) {
953
+ function loadConfigFromPath(configPath, unsupportedKeyPaths) {
665
954
  try {
666
- const content = fs.readFileSync(configPath, "utf-8");
955
+ const content = fs2.readFileSync(configPath, "utf-8");
667
956
  const rawConfig = JSON.parse(stripJsonComments(content));
957
+ if (rawConfig !== null && typeof rawConfig === "object" && ("preset" in rawConfig || "presets" in rawConfig)) {
958
+ unsupportedKeyPaths.add(configPath);
959
+ }
668
960
  const result = PluginConfigSchema.safeParse(rawConfig);
669
961
  if (!result.success) {
670
962
  console.warn(`[thoth-agents] Invalid config at ${configPath}:`);
@@ -685,10 +977,10 @@ function loadConfigFromPath(configPath) {
685
977
  function findConfigPath(basePath) {
686
978
  const jsoncPath = `${basePath}.jsonc`;
687
979
  const jsonPath = `${basePath}.json`;
688
- if (fs.existsSync(jsoncPath)) {
980
+ if (fs2.existsSync(jsoncPath)) {
689
981
  return jsoncPath;
690
982
  }
691
- if (fs.existsSync(jsonPath)) {
983
+ if (fs2.existsSync(jsonPath)) {
692
984
  return jsonPath;
693
985
  }
694
986
  return null;
@@ -712,72 +1004,52 @@ function deepMerge(base, override) {
712
1004
  return result;
713
1005
  }
714
1006
  function loadPluginConfig(directory) {
715
- const userConfigBasePath = path.join(getConfigDir(), "thoth-agents");
716
- const projectConfigBasePath = path.join(
1007
+ const userConfigBasePath = path2.join(getConfigDir(), "thoth-agents");
1008
+ const projectConfigBasePath = path2.join(
717
1009
  directory,
718
1010
  ".opencode",
719
1011
  "thoth-agents"
720
1012
  );
721
1013
  const userConfigPath = findConfigPath(userConfigBasePath);
722
1014
  const projectConfigPath = findConfigPath(projectConfigBasePath);
723
- let config = userConfigPath ? loadConfigFromPath(userConfigPath) ?? {} : {};
724
- const projectConfig = projectConfigPath ? loadConfigFromPath(projectConfigPath) : null;
1015
+ const unsupportedKeyPaths = /* @__PURE__ */ new Set();
1016
+ let config = userConfigPath ? loadConfigFromPath(userConfigPath, unsupportedKeyPaths) ?? {} : {};
1017
+ const projectConfig = projectConfigPath ? loadConfigFromPath(projectConfigPath, unsupportedKeyPaths) : null;
725
1018
  if (projectConfig) {
726
1019
  config = {
727
1020
  ...config,
728
1021
  ...projectConfig,
729
1022
  agents: deepMerge(config.agents, projectConfig.agents),
730
- tmux: deepMerge(config.tmux, projectConfig.tmux),
731
1023
  fallback: deepMerge(config.fallback, projectConfig.fallback),
732
1024
  codex: deepMerge(config.codex, projectConfig.codex),
733
1025
  claudeCode: deepMerge(config.claudeCode, projectConfig.claudeCode)
734
1026
  };
735
1027
  }
736
- const envPreset = process.env.THOTH_AGENTS_PRESET;
737
- if (envPreset) {
738
- config.preset = envPreset;
739
- }
740
- if (config.preset) {
741
- const preset = config.presets?.[config.preset];
742
- if (preset) {
743
- config.agents = deepMerge(preset, config.agents);
744
- } else {
745
- const presetSource = envPreset === config.preset ? "environment variable" : "config file";
746
- const availablePresets = config.presets ? Object.keys(config.presets).join(", ") : "none";
747
- console.warn(
748
- `[thoth-agents] Preset "${config.preset}" not found (from ${presetSource}). Available presets: ${availablePresets}`
749
- );
750
- }
1028
+ if (unsupportedKeyPaths.size > 0) {
1029
+ console.warn(
1030
+ `[thoth-agents] Config keys preset/presets are unsupported and ignored (${[...unsupportedKeyPaths].join(", ")}).`
1031
+ );
751
1032
  }
752
1033
  return config;
753
1034
  }
754
- function loadAgentPrompt(agentName, preset) {
755
- const presetDirName = preset && /^[a-zA-Z0-9_-]+$/.test(preset) ? preset : void 0;
756
- const promptsDir = path.join(getConfigDir(), PROMPTS_DIR_NAME);
757
- const promptSearchDirs = presetDirName ? [path.join(promptsDir, presetDirName), promptsDir] : [promptsDir];
1035
+ function loadAgentPrompt(agentName) {
1036
+ const promptsDir = path2.join(getConfigDir(), PROMPTS_DIR_NAME);
758
1037
  const result = {};
759
- const readFirstPrompt = (fileName, errorPrefix) => {
760
- for (const dir of promptSearchDirs) {
761
- const promptPath = path.join(dir, fileName);
762
- if (!fs.existsSync(promptPath)) {
763
- continue;
764
- }
765
- try {
766
- return fs.readFileSync(promptPath, "utf-8");
767
- } catch (error) {
768
- console.warn(
769
- `[thoth-agents] ${errorPrefix} ${promptPath}:`,
770
- error instanceof Error ? error.message : String(error)
771
- );
772
- }
1038
+ const readPrompt = (fileName, errorPrefix) => {
1039
+ const promptPath = path2.join(promptsDir, fileName);
1040
+ if (!fs2.existsSync(promptPath)) return void 0;
1041
+ try {
1042
+ return fs2.readFileSync(promptPath, "utf-8");
1043
+ } catch (error) {
1044
+ console.warn(
1045
+ `[thoth-agents] ${errorPrefix} ${promptPath}:`,
1046
+ error instanceof Error ? error.message : String(error)
1047
+ );
1048
+ return void 0;
773
1049
  }
774
- return void 0;
775
1050
  };
776
- result.prompt = readFirstPrompt(
777
- `${agentName}.md`,
778
- "Error reading prompt file"
779
- );
780
- result.appendPrompt = readFirstPrompt(
1051
+ result.prompt = readPrompt(`${agentName}.md`, "Error reading prompt file");
1052
+ result.appendPrompt = readPrompt(
781
1053
  `${agentName}_append.md`,
782
1054
  "Error reading append prompt file"
783
1055
  );
@@ -788,7 +1060,7 @@ function loadAgentPrompt(agentName, preset) {
788
1060
  var text = { type: "string" };
789
1061
  var texts = { type: "array", items: text };
790
1062
  var nullableText = { type: ["string", "null"] };
791
- function object(properties) {
1063
+ function object2(properties) {
792
1064
  return {
793
1065
  type: "object",
794
1066
  properties,
@@ -799,7 +1071,7 @@ function object(properties) {
799
1071
  function array(items) {
800
1072
  return { type: "array", items };
801
1073
  }
802
- var common = object({
1074
+ var common = object2({
803
1075
  schemaVersion: { type: "number", const: 1 },
804
1076
  role: {
805
1077
  type: "string",
@@ -811,7 +1083,7 @@ var common = object({
811
1083
  },
812
1084
  conclusion: text,
813
1085
  evidence: array(
814
- object({
1086
+ object2({
815
1087
  id: { type: "string", minLength: 1 },
816
1088
  kind: text,
817
1089
  locator: text,
@@ -821,7 +1093,7 @@ var common = object({
821
1093
  })
822
1094
  ),
823
1095
  verification: array(
824
- object({
1096
+ object2({
825
1097
  check: text,
826
1098
  method: text,
827
1099
  outcome: { type: "string", enum: ["pass", "fail", "not-run"] },
@@ -830,7 +1102,7 @@ var common = object({
830
1102
  })
831
1103
  ),
832
1104
  risks: array(
833
- object({
1105
+ object2({
834
1106
  description: text,
835
1107
  severity: { type: "string", enum: ["low", "medium", "high", "critical"] },
836
1108
  blocking: { type: "boolean" },
@@ -838,11 +1110,11 @@ var common = object({
838
1110
  })
839
1111
  ),
840
1112
  openQuestions: array(
841
- object({ question: text, owner: text, options: texts, facts: texts })
1113
+ object2({ question: text, owner: text, options: texts, facts: texts })
842
1114
  )
843
1115
  });
844
1116
  var review = {
845
- ...object({
1117
+ ...object2({
846
1118
  kind: {
847
1119
  type: "string",
848
1120
  enum: ["plan-review", "final-verification"]
@@ -858,7 +1130,7 @@ var SPECIALIST_RESULT_CONTRACT = {
858
1130
  fence: "thoth-result",
859
1131
  common,
860
1132
  roles: {
861
- oracle: object({ nextAction: nullableText, review })
1133
+ oracle: object2({ nextAction: nullableText, review })
862
1134
  },
863
1135
  reviewKinds: {
864
1136
  "plan-review": { verdicts: ["OKAY", "REJECT"] },
@@ -1381,25 +1653,30 @@ function piCapabilityDisclosure(capability) {
1381
1653
  }
1382
1654
  var OPENCODE_PROMPT_DIALECT = {
1383
1655
  harness: "opencode",
1384
- gateDeliveryInstruction: "Delivery verdicts are appended to Oracle `task` output.",
1656
+ gateDeliveryInstruction: "Foreground Oracle `subagent` gates get one `thoth-gate-verdict` line; background completions have none: use the explicit compact validator fallback.",
1385
1657
  tools: {
1386
- delegationTool: "task",
1387
- backgroundDelegationTool: "task(background=true)",
1388
- backgroundStatusTool: "task_status",
1658
+ delegationTool: "subagent",
1659
+ backgroundDelegationTool: "subagent(background:true)",
1660
+ backgroundWaitInstruction: 'Native `<subagent sessionID=... state="completed">` completion wakes root. No status polling tool exists; never poll/sleep.',
1389
1661
  userQuestionTool: "question",
1390
1662
  progressTool: "todowrite",
1391
- hostStatusSurface: "task_status",
1663
+ hostStatusSurface: "native subagent completion messages",
1392
1664
  lifecycle: {
1393
- freshDelegation: "`task` without `task_id`",
1394
- sameAssignmentContinuation: "`task` with the prior `task_id`",
1395
- independentContext: "omitting `task_id` creates an isolated child session",
1396
- statusAction: "wait, poll, and collect",
1397
- terminalState: "terminal task_status result",
1398
- nonterminalState: "nonterminal task_status result",
1399
- sameSessionProbe: "task_status on the same task session",
1665
+ rootInstructions: [
1666
+ "- New objectives, phases, work units, mutable surfaces or independent judgments require fresh specialist sessions via `subagent` (agent, description, prompt; omit sessionID). These are fresh-session boundaries, not permission for root execution; never pool completed agents.",
1667
+ "- Use sessionID only to continue/clarify the same assignment, including while running. Optional model; background:true for independent work. Default nesting depth is 1.",
1668
+ "- Every Oracle plan review, verification round, and PASS judgment uses a fresh Oracle instance. Existing ones only clarify their findings."
1669
+ ].join("\n"),
1670
+ freshDelegation: "`subagent` with agent, description and prompt, omitting sessionID",
1671
+ sameAssignmentContinuation: "`subagent` with the prior sessionID, including while running",
1672
+ independentContext: "omitting sessionID creates an isolated child session",
1673
+ statusAction: "receive native completion automatically; never poll",
1674
+ terminalState: "a completed subagent result/message",
1675
+ nonterminalState: "a running placeholder",
1676
+ sameSessionProbe: "continue or clarify with subagent and the same sessionID; never ask for status",
1400
1677
  enforcement: "runtime-supported"
1401
1678
  },
1402
- roleReference: (role) => `@${role}`
1679
+ roleReference: (role) => `subagent agent:"${role}"`
1403
1680
  },
1404
1681
  capabilities: supportedCapabilityProfile(OPENCODE_CAPABILITIES),
1405
1682
  dispatchLabel(method) {
@@ -1407,11 +1684,11 @@ var OPENCODE_PROMPT_DIALECT = {
1407
1684
  case "root-coordinator":
1408
1685
  return "root coordinator";
1409
1686
  case "task":
1410
- return "task";
1687
+ return "subagent";
1411
1688
  }
1412
1689
  },
1413
1690
  renderRoleInvocation(role) {
1414
- return `@${role}`;
1691
+ return `subagent agent:"${role}"`;
1415
1692
  }
1416
1693
  };
1417
1694
  var CODEX_PROMPT_DIALECT = {
@@ -2231,7 +2508,7 @@ Use \`${dialect.tools.userQuestionTool}\` only for a blocking material choice, d
2231
2508
  }
2232
2509
  function renderSubagentRules(section, dialect) {
2233
2510
  const rules = [
2234
- dialect.tools.progressTool ? `- Do not delegate further or call \`${dialect.tools.progressTool}\`; root owns progress.` : "- Do not delegate further; root owns progress.",
2511
+ dialect.harness === "opencode" ? "- Do not delegate further; no `todowrite` (root-owned)." : dialect.tools.progressTool ? `- Do not delegate further or call \`${dialect.tools.progressTool}\`; root owns progress.` : "- Do not delegate further; root owns progress.",
2235
2512
  "- Use terminating checks; avoid watch processes and indefinite waits.",
2236
2513
  "- Preserve operator-selected model/effort. Stop after the assigned outcome and checks; never expand scope to fill a timeout.",
2237
2514
  "- After two consecutive attempts without new evidence or progress, return partial evidence and the smallest blocker; do not repeat searches or unchanged failing commands.",
@@ -2420,6 +2697,7 @@ function composeAgentPrompt({
2420
2697
  }
2421
2698
 
2422
2699
  export {
2700
+ PROFILE_ROLES,
2423
2701
  SUBAGENT_NAMES,
2424
2702
  ALL_AGENT_NAMES,
2425
2703
  LEGACY_OPENAI_PRESET,
@@ -2428,19 +2706,30 @@ export {
2428
2706
  DEFAULT_MODELS,
2429
2707
  getDefaultOpenCodeModel,
2430
2708
  getDefaultOpenCodeVariant,
2431
- POLL_INTERVAL_BACKGROUND_MS,
2432
- findPackageRoot,
2433
- isValidPackageVersion,
2434
- resolveExecutingPackageVersion,
2435
- getOpenCodeConfigPaths,
2709
+ resolveRoleProfile,
2710
+ mergeProfileModelChain,
2711
+ formatModelString,
2712
+ validateProfileName,
2713
+ ProfileConflictError,
2714
+ ModelProfileStore,
2715
+ getConfigDir,
2436
2716
  getOpenCodeManagedModelStatePath,
2437
2717
  getExistingLiteConfigPath,
2438
2718
  getExistingConfigPath,
2439
2719
  ensureConfigDir,
2720
+ MODEL_MAPPINGS,
2440
2721
  generateLiteConfig,
2441
- stripJsonComments,
2722
+ openCodeProfileStore,
2723
+ activeOpenCodeProfile,
2724
+ effectiveOpenCodeProfile,
2725
+ withoutLegacyPresets,
2726
+ findPackageRoot,
2727
+ isValidPackageVersion,
2728
+ resolveExecutingPackageVersion,
2442
2729
  parseConfig,
2443
2730
  writeConfig,
2731
+ pluginEntries,
2732
+ isManagedOpenCodePlugin,
2444
2733
  updateOpenCodeMainConfig,
2445
2734
  writeLiteConfig,
2446
2735
  detectCurrentConfig,