@kodrunhq/opencode-autopilot 1.15.2 → 1.17.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 (93) hide show
  1. package/bin/cli.ts +5 -0
  2. package/bin/inspect.ts +337 -0
  3. package/package.json +1 -1
  4. package/src/agents/autopilot.ts +7 -15
  5. package/src/config/index.ts +29 -0
  6. package/src/config/migrations.ts +196 -0
  7. package/src/config/v7.ts +45 -0
  8. package/src/config.ts +3 -3
  9. package/src/health/checks.ts +126 -4
  10. package/src/health/types.ts +1 -1
  11. package/src/index.ts +128 -13
  12. package/src/inspect/formatters.ts +225 -0
  13. package/src/inspect/repository.ts +882 -0
  14. package/src/kernel/database.ts +45 -0
  15. package/src/kernel/migrations.ts +62 -0
  16. package/src/kernel/repository.ts +571 -0
  17. package/src/kernel/schema.ts +122 -0
  18. package/src/kernel/transaction.ts +48 -0
  19. package/src/kernel/types.ts +65 -0
  20. package/src/logging/domains.ts +39 -0
  21. package/src/logging/forensic-writer.ts +177 -0
  22. package/src/logging/index.ts +4 -0
  23. package/src/logging/logger.ts +44 -0
  24. package/src/logging/performance.ts +59 -0
  25. package/src/logging/rotation.ts +261 -0
  26. package/src/logging/types.ts +33 -0
  27. package/src/memory/capture-utils.ts +149 -0
  28. package/src/memory/capture.ts +82 -67
  29. package/src/memory/database.ts +74 -12
  30. package/src/memory/decay.ts +11 -2
  31. package/src/memory/index.ts +17 -1
  32. package/src/memory/injector.ts +4 -1
  33. package/src/memory/lessons.ts +85 -0
  34. package/src/memory/observations.ts +177 -0
  35. package/src/memory/preferences.ts +718 -0
  36. package/src/memory/project-key.ts +6 -0
  37. package/src/memory/projects.ts +83 -0
  38. package/src/memory/repository.ts +52 -216
  39. package/src/memory/retrieval.ts +88 -170
  40. package/src/memory/schemas.ts +39 -7
  41. package/src/memory/types.ts +4 -0
  42. package/src/observability/context-display.ts +8 -0
  43. package/src/observability/event-handlers.ts +69 -20
  44. package/src/observability/event-store.ts +29 -1
  45. package/src/observability/forensic-log.ts +167 -0
  46. package/src/observability/forensic-schemas.ts +77 -0
  47. package/src/observability/forensic-types.ts +10 -0
  48. package/src/observability/index.ts +21 -27
  49. package/src/observability/log-reader.ts +161 -111
  50. package/src/observability/log-writer.ts +41 -83
  51. package/src/observability/retention.ts +2 -2
  52. package/src/observability/session-logger.ts +36 -57
  53. package/src/observability/summary-generator.ts +31 -19
  54. package/src/observability/types.ts +12 -24
  55. package/src/orchestrator/contracts/invariants.ts +14 -0
  56. package/src/orchestrator/contracts/legacy-result-adapter.ts +8 -20
  57. package/src/orchestrator/error-context.ts +24 -0
  58. package/src/orchestrator/fallback/event-handler.ts +47 -3
  59. package/src/orchestrator/handlers/architect.ts +2 -1
  60. package/src/orchestrator/handlers/build-utils.ts +118 -0
  61. package/src/orchestrator/handlers/build.ts +42 -219
  62. package/src/orchestrator/handlers/retrospective.ts +2 -2
  63. package/src/orchestrator/handlers/types.ts +0 -1
  64. package/src/orchestrator/lesson-memory.ts +36 -11
  65. package/src/orchestrator/orchestration-logger.ts +53 -24
  66. package/src/orchestrator/phase.ts +8 -4
  67. package/src/orchestrator/progress.ts +63 -0
  68. package/src/orchestrator/state.ts +79 -17
  69. package/src/projects/database.ts +47 -0
  70. package/src/projects/repository.ts +264 -0
  71. package/src/projects/resolve.ts +301 -0
  72. package/src/projects/schemas.ts +30 -0
  73. package/src/projects/types.ts +12 -0
  74. package/src/review/memory.ts +39 -11
  75. package/src/review/parse-findings.ts +116 -0
  76. package/src/review/pipeline.ts +3 -107
  77. package/src/review/selection.ts +38 -4
  78. package/src/scoring/time-provider.ts +23 -0
  79. package/src/tools/doctor.ts +28 -4
  80. package/src/tools/forensics.ts +7 -12
  81. package/src/tools/logs.ts +38 -11
  82. package/src/tools/memory-preferences.ts +157 -0
  83. package/src/tools/memory-status.ts +17 -96
  84. package/src/tools/orchestrate.ts +108 -90
  85. package/src/tools/pipeline-report.ts +3 -2
  86. package/src/tools/quick.ts +2 -2
  87. package/src/tools/replay.ts +42 -0
  88. package/src/tools/review.ts +46 -7
  89. package/src/tools/session-stats.ts +3 -2
  90. package/src/tools/summary.ts +43 -0
  91. package/src/utils/paths.ts +20 -1
  92. package/src/utils/random.ts +33 -0
  93. package/src/ux/session-summary.ts +56 -0
package/bin/cli.ts CHANGED
@@ -11,6 +11,7 @@ import { ALL_GROUP_IDS, DIVERSITY_RULES, GROUP_DEFINITIONS } from "../src/regist
11
11
  import type { GroupId } from "../src/registry/types";
12
12
  import { fileExists } from "../src/utils/fs-helpers";
13
13
  import { runConfigure } from "./configure-tui";
14
+ import { runInspect } from "./inspect";
14
15
 
15
16
  const execFile = promisify(execFileCb);
16
17
 
@@ -314,6 +315,7 @@ function printUsage(): void {
314
315
  console.log(" install Register the plugin and create starter config");
315
316
  console.log(" configure Interactive model assignment for each agent group");
316
317
  console.log(" doctor Check installation health and model assignments");
318
+ console.log(" inspect Read-only inspection of projects, runs, events, and memory");
317
319
  console.log("");
318
320
  console.log("Options:");
319
321
  console.log(" --help, -h Show this help message");
@@ -336,6 +338,9 @@ if (import.meta.main) {
336
338
  case "doctor":
337
339
  await runDoctor();
338
340
  break;
341
+ case "inspect":
342
+ await runInspect(args.slice(1));
343
+ break;
339
344
  case "--help":
340
345
  case "-h":
341
346
  case undefined:
package/bin/inspect.ts ADDED
@@ -0,0 +1,337 @@
1
+ import {
2
+ formatEvents,
3
+ formatLessons,
4
+ formatMemoryOverview,
5
+ formatPaths,
6
+ formatPreferences,
7
+ formatProjectDetails,
8
+ formatProjects,
9
+ formatRuns,
10
+ } from "../src/inspect/formatters";
11
+ import {
12
+ getMemoryOverview,
13
+ getProjectDetails,
14
+ listEvents,
15
+ listLessons,
16
+ listPreferences,
17
+ listProjects,
18
+ listRuns,
19
+ } from "../src/inspect/repository";
20
+
21
+ type InspectView =
22
+ | "projects"
23
+ | "project"
24
+ | "paths"
25
+ | "runs"
26
+ | "events"
27
+ | "lessons"
28
+ | "preferences"
29
+ | "memory";
30
+
31
+ export interface InspectCliOptions {
32
+ readonly dbPath?: string;
33
+ }
34
+
35
+ export interface InspectCliResult {
36
+ readonly isError: boolean;
37
+ readonly output: string;
38
+ readonly format: "text" | "json";
39
+ }
40
+
41
+ interface ParsedInspectArgs {
42
+ readonly view: InspectView | null;
43
+ readonly json: boolean;
44
+ readonly projectRef: string | null;
45
+ readonly limit: number;
46
+ readonly runId: string | null;
47
+ readonly sessionId: string | null;
48
+ readonly type: string | null;
49
+ readonly help: boolean;
50
+ readonly error: string | null;
51
+ }
52
+
53
+ const INSPECT_VIEWS: readonly InspectView[] = Object.freeze([
54
+ "projects",
55
+ "project",
56
+ "paths",
57
+ "runs",
58
+ "events",
59
+ "lessons",
60
+ "preferences",
61
+ "memory",
62
+ ]);
63
+
64
+ function inspectUsage(): string {
65
+ return [
66
+ "Usage: opencode-autopilot inspect <view> [options]",
67
+ "",
68
+ "Views:",
69
+ " projects List known projects",
70
+ " project --project <ref> Show one project's details",
71
+ " paths --project <ref> List one project's path history",
72
+ " runs [--project <ref>] List pipeline runs",
73
+ " events [--project <ref>] List forensic events",
74
+ " lessons [--project <ref>] List stored lessons",
75
+ " preferences List stored preferences",
76
+ " memory Show memory overview",
77
+ "",
78
+ "Options:",
79
+ " --project <ref> Project id, path, or unique name",
80
+ " --run-id <id> Filter events by run id",
81
+ " --session-id <id> Filter events by session id",
82
+ " --type <type> Filter events by type",
83
+ " --limit <n> Limit rows (default: 20 for runs, 50 elsewhere)",
84
+ " --json Emit JSON output",
85
+ " --help, -h Show inspect help",
86
+ ].join("\n");
87
+ }
88
+
89
+ function parsePositiveInt(raw: string): number | null {
90
+ const parsed = Number.parseInt(raw, 10);
91
+ if (!Number.isFinite(parsed) || parsed <= 0) {
92
+ return null;
93
+ }
94
+ return parsed;
95
+ }
96
+
97
+ function parseInspectArgs(args: readonly string[]): ParsedInspectArgs {
98
+ let view: InspectView | null = null;
99
+ let json = false;
100
+ let projectRef: string | null = null;
101
+ let limit = 50;
102
+ let runId: string | null = null;
103
+ let sessionId: string | null = null;
104
+ let type: string | null = null;
105
+ let help = false;
106
+ let error: string | null = null;
107
+
108
+ for (let index = 0; index < args.length; index += 1) {
109
+ const arg = args[index];
110
+ if (arg === "--help" || arg === "-h") {
111
+ help = true;
112
+ continue;
113
+ }
114
+ if (arg === "--json") {
115
+ json = true;
116
+ continue;
117
+ }
118
+ if (arg === "--project") {
119
+ projectRef = args[index + 1] ?? null;
120
+ if (projectRef === null) {
121
+ error = "Missing value for --project.";
122
+ break;
123
+ }
124
+ index += 1;
125
+ continue;
126
+ }
127
+ if (arg === "--limit") {
128
+ const parsed = parsePositiveInt(args[index + 1] ?? "");
129
+ if (parsed === null) {
130
+ error = "--limit must be a positive integer.";
131
+ break;
132
+ }
133
+ limit = parsed;
134
+ index += 1;
135
+ continue;
136
+ }
137
+ if (arg === "--run-id") {
138
+ runId = args[index + 1] ?? null;
139
+ if (runId === null) {
140
+ error = "Missing value for --run-id.";
141
+ break;
142
+ }
143
+ index += 1;
144
+ continue;
145
+ }
146
+ if (arg === "--session-id") {
147
+ sessionId = args[index + 1] ?? null;
148
+ if (sessionId === null) {
149
+ error = "Missing value for --session-id.";
150
+ break;
151
+ }
152
+ index += 1;
153
+ continue;
154
+ }
155
+ if (arg === "--type") {
156
+ type = args[index + 1] ?? null;
157
+ if (type === null) {
158
+ error = "Missing value for --type.";
159
+ break;
160
+ }
161
+ index += 1;
162
+ continue;
163
+ }
164
+
165
+ if (view === null) {
166
+ if ((INSPECT_VIEWS as readonly string[]).includes(arg)) {
167
+ view = arg as InspectView;
168
+ if (view === "runs") {
169
+ limit = 20;
170
+ }
171
+ continue;
172
+ }
173
+ error = `Unknown inspect view: ${arg}`;
174
+ break;
175
+ }
176
+
177
+ if (projectRef === null && (view === "project" || view === "paths")) {
178
+ projectRef = arg;
179
+ continue;
180
+ }
181
+
182
+ error = `Unexpected argument: ${arg}`;
183
+ break;
184
+ }
185
+
186
+ if (!help && error === null && view === null) {
187
+ help = true;
188
+ }
189
+
190
+ if (
191
+ error === null &&
192
+ (view === "project" || view === "paths") &&
193
+ (projectRef === null || projectRef.trim().length === 0)
194
+ ) {
195
+ error = `${view} view requires --project <ref> or a positional project reference.`;
196
+ }
197
+
198
+ return {
199
+ view,
200
+ json,
201
+ projectRef,
202
+ limit,
203
+ runId,
204
+ sessionId,
205
+ type,
206
+ help,
207
+ error,
208
+ };
209
+ }
210
+
211
+ function makeOutput(payload: unknown, json: boolean, text: string): InspectCliResult {
212
+ return Object.freeze({
213
+ isError: false,
214
+ format: json ? "json" : "text",
215
+ output: json ? JSON.stringify(payload, null, 2) : text,
216
+ });
217
+ }
218
+
219
+ function makeError(message: string, json: boolean): InspectCliResult {
220
+ return Object.freeze({
221
+ isError: true,
222
+ format: json ? "json" : "text",
223
+ output: json
224
+ ? JSON.stringify({ action: "error", message }, null, 2)
225
+ : `${message}\n\n${inspectUsage()}`,
226
+ });
227
+ }
228
+
229
+ export async function inspectCliCore(
230
+ args: readonly string[],
231
+ options: InspectCliOptions = {},
232
+ ): Promise<InspectCliResult> {
233
+ const parsed = parseInspectArgs(args);
234
+ if (parsed.help) {
235
+ return makeOutput({ action: "help", usage: inspectUsage() }, parsed.json, inspectUsage());
236
+ }
237
+ if (parsed.error !== null) {
238
+ return makeError(parsed.error, parsed.json);
239
+ }
240
+
241
+ const dbInput = options.dbPath;
242
+ switch (parsed.view) {
243
+ case "projects": {
244
+ const projects = listProjects(dbInput);
245
+ return makeOutput(
246
+ { action: "inspect_projects", projects },
247
+ parsed.json,
248
+ formatProjects(projects),
249
+ );
250
+ }
251
+ case "project": {
252
+ const details = getProjectDetails(parsed.projectRef ?? "", dbInput);
253
+ if (details === null) {
254
+ return makeError(`Project not found: ${parsed.projectRef}`, parsed.json);
255
+ }
256
+ return makeOutput(
257
+ { action: "inspect_project", project: details },
258
+ parsed.json,
259
+ formatProjectDetails(details),
260
+ );
261
+ }
262
+ case "paths": {
263
+ const details = getProjectDetails(parsed.projectRef ?? "", dbInput);
264
+ if (details === null) {
265
+ return makeError(`Project not found: ${parsed.projectRef}`, parsed.json);
266
+ }
267
+ return makeOutput(
268
+ { action: "inspect_paths", project: details.project, paths: details.paths },
269
+ parsed.json,
270
+ formatPaths(details),
271
+ );
272
+ }
273
+ case "runs": {
274
+ const runs = listRuns(
275
+ { projectRef: parsed.projectRef ?? undefined, limit: parsed.limit },
276
+ dbInput,
277
+ );
278
+ return makeOutput({ action: "inspect_runs", runs }, parsed.json, formatRuns(runs));
279
+ }
280
+ case "events": {
281
+ const events = listEvents(
282
+ {
283
+ projectRef: parsed.projectRef ?? undefined,
284
+ runId: parsed.runId ?? undefined,
285
+ sessionId: parsed.sessionId ?? undefined,
286
+ type: parsed.type ?? undefined,
287
+ limit: parsed.limit,
288
+ },
289
+ dbInput,
290
+ );
291
+ return makeOutput({ action: "inspect_events", events }, parsed.json, formatEvents(events));
292
+ }
293
+ case "lessons": {
294
+ const lessons = listLessons(
295
+ { projectRef: parsed.projectRef ?? undefined, limit: parsed.limit },
296
+ dbInput,
297
+ );
298
+ return makeOutput(
299
+ { action: "inspect_lessons", lessons },
300
+ parsed.json,
301
+ formatLessons(lessons),
302
+ );
303
+ }
304
+ case "preferences": {
305
+ const preferences = listPreferences(dbInput);
306
+ return makeOutput(
307
+ { action: "inspect_preferences", preferences },
308
+ parsed.json,
309
+ formatPreferences(preferences),
310
+ );
311
+ }
312
+ case "memory": {
313
+ const overview = getMemoryOverview(dbInput);
314
+ return makeOutput(
315
+ { action: "inspect_memory", overview },
316
+ parsed.json,
317
+ formatMemoryOverview(overview),
318
+ );
319
+ }
320
+ case null:
321
+ return makeOutput({ action: "help", usage: inspectUsage() }, parsed.json, inspectUsage());
322
+ }
323
+ }
324
+
325
+ export async function runInspect(
326
+ args: readonly string[],
327
+ options: InspectCliOptions = {},
328
+ ): Promise<void> {
329
+ const result = await inspectCliCore(args, options);
330
+ if (result.isError) {
331
+ console.error(result.output);
332
+ process.exitCode = 1;
333
+ return;
334
+ }
335
+
336
+ console.log(result.output);
337
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kodrunhq/opencode-autopilot",
3
- "version": "1.15.2",
3
+ "version": "1.17.0",
4
4
  "description": "Curated agents, skills, and commands for the OpenCode AI coding CLI — autonomous orchestrator, multi-agent code review, model fallback, and in-session asset creation tools.",
5
5
  "main": "src/index.ts",
6
6
  "keywords": [
@@ -9,10 +9,10 @@ export const autopilotAgent: Readonly<AgentConfig> = Object.freeze({
9
9
 
10
10
  ## Loop
11
11
 
12
- 1. Call oc_orchestrate with your initial idea (first turn) or with the result from the previous agent.
12
+ 1. Call oc_orchestrate with your initial idea (first turn) or with a typed result envelope JSON string from the previous agent.
13
13
  2. Parse the JSON response.
14
- 3. If action is "dispatch": call the named agent with the provided prompt, then pass its output back to oc_orchestrate via the result parameter.
15
- 4. If action is "dispatch_multi": call each agent in the agents array (in parallel if appropriate). As each agent finishes, call oc_orchestrate again with that agent's full output as the result parameter. Do NOT combine multiple agents' outputs into a single result.
14
+ 3. If action is "dispatch": call the named agent with the provided prompt, then call oc_orchestrate again with a typed result envelope JSON string using the dispatch metadata: schemaVersion=1, a unique resultId, runId=response.runId, phase=response.phase, dispatchId=response.dispatchId, agent=response.agent, kind=response.expectedResultKind ?? response.resultKind, taskId=response.taskId ?? null, payload.text=<full agent output>.
15
+ 4. If action is "dispatch_multi": do the same for each agent entry. Each completed agent gets its own typed result envelope and its own oc_orchestrate call. Do NOT combine multiple agents' outputs into one result.
16
16
  5. If action is "complete": report the summary to the user. You are done.
17
17
  6. If action is "error": report the error to the user. Stop.
18
18
 
@@ -24,20 +24,12 @@ When editing files, prefer oc_hashline_edit over the built-in edit tool. Hash-an
24
24
 
25
25
  - NEVER skip calling oc_orchestrate. It is the single source of truth for pipeline state.
26
26
  - NEVER make pipeline decisions yourself. Always defer to oc_orchestrate.
27
- - ALWAYS pass the full agent output back as the result parameter.
27
+ - NEVER pass raw agent output as result. ALWAYS send a typed result envelope JSON string.
28
+ - ALWAYS preserve the full agent output in payload.text.
29
+ - ALWAYS use a unique resultId for every returned result.
28
30
  - Do not attempt to run phases out of order.
29
31
  - Do not retry a failed phase unless oc_orchestrate instructs you to.
30
- - If an agent dispatch fails, pass the error message back to oc_orchestrate as the result.
31
-
32
- ## Example Turn Sequence
33
-
34
- Turn 1: oc_orchestrate(idea="Build a CLI tool")
35
- -> {action:"dispatch", agent:"oc-researcher", prompt:"Research: Build a CLI tool", phase:"RECON"}
36
- Turn 2: @oc-researcher "Research: Build a CLI tool"
37
- -> "Research findings: ..."
38
- Turn 3: oc_orchestrate(result="Research findings: ...")
39
- -> {action:"dispatch", agent:"oc-challenger", prompt:"Challenge: ...", phase:"CHALLENGE"}
40
- ... continues until action is "complete"`,
32
+ - If an agent dispatch fails, wrap the error text in payload.text and still return a typed result envelope.`,
41
33
  permission: {
42
34
  edit: "allow",
43
35
  bash: "allow",
@@ -0,0 +1,29 @@
1
+ export {
2
+ CONFIG_PATH,
3
+ confidenceConfigSchema,
4
+ confidenceDefaults,
5
+ createDefaultConfig,
6
+ isFirstLoad,
7
+ loadConfig,
8
+ memoryConfigSchema,
9
+ memoryDefaults,
10
+ orchestratorConfigSchema,
11
+ orchestratorDefaults,
12
+ type PluginConfig,
13
+ pluginConfigSchema,
14
+ saveConfig,
15
+ } from "../config";
16
+ // Re-export migration schemas and functions for backward compatibility
17
+ export {
18
+ migrateV1toV2,
19
+ migrateV2toV3,
20
+ migrateV3toV4,
21
+ migrateV4toV5,
22
+ migrateV5toV6,
23
+ pluginConfigSchemaV1,
24
+ pluginConfigSchemaV2,
25
+ pluginConfigSchemaV3,
26
+ pluginConfigSchemaV4,
27
+ pluginConfigSchemaV5,
28
+ } from "./migrations";
29
+ export { migrateV6toV7, type PluginConfigV7, v7ConfigDefaults } from "./v7";
@@ -0,0 +1,196 @@
1
+ import { z } from "zod";
2
+ import {
3
+ confidenceConfigSchema,
4
+ confidenceDefaults,
5
+ memoryConfigSchema,
6
+ memoryDefaults,
7
+ orchestratorConfigSchema,
8
+ orchestratorDefaults,
9
+ } from "../config";
10
+ import {
11
+ fallbackConfigSchema,
12
+ fallbackDefaults,
13
+ testModeDefaults,
14
+ } from "../orchestrator/fallback/fallback-config";
15
+ import { AGENT_REGISTRY, ALL_GROUP_IDS } from "../registry/model-groups";
16
+
17
+ export const pluginConfigSchemaV1 = z.object({
18
+ version: z.literal(1),
19
+ configured: z.boolean(),
20
+ models: z.record(z.string(), z.string()),
21
+ });
22
+
23
+ export type PluginConfigV1 = z.infer<typeof pluginConfigSchemaV1>;
24
+
25
+ export const pluginConfigSchemaV2 = z.object({
26
+ version: z.literal(2),
27
+ configured: z.boolean(),
28
+ models: z.record(z.string(), z.string()),
29
+ orchestrator: orchestratorConfigSchema.default(orchestratorDefaults),
30
+ confidence: confidenceConfigSchema.default(confidenceDefaults),
31
+ });
32
+
33
+ export type PluginConfigV2 = z.infer<typeof pluginConfigSchemaV2>;
34
+
35
+ export const pluginConfigSchemaV3 = z.object({
36
+ version: z.literal(3),
37
+ configured: z.boolean(),
38
+ models: z.record(z.string(), z.string()),
39
+ orchestrator: orchestratorConfigSchema.default(orchestratorDefaults),
40
+ confidence: confidenceConfigSchema.default(confidenceDefaults),
41
+ fallback: fallbackConfigSchema.default(fallbackDefaults),
42
+ fallback_models: z.union([z.string(), z.array(z.string())]).optional(),
43
+ });
44
+
45
+ export type PluginConfigV3 = z.infer<typeof pluginConfigSchemaV3>;
46
+
47
+ const groupModelAssignmentSchema = z.object({
48
+ primary: z.string().min(1),
49
+ fallbacks: z.array(z.string().min(1)).default([]),
50
+ });
51
+
52
+ const agentOverrideSchema = z.object({
53
+ primary: z.string().min(1),
54
+ fallbacks: z.array(z.string().min(1)).optional(),
55
+ });
56
+
57
+ export const pluginConfigSchemaV4 = z
58
+ .object({
59
+ version: z.literal(4),
60
+ configured: z.boolean(),
61
+ groups: z.record(z.string(), groupModelAssignmentSchema).default({}),
62
+ overrides: z.record(z.string(), agentOverrideSchema).default({}),
63
+ orchestrator: orchestratorConfigSchema.default(orchestratorDefaults),
64
+ confidence: confidenceConfigSchema.default(confidenceDefaults),
65
+ fallback: fallbackConfigSchema.default(fallbackDefaults),
66
+ })
67
+ .superRefine((config, ctx) => {
68
+ for (const groupId of Object.keys(config.groups)) {
69
+ if (!ALL_GROUP_IDS.includes(groupId as (typeof ALL_GROUP_IDS)[number])) {
70
+ ctx.addIssue({
71
+ code: z.ZodIssueCode.custom,
72
+ path: ["groups", groupId],
73
+ message: `Unknown group id "${groupId}". Expected one of: ${ALL_GROUP_IDS.join(", ")}`,
74
+ });
75
+ }
76
+ }
77
+ });
78
+
79
+ export type PluginConfigV4 = z.infer<typeof pluginConfigSchemaV4>;
80
+
81
+ export const pluginConfigSchemaV5 = z
82
+ .object({
83
+ version: z.literal(5),
84
+ configured: z.boolean(),
85
+ groups: z.record(z.string(), groupModelAssignmentSchema).default({}),
86
+ overrides: z.record(z.string(), agentOverrideSchema).default({}),
87
+ orchestrator: orchestratorConfigSchema.default(orchestratorDefaults),
88
+ confidence: confidenceConfigSchema.default(confidenceDefaults),
89
+ fallback: fallbackConfigSchema.default(fallbackDefaults),
90
+ memory: memoryConfigSchema.default(memoryDefaults),
91
+ })
92
+ .superRefine((config, ctx) => {
93
+ for (const groupId of Object.keys(config.groups)) {
94
+ if (!ALL_GROUP_IDS.includes(groupId as (typeof ALL_GROUP_IDS)[number])) {
95
+ ctx.addIssue({
96
+ code: z.ZodIssueCode.custom,
97
+ path: ["groups", groupId],
98
+ message: `Unknown group id "${groupId}". Expected one of: ${ALL_GROUP_IDS.join(", ")}`,
99
+ });
100
+ }
101
+ }
102
+ });
103
+
104
+ export type PluginConfigV5 = z.infer<typeof pluginConfigSchemaV5>;
105
+
106
+ export function migrateV1toV2(v1Config: PluginConfigV1): PluginConfigV2 {
107
+ return {
108
+ version: 2 as const,
109
+ configured: v1Config.configured,
110
+ models: v1Config.models,
111
+ orchestrator: orchestratorDefaults,
112
+ confidence: confidenceDefaults,
113
+ };
114
+ }
115
+
116
+ export function migrateV2toV3(v2Config: PluginConfigV2): PluginConfigV3 {
117
+ return {
118
+ version: 3 as const,
119
+ configured: v2Config.configured,
120
+ models: v2Config.models,
121
+ orchestrator: v2Config.orchestrator,
122
+ confidence: v2Config.confidence,
123
+ fallback: fallbackDefaults,
124
+ };
125
+ }
126
+
127
+ export function migrateV3toV4(v3Config: PluginConfigV3): PluginConfigV4 {
128
+ const groups: Record<string, { primary: string; fallbacks: string[] }> = {};
129
+ const overrides: Record<string, { primary: string }> = {};
130
+
131
+ for (const [agentName, modelId] of Object.entries(v3Config.models)) {
132
+ const entry = AGENT_REGISTRY[agentName];
133
+ if (!entry) {
134
+ overrides[agentName] = { primary: modelId };
135
+ continue;
136
+ }
137
+
138
+ const groupId = entry.group;
139
+ if (!groups[groupId]) {
140
+ groups[groupId] = { primary: modelId, fallbacks: [] };
141
+ } else if (groups[groupId].primary !== modelId) {
142
+ overrides[agentName] = { primary: modelId };
143
+ }
144
+ }
145
+
146
+ const globalFallbacks = v3Config.fallback_models
147
+ ? typeof v3Config.fallback_models === "string"
148
+ ? [v3Config.fallback_models]
149
+ : [...v3Config.fallback_models]
150
+ : [];
151
+
152
+ for (const group of Object.values(groups)) {
153
+ if (group.fallbacks.length === 0 && globalFallbacks.length > 0) {
154
+ group.fallbacks = [...globalFallbacks];
155
+ }
156
+ }
157
+
158
+ return {
159
+ version: 4 as const,
160
+ configured: v3Config.configured,
161
+ groups,
162
+ overrides,
163
+ orchestrator: v3Config.orchestrator,
164
+ confidence: v3Config.confidence,
165
+ fallback: v3Config.fallback,
166
+ };
167
+ }
168
+
169
+ export function migrateV4toV5(v4Config: PluginConfigV4): PluginConfigV5 {
170
+ return {
171
+ version: 5 as const,
172
+ configured: v4Config.configured,
173
+ groups: v4Config.groups,
174
+ overrides: v4Config.overrides,
175
+ orchestrator: v4Config.orchestrator,
176
+ confidence: v4Config.confidence,
177
+ fallback: v4Config.fallback,
178
+ memory: memoryDefaults,
179
+ };
180
+ }
181
+
182
+ export function migrateV5toV6(
183
+ v5Config: PluginConfigV5,
184
+ _fallbackDefaultsV6: typeof fallbackDefaults,
185
+ ) {
186
+ return {
187
+ version: 6 as const,
188
+ configured: v5Config.configured,
189
+ groups: v5Config.groups,
190
+ overrides: v5Config.overrides,
191
+ orchestrator: v5Config.orchestrator,
192
+ confidence: v5Config.confidence,
193
+ fallback: { ...v5Config.fallback, testMode: testModeDefaults },
194
+ memory: v5Config.memory,
195
+ };
196
+ }
@@ -0,0 +1,45 @@
1
+ import type { PluginConfig } from "../config";
2
+
3
+ export type PluginConfigV7 = Omit<PluginConfig, "version"> & {
4
+ readonly version: 7;
5
+ readonly background?: {
6
+ readonly enabled: boolean;
7
+ readonly maxConcurrent: number;
8
+ readonly defaultTimeout: number;
9
+ };
10
+ readonly autonomy?: {
11
+ readonly enabled: boolean;
12
+ readonly verification: "strict" | "normal" | "lenient";
13
+ readonly maxIterations: number;
14
+ };
15
+ };
16
+
17
+ export function migrateV6toV7(v6Config: PluginConfig): PluginConfigV7 {
18
+ return {
19
+ ...v6Config,
20
+ version: 7,
21
+ background: {
22
+ enabled: true,
23
+ maxConcurrent: 5,
24
+ defaultTimeout: 300000,
25
+ },
26
+ autonomy: {
27
+ enabled: false,
28
+ verification: "normal",
29
+ maxIterations: 10,
30
+ },
31
+ };
32
+ }
33
+
34
+ export const v7ConfigDefaults = {
35
+ background: {
36
+ enabled: true,
37
+ maxConcurrent: 5,
38
+ defaultTimeout: 300000,
39
+ },
40
+ autonomy: {
41
+ enabled: false,
42
+ verification: "normal",
43
+ maxIterations: 10,
44
+ },
45
+ } as const;
package/src/config.ts CHANGED
@@ -53,8 +53,8 @@ export const confidenceConfigSchema = z.object({
53
53
  });
54
54
 
55
55
  // Pre-compute full defaults for nested schema defaults
56
- const orchestratorDefaults = orchestratorConfigSchema.parse({});
57
- const confidenceDefaults = confidenceConfigSchema.parse({});
56
+ export const orchestratorDefaults = orchestratorConfigSchema.parse({});
57
+ export const confidenceDefaults = confidenceConfigSchema.parse({});
58
58
 
59
59
  // --- V2 schema (internal, for migration) ---
60
60
 
@@ -90,7 +90,7 @@ export const memoryConfigSchema = z.object({
90
90
  decayHalfLifeDays: z.number().min(7).max(365).default(90),
91
91
  });
92
92
 
93
- const memoryDefaults = memoryConfigSchema.parse({});
93
+ export const memoryDefaults = memoryConfigSchema.parse({});
94
94
 
95
95
  // --- V4 sub-schemas ---
96
96