agent-mockingbird 0.0.1

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 (227) hide show
  1. package/.agents/skills/btca-cli/SKILL.md +64 -0
  2. package/.agents/skills/btca-cli/agents/openai.yaml +3 -0
  3. package/.agents/skills/frontend-design/SKILL.md +42 -0
  4. package/.agents/skills/frontend-design/agents/openai.yaml +3 -0
  5. package/.env.example +36 -0
  6. package/.githooks/pre-commit +33 -0
  7. package/.github/workflows/ci.yml +309 -0
  8. package/.opencode/bun.lock +18 -0
  9. package/.opencode/package.json +5 -0
  10. package/.opencode/tools/agent_type_manager.ts +100 -0
  11. package/.opencode/tools/config_manager.ts +87 -0
  12. package/.opencode/tools/cron_manager.ts +145 -0
  13. package/.opencode/tools/memory_get.ts +43 -0
  14. package/.opencode/tools/memory_remember.ts +53 -0
  15. package/.opencode/tools/memory_search.ts +48 -0
  16. package/AGENTS.md +126 -0
  17. package/MEMORY.md +2 -0
  18. package/README.md +451 -0
  19. package/THIRD_PARTY_NOTICES.md +11 -0
  20. package/agent-mockingbird.config.example.json +135 -0
  21. package/apps/server/package.json +32 -0
  22. package/apps/server/src/backend/agents/bootstrapContext.ts +362 -0
  23. package/apps/server/src/backend/agents/openclawImport.test.ts +133 -0
  24. package/apps/server/src/backend/agents/openclawImport.ts +797 -0
  25. package/apps/server/src/backend/agents/opencodeConfig.ts +428 -0
  26. package/apps/server/src/backend/agents/service.ts +10 -0
  27. package/apps/server/src/backend/config/example-config.test.ts +20 -0
  28. package/apps/server/src/backend/config/orchestration.ts +243 -0
  29. package/apps/server/src/backend/config/policy.ts +158 -0
  30. package/apps/server/src/backend/config/schema.test.ts +15 -0
  31. package/apps/server/src/backend/config/schema.ts +391 -0
  32. package/apps/server/src/backend/config/semantic.test.ts +34 -0
  33. package/apps/server/src/backend/config/semantic.ts +149 -0
  34. package/apps/server/src/backend/config/service.test.ts +75 -0
  35. package/apps/server/src/backend/config/service.ts +207 -0
  36. package/apps/server/src/backend/config/smoke.ts +77 -0
  37. package/apps/server/src/backend/config/store.test.ts +123 -0
  38. package/apps/server/src/backend/config/store.ts +581 -0
  39. package/apps/server/src/backend/config/testFixtures.ts +5 -0
  40. package/apps/server/src/backend/config/types.ts +56 -0
  41. package/apps/server/src/backend/contracts/events.ts +320 -0
  42. package/apps/server/src/backend/contracts/runtime.ts +111 -0
  43. package/apps/server/src/backend/cron/executor.ts +435 -0
  44. package/apps/server/src/backend/cron/repository.ts +170 -0
  45. package/apps/server/src/backend/cron/service.ts +660 -0
  46. package/apps/server/src/backend/cron/storage.ts +92 -0
  47. package/apps/server/src/backend/cron/types.ts +138 -0
  48. package/apps/server/src/backend/cron/utils.ts +351 -0
  49. package/apps/server/src/backend/db/client.ts +20 -0
  50. package/apps/server/src/backend/db/migrate.ts +40 -0
  51. package/apps/server/src/backend/db/repository.ts +1762 -0
  52. package/apps/server/src/backend/db/schema.ts +113 -0
  53. package/apps/server/src/backend/db/usageDashboard.test.ts +102 -0
  54. package/apps/server/src/backend/db/wipe.ts +13 -0
  55. package/apps/server/src/backend/defaults.ts +32 -0
  56. package/apps/server/src/backend/env.ts +48 -0
  57. package/apps/server/src/backend/heartbeat/activeHours.ts +45 -0
  58. package/apps/server/src/backend/heartbeat/defaultJob.ts +88 -0
  59. package/apps/server/src/backend/heartbeat/heartbeat.test.ts +110 -0
  60. package/apps/server/src/backend/heartbeat/runtimeService.ts +190 -0
  61. package/apps/server/src/backend/heartbeat/service.ts +176 -0
  62. package/apps/server/src/backend/heartbeat/state.test.ts +63 -0
  63. package/apps/server/src/backend/heartbeat/state.ts +167 -0
  64. package/apps/server/src/backend/heartbeat/types.ts +54 -0
  65. package/apps/server/src/backend/http/boundedQueue.test.ts +49 -0
  66. package/apps/server/src/backend/http/boundedQueue.ts +92 -0
  67. package/apps/server/src/backend/http/parsers.ts +40 -0
  68. package/apps/server/src/backend/http/router.ts +61 -0
  69. package/apps/server/src/backend/http/routes/agentRoutes.ts +67 -0
  70. package/apps/server/src/backend/http/routes/backgroundRoutes.ts +203 -0
  71. package/apps/server/src/backend/http/routes/chatRoutes.ts +107 -0
  72. package/apps/server/src/backend/http/routes/configRoutes.ts +602 -0
  73. package/apps/server/src/backend/http/routes/cronRoutes.ts +221 -0
  74. package/apps/server/src/backend/http/routes/dashboardRoutes.ts +308 -0
  75. package/apps/server/src/backend/http/routes/eventRoutes.ts +7 -0
  76. package/apps/server/src/backend/http/routes/heartbeatRoutes.test.ts +41 -0
  77. package/apps/server/src/backend/http/routes/heartbeatRoutes.ts +28 -0
  78. package/apps/server/src/backend/http/routes/index.ts +101 -0
  79. package/apps/server/src/backend/http/routes/mcpRoutes.ts +213 -0
  80. package/apps/server/src/backend/http/routes/memoryRoutes.ts +154 -0
  81. package/apps/server/src/backend/http/routes/runRoutes.ts +310 -0
  82. package/apps/server/src/backend/http/routes/runtimeRoutes.ts +197 -0
  83. package/apps/server/src/backend/http/routes/skillRoutes.ts +112 -0
  84. package/apps/server/src/backend/http/routes/uiRoutes.test.ts +161 -0
  85. package/apps/server/src/backend/http/routes/uiRoutes.ts +177 -0
  86. package/apps/server/src/backend/http/routes/usageRoutes.test.ts +104 -0
  87. package/apps/server/src/backend/http/routes/usageRoutes.ts +767 -0
  88. package/apps/server/src/backend/http/schemas.ts +64 -0
  89. package/apps/server/src/backend/http/sse.ts +144 -0
  90. package/apps/server/src/backend/integration/backend-core.test.ts +2316 -0
  91. package/apps/server/src/backend/logging/logger.ts +64 -0
  92. package/apps/server/src/backend/mcp/service.ts +326 -0
  93. package/apps/server/src/backend/memory/cli.ts +170 -0
  94. package/apps/server/src/backend/memory/conceptExpansion.test.ts +28 -0
  95. package/apps/server/src/backend/memory/conceptExpansion.ts +80 -0
  96. package/apps/server/src/backend/memory/qmdPort.test.ts +54 -0
  97. package/apps/server/src/backend/memory/qmdPort.ts +61 -0
  98. package/apps/server/src/backend/memory/records.test.ts +66 -0
  99. package/apps/server/src/backend/memory/records.ts +229 -0
  100. package/apps/server/src/backend/memory/service.ts +2012 -0
  101. package/apps/server/src/backend/memory/sqliteVec.ts +58 -0
  102. package/apps/server/src/backend/memory/types.ts +104 -0
  103. package/apps/server/src/backend/opencode/agentMockingbirdPlugin.test.ts +396 -0
  104. package/apps/server/src/backend/opencode/client.ts +98 -0
  105. package/apps/server/src/backend/opencode/models.ts +41 -0
  106. package/apps/server/src/backend/opencode/systemPrompt.test.ts +146 -0
  107. package/apps/server/src/backend/opencode/systemPrompt.ts +284 -0
  108. package/apps/server/src/backend/paths.ts +57 -0
  109. package/apps/server/src/backend/prompts/service.ts +100 -0
  110. package/apps/server/src/backend/queue/queue.test.ts +189 -0
  111. package/apps/server/src/backend/queue/service.ts +177 -0
  112. package/apps/server/src/backend/queue/types.ts +39 -0
  113. package/apps/server/src/backend/run/service.ts +576 -0
  114. package/apps/server/src/backend/run/storage.ts +47 -0
  115. package/apps/server/src/backend/run/types.ts +44 -0
  116. package/apps/server/src/backend/runtime/errors.ts +61 -0
  117. package/apps/server/src/backend/runtime/index.ts +72 -0
  118. package/apps/server/src/backend/runtime/memoryPromptDedup.test.ts +153 -0
  119. package/apps/server/src/backend/runtime/memoryPromptDedup.ts +76 -0
  120. package/apps/server/src/backend/runtime/opencodeRuntime/backgroundMethods.ts +765 -0
  121. package/apps/server/src/backend/runtime/opencodeRuntime/coreMethods.ts +705 -0
  122. package/apps/server/src/backend/runtime/opencodeRuntime/eventMethods.ts +503 -0
  123. package/apps/server/src/backend/runtime/opencodeRuntime/memoryMethods.ts +462 -0
  124. package/apps/server/src/backend/runtime/opencodeRuntime/promptMethods.ts +1167 -0
  125. package/apps/server/src/backend/runtime/opencodeRuntime/shared.ts +254 -0
  126. package/apps/server/src/backend/runtime/opencodeRuntime.test.ts +2899 -0
  127. package/apps/server/src/backend/runtime/opencodeRuntime.ts +135 -0
  128. package/apps/server/src/backend/runtime/sessionScope.ts +45 -0
  129. package/apps/server/src/backend/skills/service.ts +442 -0
  130. package/apps/server/src/backend/workspace/resolve.ts +27 -0
  131. package/apps/server/src/cli/agent-mockingbird.mjs +2522 -0
  132. package/apps/server/src/cli/agent-mockingbird.test.ts +68 -0
  133. package/apps/server/src/cli/runtime-assets.mjs +269 -0
  134. package/apps/server/src/cli/runtime-assets.test.ts +52 -0
  135. package/apps/server/src/cli/runtime-layout.mjs +75 -0
  136. package/apps/server/src/cli/standaloneBuild.test.ts +19 -0
  137. package/apps/server/src/cli/standaloneBuild.ts +19 -0
  138. package/apps/server/src/cli/standaloneCronBinary.test.ts +187 -0
  139. package/apps/server/src/index.ts +178 -0
  140. package/apps/server/tsconfig.json +12 -0
  141. package/backlog.md +5 -0
  142. package/bin/agent-mockingbird +2522 -0
  143. package/bin/runtime-layout.mjs +75 -0
  144. package/build-bin.ts +34 -0
  145. package/build-cli.mjs +37 -0
  146. package/build.ts +40 -0
  147. package/bun-env.d.ts +11 -0
  148. package/bun.lock +888 -0
  149. package/bunfig.toml +2 -0
  150. package/components.json +21 -0
  151. package/config.json +130 -0
  152. package/deploy/RELEASE_INSTALL.md +112 -0
  153. package/deploy/docker-compose.yml +42 -0
  154. package/deploy/systemd/README.md +46 -0
  155. package/deploy/systemd/agent-mockingbird.service +28 -0
  156. package/deploy/systemd/opencode.service +25 -0
  157. package/docs/legacy-config-ui-reference.md +51 -0
  158. package/docs/memory-e2e-trace-2026-03-04.md +63 -0
  159. package/docs/memory-ops.md +96 -0
  160. package/docs/memory-runtime-contract.md +42 -0
  161. package/docs/memory-tuning-remote-2026-03-04.md +59 -0
  162. package/docs/opencode-rebase-workflow-plan.md +614 -0
  163. package/docs/opencode-startup-sync-plan.md +94 -0
  164. package/docs/vendor-opencode.md +41 -0
  165. package/drizzle/0000_famous_turbo.sql +49 -0
  166. package/drizzle/0001_cron_memory_aux.sql +160 -0
  167. package/drizzle/0002_runtime_session_bindings.sql +28 -0
  168. package/drizzle/0003_background_runs.sql +27 -0
  169. package/drizzle/0004_memory_open_write.sql +63 -0
  170. package/drizzle/0005_signal_channel.sql +47 -0
  171. package/drizzle/0006_usage_event_dimensions.sql +7 -0
  172. package/drizzle/meta/0000_snapshot.json +341 -0
  173. package/drizzle/meta/_journal.json +55 -0
  174. package/drizzle.config.ts +14 -0
  175. package/eslint.config.mjs +77 -0
  176. package/knip.json +18 -0
  177. package/memory/2026-03-04.md +4 -0
  178. package/opencode.lock.json +16 -0
  179. package/package.json +67 -0
  180. package/packages/agent-mockingbird-installer/README.md +31 -0
  181. package/packages/agent-mockingbird-installer/bin/agent-mockingbird-installer.mjs +44 -0
  182. package/packages/agent-mockingbird-installer/opencode.lock.json +16 -0
  183. package/packages/agent-mockingbird-installer/package.json +23 -0
  184. package/packages/contracts/package.json +19 -0
  185. package/packages/contracts/src/agentTypes.ts +122 -0
  186. package/packages/contracts/src/cron.ts +146 -0
  187. package/packages/contracts/src/dashboard.ts +378 -0
  188. package/packages/contracts/src/index.ts +3 -0
  189. package/packages/contracts/tsconfig.json +4 -0
  190. package/patches/opencode/0001-Wafflebot-OpenCode-baseline.patch +2341 -0
  191. package/patches/opencode/0002-Fix-OpenCode-web-entry-and-settings-icons.patch +104 -0
  192. package/patches/opencode/0003-fix-app-remove-duplicate-sidebar-mount.patch +32 -0
  193. package/patches/opencode/0004-Add-heartbeat-settings-and-usage-nav.patch +506 -0
  194. package/patches/opencode/0005-Use-chart-icon-for-usage-nav.patch +38 -0
  195. package/patches/opencode/0006-Modernize-cron-settings.patch +399 -0
  196. package/patches/opencode/0007-Rename-waffle-namespaces-to-mockingbird.patch +1110 -0
  197. package/patches/opencode/0008-Remove-cron-contract-section.patch +178 -0
  198. package/patches/opencode/0009-Rework-cron-tab-as-operations-console.patch +414 -0
  199. package/patches/opencode/0010-Refine-heartbeat-settings-controls.patch +208 -0
  200. package/runtime-assets/opencode-config/opencode.jsonc +25 -0
  201. package/runtime-assets/opencode-config/package.json +5 -0
  202. package/runtime-assets/opencode-config/plugins/agent-mockingbird.ts +715 -0
  203. package/runtime-assets/workspace/.agents/skills/config-auditor/SKILL.md +25 -0
  204. package/runtime-assets/workspace/.agents/skills/config-editor/SKILL.md +24 -0
  205. package/runtime-assets/workspace/.agents/skills/cron-manager/SKILL.md +57 -0
  206. package/runtime-assets/workspace/.agents/skills/memory-ops/SKILL.md +120 -0
  207. package/runtime-assets/workspace/.agents/skills/runtime-diagnose/SKILL.md +25 -0
  208. package/runtime-assets/workspace/AGENTS.md +56 -0
  209. package/runtime-assets/workspace/MEMORY.md +4 -0
  210. package/scripts/build-release-bundle.sh +66 -0
  211. package/scripts/check-ship.ts +383 -0
  212. package/scripts/dev-opencode.sh +17 -0
  213. package/scripts/dev-stack-opencode.sh +15 -0
  214. package/scripts/dev-stack.sh +61 -0
  215. package/scripts/install-systemd.sh +87 -0
  216. package/scripts/memory-e2e.sh +76 -0
  217. package/scripts/memory-trace-e2e.sh +141 -0
  218. package/scripts/migrate-opencode-env.ts +108 -0
  219. package/scripts/onboard/bootstrap.sh +32 -0
  220. package/scripts/opencode-swap.ts +78 -0
  221. package/scripts/opencode-sync.ts +715 -0
  222. package/scripts/runtime-assets-sync.mjs +83 -0
  223. package/scripts/setup-git-hooks.ts +39 -0
  224. package/tsconfig.json +45 -0
  225. package/tui.json +98 -0
  226. package/turbo.json +36 -0
  227. package/vendor/OPENCODE_VENDOR.md +13 -0
@@ -0,0 +1,581 @@
1
+ import { legacySpecialistToAgentType } from "@agent-mockingbird/contracts/agentTypes";
2
+ import { createHash } from "node:crypto";
3
+ import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { z } from "zod";
6
+
7
+ import { sqlite } from "../db/client";
8
+ import { DEFAULT_AGENTS, DEFAULT_AGENT_TYPES, DEFAULT_MCPS, DEFAULT_SKILLS } from "../defaults";
9
+ import { env } from "../env";
10
+ import { resolveDataPath } from "../paths";
11
+ import {
12
+ agentTypeDefinitionSchema,
13
+ agentMockingbirdConfigSchema,
14
+ specialistAgentSchema,
15
+ type AgentTypeDefinition,
16
+ type AgentMockingbirdConfig,
17
+ } from "./schema";
18
+ import { ConfigApplyError, type AgentMockingbirdConfigSnapshot } from "./types";
19
+ import { resolveWorkspaceAlignment } from "../workspace/resolve";
20
+
21
+ interface ConfigRow {
22
+ value_json: string;
23
+ }
24
+
25
+ const CONFIG_VERSION = 2 as const;
26
+ const DEFAULT_CONFIG_FILENAME = "agent-mockingbird.config.json";
27
+ const LEGACY_CONFIG_FILENAME = "mockingbird.config.json";
28
+ const BACKUP_SUFFIX = ".bak";
29
+ const DEFAULT_SMOKE_TEST_PROMPT = 'Just respond "OK" to this to confirm the gateway is working.';
30
+ const DEFAULT_SMOKE_TEST_PATTERN = "\\bok\\b";
31
+ const DEFAULT_OPENCODE_BASE_URL =
32
+ process.env.AGENT_MOCKINGBIRD_OPENCODE_BASE_URL?.trim() ||
33
+ `http://127.0.0.1:${process.env.OPENCODE_PORT?.trim() || "4096"}`;
34
+ const DEFAULT_OPENCODE_PROVIDER_ID = "opencode";
35
+ const DEFAULT_OPENCODE_MODEL_ID = "big-pickle";
36
+ const DEFAULT_OPENCODE_SMALL_MODEL = "opencode/big-pickle";
37
+ const DEFAULT_OPENCODE_TIMEOUT_MS = 120_000;
38
+ const DEFAULT_OPENCODE_PROMPT_TIMEOUT_MS = 300_000;
39
+ const DEFAULT_OPENCODE_RUN_WAIT_TIMEOUT_MS = 180_000;
40
+ const DEFAULT_HEARTBEAT_PROMPT =
41
+ 'Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.';
42
+ const legacyStringListSchema = z.array(z.string().min(1));
43
+ const legacyAgentListSchema = z.array(specialistAgentSchema);
44
+ const legacyAgentTypeListSchema = z.array(agentTypeDefinitionSchema);
45
+ type LegacyConfigKey = "skills" | "mcps" | "agents" | "agent_types";
46
+
47
+ function resolvedConfigPath() {
48
+ const configuredPath = env.AGENT_MOCKINGBIRD_CONFIG_PATH?.trim();
49
+ if (configuredPath) {
50
+ return path.resolve(configuredPath);
51
+ }
52
+ return resolveDataPath(DEFAULT_CONFIG_FILENAME);
53
+ }
54
+
55
+ function legacyDefaultConfigPath() {
56
+ return resolveDataPath(LEGACY_CONFIG_FILENAME);
57
+ }
58
+
59
+ function backupPathFor(configPath: string) {
60
+ return `${configPath}${BACKUP_SUFFIX}`;
61
+ }
62
+
63
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
64
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
65
+ }
66
+
67
+ function appearsToBeOpencodeConfig(raw: unknown) {
68
+ if (!isPlainObject(raw)) return false;
69
+ const schema = raw.$schema;
70
+ return typeof schema === "string" && schema.trim() === "https://opencode.ai/config.json";
71
+ }
72
+
73
+ function normalizeStringList(values: string[]) {
74
+ return [...new Set(values.map(value => value.trim()).filter(Boolean))];
75
+ }
76
+
77
+ function stableStringify(value: unknown): string {
78
+ if (value === null) return "null";
79
+ if (typeof value === "number" || typeof value === "boolean") return JSON.stringify(value);
80
+ if (typeof value === "string") return JSON.stringify(value);
81
+ if (Array.isArray(value)) return `[${value.map(item => stableStringify(item)).join(",")}]`;
82
+ if (!isPlainObject(value)) return JSON.stringify(value);
83
+ const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
84
+ const serializedEntries = entries.map(([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`);
85
+ return `{${serializedEntries.join(",")}}`;
86
+ }
87
+
88
+ function computeConfigHash(config: AgentMockingbirdConfig) {
89
+ return createHash("sha256").update(stableStringify(config)).digest("hex");
90
+ }
91
+
92
+ function hasExplicitEnvValue(key: string) {
93
+ const raw = process.env[key];
94
+ return typeof raw === "string" && raw.trim().length > 0;
95
+ }
96
+
97
+ function readExplicitEnvString(key: string) {
98
+ if (!hasExplicitEnvValue(key)) return undefined;
99
+ return process.env[key]?.trim();
100
+ }
101
+
102
+ function readExplicitEnvNumber(key: string) {
103
+ const raw = readExplicitEnvString(key);
104
+ if (!raw) return undefined;
105
+ const parsed = Number(raw);
106
+ return Number.isFinite(parsed) ? parsed : undefined;
107
+ }
108
+
109
+ function readExplicitEnvBoolean(key: string) {
110
+ const raw = readExplicitEnvString(key)?.toLowerCase();
111
+ if (!raw) return undefined;
112
+ if (raw === "true" || raw === "1") return true;
113
+ if (raw === "false" || raw === "0") return false;
114
+ return undefined;
115
+ }
116
+
117
+ function buildExplicitEnvConfigDefaultsPatch(): Record<string, unknown> {
118
+ const opencodePatch: Record<string, unknown> = {};
119
+ const opencodeBaseUrl = readExplicitEnvString("AGENT_MOCKINGBIRD_OPENCODE_BASE_URL");
120
+ if (opencodeBaseUrl) opencodePatch.baseUrl = opencodeBaseUrl;
121
+ const opencodeDirectory = readExplicitEnvString("AGENT_MOCKINGBIRD_OPENCODE_DIRECTORY");
122
+ if (opencodeDirectory) opencodePatch.directory = opencodeDirectory;
123
+ const opencodeProviderId = readExplicitEnvString("AGENT_MOCKINGBIRD_OPENCODE_PROVIDER_ID");
124
+ if (opencodeProviderId) opencodePatch.providerId = opencodeProviderId;
125
+ const opencodeModelId = readExplicitEnvString("AGENT_MOCKINGBIRD_OPENCODE_MODEL_ID");
126
+ if (opencodeModelId) opencodePatch.modelId = opencodeModelId;
127
+ const opencodeSmallModel = readExplicitEnvString("AGENT_MOCKINGBIRD_OPENCODE_SMALL_MODEL");
128
+ if (opencodeSmallModel) opencodePatch.smallModel = opencodeSmallModel;
129
+ const opencodeTimeoutMs = readExplicitEnvNumber("AGENT_MOCKINGBIRD_OPENCODE_TIMEOUT_MS");
130
+ if (typeof opencodeTimeoutMs === "number") opencodePatch.timeoutMs = opencodeTimeoutMs;
131
+ const opencodePromptTimeoutMs = readExplicitEnvNumber("AGENT_MOCKINGBIRD_OPENCODE_PROMPT_TIMEOUT_MS");
132
+ if (typeof opencodePromptTimeoutMs === "number") opencodePatch.promptTimeoutMs = opencodePromptTimeoutMs;
133
+ const opencodeRunWaitTimeoutMs = readExplicitEnvNumber("AGENT_MOCKINGBIRD_OPENCODE_RUN_WAIT_TIMEOUT_MS");
134
+ if (typeof opencodeRunWaitTimeoutMs === "number") opencodePatch.runWaitTimeoutMs = opencodeRunWaitTimeoutMs;
135
+
136
+ const memoryPatch: Record<string, unknown> = {};
137
+
138
+ const memoryEnabled = readExplicitEnvBoolean("AGENT_MOCKINGBIRD_MEMORY_ENABLED");
139
+ if (typeof memoryEnabled === "boolean") memoryPatch.enabled = memoryEnabled;
140
+ const memoryWorkspaceDir = readExplicitEnvString("AGENT_MOCKINGBIRD_MEMORY_WORKSPACE_DIR");
141
+ if (memoryWorkspaceDir) memoryPatch.workspaceDir = memoryWorkspaceDir;
142
+ const memoryEmbedProvider = readExplicitEnvString("AGENT_MOCKINGBIRD_MEMORY_EMBED_PROVIDER");
143
+ if (memoryEmbedProvider) memoryPatch.embedProvider = memoryEmbedProvider;
144
+ const memoryEmbedModel = readExplicitEnvString("AGENT_MOCKINGBIRD_MEMORY_EMBED_MODEL");
145
+ if (memoryEmbedModel) memoryPatch.embedModel = memoryEmbedModel;
146
+ const memoryOllamaBaseUrl = readExplicitEnvString("AGENT_MOCKINGBIRD_MEMORY_OLLAMA_BASE_URL");
147
+ if (memoryOllamaBaseUrl) memoryPatch.ollamaBaseUrl = memoryOllamaBaseUrl;
148
+ const memoryChunkTokens = readExplicitEnvNumber("AGENT_MOCKINGBIRD_MEMORY_CHUNK_TOKENS");
149
+ if (typeof memoryChunkTokens === "number") memoryPatch.chunkTokens = memoryChunkTokens;
150
+ const memoryChunkOverlap = readExplicitEnvNumber("AGENT_MOCKINGBIRD_MEMORY_CHUNK_OVERLAP");
151
+ if (typeof memoryChunkOverlap === "number") memoryPatch.chunkOverlap = memoryChunkOverlap;
152
+ const memoryMaxResults = readExplicitEnvNumber("AGENT_MOCKINGBIRD_MEMORY_MAX_RESULTS");
153
+ if (typeof memoryMaxResults === "number") memoryPatch.maxResults = memoryMaxResults;
154
+ const memoryMinScore = readExplicitEnvNumber("AGENT_MOCKINGBIRD_MEMORY_MIN_SCORE");
155
+ if (typeof memoryMinScore === "number") memoryPatch.minScore = memoryMinScore;
156
+ const memorySyncCooldownMs = readExplicitEnvNumber("AGENT_MOCKINGBIRD_MEMORY_SYNC_COOLDOWN_MS");
157
+ if (typeof memorySyncCooldownMs === "number") memoryPatch.syncCooldownMs = memorySyncCooldownMs;
158
+ const memoryToolMode = readExplicitEnvString("AGENT_MOCKINGBIRD_MEMORY_TOOL_MODE");
159
+ if (memoryToolMode) memoryPatch.toolMode = memoryToolMode;
160
+ const memoryInjectionDedupeEnabled = readExplicitEnvBoolean("AGENT_MOCKINGBIRD_MEMORY_INJECTION_DEDUPE_ENABLED");
161
+ if (typeof memoryInjectionDedupeEnabled === "boolean") memoryPatch.injectionDedupeEnabled = memoryInjectionDedupeEnabled;
162
+ const memoryInjectionDedupeFallbackRecallOnly = readExplicitEnvBoolean(
163
+ "AGENT_MOCKINGBIRD_MEMORY_INJECTION_DEDUPE_FALLBACK_RECALL_ONLY",
164
+ );
165
+ if (typeof memoryInjectionDedupeFallbackRecallOnly === "boolean") {
166
+ memoryPatch.injectionDedupeFallbackRecallOnly = memoryInjectionDedupeFallbackRecallOnly;
167
+ }
168
+ const memoryInjectionDedupeMaxTracked = readExplicitEnvNumber("AGENT_MOCKINGBIRD_MEMORY_INJECTION_DEDUPE_MAX_TRACKED");
169
+ if (typeof memoryInjectionDedupeMaxTracked === "number") {
170
+ memoryPatch.injectionDedupeMaxTracked = memoryInjectionDedupeMaxTracked;
171
+ }
172
+
173
+ if (!Object.keys(opencodePatch).length && !Object.keys(memoryPatch).length) {
174
+ return {};
175
+ }
176
+ return {
177
+ runtime: {
178
+ ...(Object.keys(opencodePatch).length ? { opencode: opencodePatch } : {}),
179
+ ...(Object.keys(memoryPatch).length ? { memory: memoryPatch } : {}),
180
+ },
181
+ };
182
+ }
183
+
184
+ function readLegacyConfigRow(key: LegacyConfigKey) {
185
+ const row = sqlite.query("SELECT value_json FROM runtime_config WHERE key = ?1").get(key) as ConfigRow | null;
186
+ if (!row?.value_json) return null;
187
+ try {
188
+ return JSON.parse(row.value_json) as unknown;
189
+ } catch {
190
+ return null;
191
+ }
192
+ }
193
+
194
+ function readLegacyStringListConfig(key: "skills" | "mcps", fallback: string[]) {
195
+ const value = readLegacyConfigRow(key);
196
+ const parsed = legacyStringListSchema.safeParse(value);
197
+ if (!parsed.success) return fallback;
198
+ return normalizeStringList(parsed.data);
199
+ }
200
+
201
+ function readLegacyAgentConfig(fallback: AgentMockingbirdConfig["ui"]["agents"]) {
202
+ const value = readLegacyConfigRow("agents");
203
+ const parsed = legacyAgentListSchema.safeParse(value);
204
+ if (!parsed.success) return fallback;
205
+ return parsed.data;
206
+ }
207
+
208
+ function readLegacyAgentTypeConfig(fallback: AgentMockingbirdConfig["ui"]["agentTypes"]) {
209
+ const value = readLegacyConfigRow("agent_types");
210
+ const parsed = legacyAgentTypeListSchema.safeParse(value);
211
+ if (!parsed.success) return fallback;
212
+ return parsed.data;
213
+ }
214
+
215
+ function mergeAgentTypesWithLegacyAgents(
216
+ agentTypes: AgentMockingbirdConfig["ui"]["agentTypes"],
217
+ agents: AgentMockingbirdConfig["ui"]["agents"],
218
+ ) {
219
+ const merged = new Map(agentTypes.map(agentType => [agentType.id, agentType]));
220
+ for (const agent of agents) {
221
+ const id = agent.id.trim();
222
+ if (!id || merged.has(id)) continue;
223
+ merged.set(id, legacySpecialistToAgentType(agent) as AgentTypeDefinition);
224
+ }
225
+ return [...merged.values()].sort((a, b) => a.id.localeCompare(b.id));
226
+ }
227
+
228
+ function normalizeOpencodeBaseUrl(baseUrl: string) {
229
+ const trimmed = baseUrl.trim();
230
+ if (!trimmed) return DEFAULT_OPENCODE_BASE_URL;
231
+ return trimmed;
232
+ }
233
+
234
+ function buildLegacyBootstrappedConfig() {
235
+ const candidate: AgentMockingbirdConfig = {
236
+ version: CONFIG_VERSION,
237
+ workspace: {
238
+ pinnedDirectory: env.AGENT_MOCKINGBIRD_MEMORY_WORKSPACE_DIR,
239
+ },
240
+ runtime: {
241
+ opencode: {
242
+ baseUrl: DEFAULT_OPENCODE_BASE_URL,
243
+ providerId: DEFAULT_OPENCODE_PROVIDER_ID,
244
+ modelId: DEFAULT_OPENCODE_MODEL_ID,
245
+ fallbackModels: [],
246
+ imageModel: null,
247
+ smallModel: DEFAULT_OPENCODE_SMALL_MODEL,
248
+ timeoutMs: DEFAULT_OPENCODE_TIMEOUT_MS,
249
+ promptTimeoutMs: DEFAULT_OPENCODE_PROMPT_TIMEOUT_MS,
250
+ runWaitTimeoutMs: DEFAULT_OPENCODE_RUN_WAIT_TIMEOUT_MS,
251
+ childSessionHideAfterDays: 3,
252
+ directory: env.AGENT_MOCKINGBIRD_MEMORY_WORKSPACE_DIR,
253
+ bootstrap: {
254
+ enabled: true,
255
+ maxCharsPerFile: 20_000,
256
+ maxCharsTotal: 150_000,
257
+ subagentMinimal: true,
258
+ includeAgentPrompt: true,
259
+ },
260
+ },
261
+ smokeTest: {
262
+ prompt: DEFAULT_SMOKE_TEST_PROMPT,
263
+ expectedResponsePattern: DEFAULT_SMOKE_TEST_PATTERN,
264
+ },
265
+ runStream: {
266
+ heartbeatMs: 15_000,
267
+ replayPageSize: 200,
268
+ },
269
+ memory: {
270
+ enabled: env.AGENT_MOCKINGBIRD_MEMORY_ENABLED,
271
+ workspaceDir: env.AGENT_MOCKINGBIRD_MEMORY_WORKSPACE_DIR,
272
+ embedProvider: env.AGENT_MOCKINGBIRD_MEMORY_EMBED_PROVIDER,
273
+ embedModel: env.AGENT_MOCKINGBIRD_MEMORY_EMBED_MODEL,
274
+ ollamaBaseUrl: env.AGENT_MOCKINGBIRD_MEMORY_OLLAMA_BASE_URL,
275
+ chunkTokens: env.AGENT_MOCKINGBIRD_MEMORY_CHUNK_TOKENS,
276
+ chunkOverlap: env.AGENT_MOCKINGBIRD_MEMORY_CHUNK_OVERLAP,
277
+ maxResults: env.AGENT_MOCKINGBIRD_MEMORY_MAX_RESULTS,
278
+ minScore: env.AGENT_MOCKINGBIRD_MEMORY_MIN_SCORE,
279
+ syncCooldownMs: env.AGENT_MOCKINGBIRD_MEMORY_SYNC_COOLDOWN_MS,
280
+ toolMode: env.AGENT_MOCKINGBIRD_MEMORY_TOOL_MODE,
281
+ injectionDedupeEnabled: env.AGENT_MOCKINGBIRD_MEMORY_INJECTION_DEDUPE_ENABLED,
282
+ injectionDedupeFallbackRecallOnly: env.AGENT_MOCKINGBIRD_MEMORY_INJECTION_DEDUPE_FALLBACK_RECALL_ONLY,
283
+ injectionDedupeMaxTracked: env.AGENT_MOCKINGBIRD_MEMORY_INJECTION_DEDUPE_MAX_TRACKED,
284
+ retrieval: {
285
+ engine: "qmd_hybrid",
286
+ strongSignalMinScore: 0.85,
287
+ strongSignalMinGap: 0.15,
288
+ candidateLimit: 40,
289
+ rrfK: 60,
290
+ expansionEnabled: true,
291
+ conceptExpansionEnabled: true,
292
+ conceptExpansionMaxPacks: 3,
293
+ conceptExpansionMaxTerms: 10,
294
+ rerankEnabled: true,
295
+ rerankTopN: 40,
296
+ semanticRescueEnabled: true,
297
+ semanticRescueMinVectorScore: 0.75,
298
+ semanticRescueMaxResults: 2,
299
+ expansionModel: null,
300
+ rerankModel: null,
301
+ vectorBackend: "sqlite_vec",
302
+ vectorUnavailableFallback: "disabled",
303
+ vectorK: 60,
304
+ vectorProbeLimit: 20,
305
+ },
306
+ },
307
+ heartbeat: {
308
+ enabled: true,
309
+ interval: "30m",
310
+ agentId: "build",
311
+ model: `${DEFAULT_OPENCODE_PROVIDER_ID}/${DEFAULT_OPENCODE_MODEL_ID}`,
312
+ prompt: DEFAULT_HEARTBEAT_PROMPT,
313
+ ackMaxChars: 300,
314
+ activeHours: null,
315
+ },
316
+ cron: {
317
+ defaultMaxAttempts: 3,
318
+ defaultRetryBackoffMs: 30_000,
319
+ retryBackoffCapMs: 3_600_000,
320
+ conditionalModuleTimeoutMs: 30_000,
321
+ },
322
+ queue: {
323
+ enabled: true,
324
+ defaultMode: "collect",
325
+ maxDepth: 10,
326
+ coalesceDebounceMs: 500,
327
+ },
328
+ configPolicy: {
329
+ mode: "builder",
330
+ denyPaths: ["version", "runtime.configPolicy", "runtime.smokeTest"],
331
+ strictAllowPaths: [
332
+ "runtime.opencode.runWaitTimeoutMs",
333
+ "runtime.opencode.childSessionHideAfterDays",
334
+ "runtime.opencode.bootstrap",
335
+ "runtime.runStream",
336
+ "runtime.memory",
337
+ "runtime.heartbeat",
338
+ "runtime.cron",
339
+ "runtime.queue",
340
+ "ui.skills",
341
+ "ui.mcps",
342
+ "ui.mcpServers",
343
+ "ui.agents",
344
+ "ui.agentTypes",
345
+ ],
346
+ requireExpectedHash: true,
347
+ requireSmokeTest: true,
348
+ autoRollbackOnFailure: true,
349
+ },
350
+ },
351
+ ui: {
352
+ skills: readLegacyStringListConfig("skills", DEFAULT_SKILLS),
353
+ mcps: readLegacyStringListConfig("mcps", DEFAULT_MCPS),
354
+ mcpServers: [],
355
+ agents: readLegacyAgentConfig(DEFAULT_AGENTS),
356
+ agentTypes: readLegacyAgentTypeConfig(DEFAULT_AGENT_TYPES),
357
+ },
358
+ };
359
+ candidate.ui.agentTypes = mergeAgentTypesWithLegacyAgents(candidate.ui.agentTypes, candidate.ui.agents);
360
+ return agentMockingbirdConfigSchema.parse(candidate);
361
+ }
362
+
363
+ function migrateConfigShape(raw: unknown): unknown {
364
+ if (!isPlainObject(raw)) return raw;
365
+ if (raw.version === CONFIG_VERSION && isPlainObject(raw.workspace)) {
366
+ return raw;
367
+ }
368
+
369
+ const root = { ...raw };
370
+ const runtime = isPlainObject(root.runtime) ? root.runtime : {};
371
+ const opencode = isPlainObject(runtime.opencode) ? runtime.opencode : {};
372
+ const memory = isPlainObject(runtime.memory) ? runtime.memory : {};
373
+ const pinnedDirectory =
374
+ (typeof root.workspace === "object" &&
375
+ root.workspace &&
376
+ !Array.isArray(root.workspace) &&
377
+ typeof (root.workspace as { pinnedDirectory?: unknown }).pinnedDirectory === "string"
378
+ ? (root.workspace as { pinnedDirectory: string }).pinnedDirectory
379
+ : undefined) ??
380
+ (typeof opencode.directory === "string" ? opencode.directory : undefined) ??
381
+ (typeof memory.workspaceDir === "string" ? memory.workspaceDir : undefined) ??
382
+ env.AGENT_MOCKINGBIRD_MEMORY_WORKSPACE_DIR;
383
+
384
+ return {
385
+ ...root,
386
+ version: CONFIG_VERSION,
387
+ workspace: {
388
+ pinnedDirectory,
389
+ },
390
+ };
391
+ }
392
+
393
+ function stripLegacyMemoryWriteConfig(raw: unknown): unknown {
394
+ if (!isPlainObject(raw)) return raw;
395
+ const root = { ...raw };
396
+ const runtime = isPlainObject(root.runtime) ? { ...root.runtime } : null;
397
+ if (!runtime) return root;
398
+ const memory = isPlainObject(runtime.memory) ? { ...runtime.memory } : null;
399
+ if (!memory) return root;
400
+
401
+ delete memory.writePolicy;
402
+ delete memory.minConfidence;
403
+
404
+ runtime.memory = memory;
405
+ root.runtime = runtime;
406
+ return root;
407
+ }
408
+
409
+ function migrateLegacyHeartbeatConfig(raw: unknown): unknown {
410
+ if (!isPlainObject(raw)) return raw;
411
+
412
+ const root = { ...raw };
413
+ const runtime = isPlainObject(root.runtime) ? { ...root.runtime } : {};
414
+ const ui = isPlainObject(root.ui) ? { ...root.ui } : {};
415
+ const agentTypes = Array.isArray(ui.agentTypes)
416
+ ? ui.agentTypes.map(value => (isPlainObject(value) ? { ...value } : value))
417
+ : [];
418
+
419
+ let derivedHeartbeat: Record<string, unknown> | null = null;
420
+ for (const rawAgentType of agentTypes) {
421
+ if (!isPlainObject(rawAgentType)) continue;
422
+ const heartbeat = isPlainObject(rawAgentType.heartbeat) ? rawAgentType.heartbeat : null;
423
+ if (!heartbeat) continue;
424
+
425
+ if (!derivedHeartbeat) {
426
+ const model =
427
+ typeof rawAgentType.model === "string" && rawAgentType.model.trim()
428
+ ? rawAgentType.model.trim()
429
+ : `${DEFAULT_OPENCODE_PROVIDER_ID}/${DEFAULT_OPENCODE_MODEL_ID}`;
430
+ derivedHeartbeat = {
431
+ enabled: typeof heartbeat.enabled === "boolean" ? heartbeat.enabled : true,
432
+ interval: typeof heartbeat.interval === "string" && heartbeat.interval.trim() ? heartbeat.interval.trim() : "30m",
433
+ agentId: typeof rawAgentType.id === "string" && rawAgentType.id.trim() ? rawAgentType.id.trim() : "build",
434
+ model,
435
+ prompt:
436
+ typeof heartbeat.prompt === "string" && heartbeat.prompt.trim()
437
+ ? heartbeat.prompt.trim()
438
+ : DEFAULT_HEARTBEAT_PROMPT,
439
+ ackMaxChars:
440
+ typeof heartbeat.ackMaxChars === "number" && Number.isFinite(heartbeat.ackMaxChars)
441
+ ? heartbeat.ackMaxChars
442
+ : 300,
443
+ activeHours: isPlainObject(heartbeat.activeHours) ? heartbeat.activeHours : null,
444
+ };
445
+ }
446
+
447
+ delete rawAgentType.heartbeat;
448
+ }
449
+
450
+ if (!isPlainObject(runtime.heartbeat) && derivedHeartbeat) {
451
+ runtime.heartbeat = derivedHeartbeat;
452
+ }
453
+ root.runtime = runtime;
454
+ if (agentTypes.length > 0) {
455
+ ui.agentTypes = agentTypes;
456
+ }
457
+ root.ui = ui;
458
+ return root;
459
+ }
460
+
461
+ export function parseConfig(raw: unknown) {
462
+ const normalized = migrateLegacyHeartbeatConfig(migrateConfigShape(stripLegacyMemoryWriteConfig(raw)));
463
+ if (appearsToBeOpencodeConfig(normalized)) {
464
+ throw new ConfigApplyError(
465
+ "schema",
466
+ "Config file appears to be OpenCode config.json, not Agent Mockingbird config. Set AGENT_MOCKINGBIRD_CONFIG_PATH to an Agent Mockingbird config file (default: ./data/agent-mockingbird.config.json).",
467
+ );
468
+ }
469
+ const withExplicitEnvDefaults = deepMerge(buildExplicitEnvConfigDefaultsPatch(), normalized);
470
+ const parsed = agentMockingbirdConfigSchema.safeParse(withExplicitEnvDefaults);
471
+ if (!parsed.success) {
472
+ throw new ConfigApplyError("schema", "Config schema validation failed", parsed.error.flatten());
473
+ }
474
+ const config = parsed.data;
475
+ config.runtime.opencode.baseUrl = normalizeOpencodeBaseUrl(config.runtime.opencode.baseUrl);
476
+ const workspaceAlignment = resolveWorkspaceAlignment(config);
477
+ const normalizedMemoryWorkspaceDir = workspaceAlignment.memoryWorkspaceDir;
478
+ config.workspace.pinnedDirectory = workspaceAlignment.opencodeWorkspaceDir;
479
+ config.runtime.opencode.directory = workspaceAlignment.opencodeWorkspaceDir;
480
+ config.runtime.memory.workspaceDir = normalizedMemoryWorkspaceDir;
481
+ config.ui.agentTypes = mergeAgentTypesWithLegacyAgents(config.ui.agentTypes, config.ui.agents);
482
+ return config;
483
+ }
484
+
485
+ function createSnapshot(configPath: string, config: AgentMockingbirdConfig): AgentMockingbirdConfigSnapshot {
486
+ const updatedAt = existsSync(configPath) ? new Date(statSync(configPath).mtimeMs).toISOString() : new Date().toISOString();
487
+ return {
488
+ path: configPath,
489
+ hash: computeConfigHash(config),
490
+ updatedAt,
491
+ config,
492
+ };
493
+ }
494
+
495
+ function readSnapshotFromDisk(configPath: string): AgentMockingbirdConfigSnapshot {
496
+ try {
497
+ const raw = readFileSync(configPath, "utf8");
498
+ const parsed = parseConfig(JSON.parse(raw) as unknown);
499
+ return createSnapshot(configPath, parsed);
500
+ } catch (error) {
501
+ if (error instanceof ConfigApplyError) {
502
+ throw error;
503
+ }
504
+ const message = error instanceof Error ? error.message : "Failed to parse config file";
505
+ throw new ConfigApplyError("schema", message);
506
+ }
507
+ }
508
+
509
+ function writeConfigAtomic(configPath: string, config: AgentMockingbirdConfig) {
510
+ const directory = path.dirname(configPath);
511
+ mkdirSync(directory, { recursive: true });
512
+
513
+ const tempPath = `${configPath}.${process.pid}.${Date.now()}.tmp`;
514
+ const backupPath = backupPathFor(configPath);
515
+ const serialized = `${JSON.stringify(config, null, 2)}\n`;
516
+
517
+ if (existsSync(configPath)) {
518
+ writeFileSync(backupPath, readFileSync(configPath));
519
+ }
520
+
521
+ writeFileSync(tempPath, serialized, "utf8");
522
+ renameSync(tempPath, configPath);
523
+ }
524
+
525
+ export function ensureConfigSnapshot() {
526
+ const configPath = resolvedConfigPath();
527
+ if (existsSync(configPath)) {
528
+ return readSnapshotFromDisk(configPath);
529
+ }
530
+ const legacyPath = legacyDefaultConfigPath();
531
+ if (!env.AGENT_MOCKINGBIRD_CONFIG_PATH && existsSync(legacyPath)) {
532
+ const snapshot = readSnapshotFromDisk(legacyPath);
533
+ writeConfigAtomic(configPath, snapshot.config);
534
+ return readSnapshotFromDisk(configPath);
535
+ }
536
+ const config = buildLegacyBootstrappedConfig();
537
+ writeConfigAtomic(configPath, config);
538
+ return readSnapshotFromDisk(configPath);
539
+ }
540
+
541
+ export function getConfigSnapshot() {
542
+ return ensureConfigSnapshot();
543
+ }
544
+
545
+ export function getConfig() {
546
+ return getConfigSnapshot().config;
547
+ }
548
+
549
+ export function mergeConfigPatch(baseConfig: AgentMockingbirdConfig, patch: unknown): unknown {
550
+ return deepMerge(baseConfig, patch);
551
+ }
552
+
553
+ function deepMerge(base: unknown, patch: unknown): unknown {
554
+ if (typeof patch === "undefined") return base;
555
+ if (Array.isArray(patch)) return patch;
556
+ if (isPlainObject(base) && isPlainObject(patch)) {
557
+ const merged: Record<string, unknown> = { ...base };
558
+ for (const [key, value] of Object.entries(patch)) {
559
+ if (typeof value === "undefined") continue;
560
+ merged[key] = key in merged ? deepMerge(merged[key], value) : value;
561
+ }
562
+ return merged;
563
+ }
564
+ return patch;
565
+ }
566
+
567
+ export function assertExpectedHashMatches(currentHash: string, expectedHash?: string) {
568
+ if (expectedHash && expectedHash !== currentHash) {
569
+ throw new ConfigApplyError("conflict", "Config has changed; refresh and retry");
570
+ }
571
+ }
572
+
573
+ export function persistConfigSnapshot(configPath: string, config: AgentMockingbirdConfig): AgentMockingbirdConfigSnapshot {
574
+ try {
575
+ writeConfigAtomic(configPath, config);
576
+ return ensureConfigSnapshot();
577
+ } catch (error) {
578
+ const message = error instanceof Error ? error.message : "Failed to write config file";
579
+ throw new ConfigApplyError("write", message);
580
+ }
581
+ }
@@ -0,0 +1,5 @@
1
+ import path from "node:path";
2
+
3
+ export function resolveExampleConfigPath() {
4
+ return path.resolve(import.meta.dir, "../../../../../agent-mockingbird.config.example.json");
5
+ }
@@ -0,0 +1,56 @@
1
+ import type { AgentMockingbirdConfig } from "./schema";
2
+
3
+ type ConfigApplyStage =
4
+ | "request"
5
+ | "conflict"
6
+ | "schema"
7
+ | "semantic"
8
+ | "smoke"
9
+ | "policy"
10
+ | "rollback"
11
+ | "write";
12
+
13
+ export class ConfigApplyError extends Error {
14
+ readonly stage: ConfigApplyStage;
15
+ readonly details?: unknown;
16
+
17
+ constructor(stage: ConfigApplyStage, message: string, details?: unknown) {
18
+ super(message);
19
+ this.name = "ConfigApplyError";
20
+ this.stage = stage;
21
+ this.details = details;
22
+ }
23
+ }
24
+
25
+ export interface AgentMockingbirdConfigSnapshot {
26
+ path: string;
27
+ hash: string;
28
+ updatedAt: string;
29
+ config: AgentMockingbirdConfig;
30
+ }
31
+
32
+ export interface ConfigSemanticSummary {
33
+ providerCount: number;
34
+ modelCount: number;
35
+ }
36
+
37
+ export interface ConfigSmokeTestSummary {
38
+ sessionId: string;
39
+ responseText: string;
40
+ }
41
+
42
+ export interface ConfigPolicySummary {
43
+ mode: "builder" | "strict";
44
+ changedPaths: string[];
45
+ rejectedPaths: string[];
46
+ requireExpectedHash: boolean;
47
+ requireSmokeTest: boolean;
48
+ autoRollbackOnFailure: boolean;
49
+ }
50
+
51
+ export interface ApplyConfigResult {
52
+ snapshot: AgentMockingbirdConfigSnapshot;
53
+ semantic: ConfigSemanticSummary;
54
+ smokeTest: ConfigSmokeTestSummary | null;
55
+ policy: ConfigPolicySummary | null;
56
+ }