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,602 @@
1
+ import { z } from "zod";
2
+
3
+ import { migrateOpenclawWorkspace } from "../../agents/openclawImport";
4
+ import {
5
+ getOpencodeAgentStorageInfo,
6
+ listOpencodeAgentTypes,
7
+ patchOpencodeAgentTypes,
8
+ validateOpencodeAgentPatch,
9
+ } from "../../agents/opencodeConfig";
10
+ import {
11
+ getEnabledSkillsFromCatalog,
12
+ importManagedSkillWithConfigUpdate,
13
+ loadRuntimeMcpCatalog,
14
+ loadRuntimeSkillCatalog,
15
+ runRuntimeMcpActionForCurrentConfig,
16
+ setEnabledSkillsFromCatalog,
17
+ } from "../../config/orchestration";
18
+ import { configuredMcpServerSchema } from "../../config/schema";
19
+ import {
20
+ applyConfigPatch,
21
+ applyConfigPatchSafe,
22
+ ConfigApplyError,
23
+ getConfigSnapshot,
24
+ replaceConfig,
25
+ replaceConfigSafe,
26
+ type ApplyConfigResult,
27
+ } from "../../config/service";
28
+ import {
29
+ createConfigRolledBackEvent,
30
+ createConfigUpdateFailedEvent,
31
+ createConfigUpdatedEvent,
32
+ createSkillsCatalogUpdatedEvent,
33
+ } from "../../contracts/events";
34
+ import {
35
+ normalizeMcpIds,
36
+ resolveConfiguredMcpIds,
37
+ resolveConfiguredMcpServers,
38
+ } from "../../mcp/service";
39
+ import { syncMemoryIndex } from "../../memory/service";
40
+ import { parseStringListBody } from "../parsers";
41
+ import type { RuntimeEventStream } from "../sse";
42
+
43
+ function toErrorResponse(error: unknown) {
44
+ if (error instanceof ConfigApplyError) {
45
+ if (error.stage === "conflict") {
46
+ return {
47
+ status: 409,
48
+ body: {
49
+ error: error.message,
50
+ stage: error.stage,
51
+ },
52
+ };
53
+ }
54
+ if (error.stage === "request" || error.stage === "schema") {
55
+ return {
56
+ status: 400,
57
+ body: {
58
+ error: error.message,
59
+ stage: error.stage,
60
+ details: error.details,
61
+ },
62
+ };
63
+ }
64
+ if (error.stage === "semantic" || error.stage === "smoke" || error.stage === "policy") {
65
+ return {
66
+ status: 422,
67
+ body: {
68
+ error: error.message,
69
+ stage: error.stage,
70
+ details: error.details,
71
+ },
72
+ };
73
+ }
74
+ return {
75
+ status: 500,
76
+ body: {
77
+ error: error.message,
78
+ stage: error.stage,
79
+ },
80
+ };
81
+ }
82
+
83
+ return {
84
+ status: 500,
85
+ body: {
86
+ error: error instanceof Error ? error.message : "Config update failed",
87
+ stage: "unknown",
88
+ },
89
+ };
90
+ }
91
+
92
+ function configErrorResponse(eventStream: RuntimeEventStream, error: unknown) {
93
+ publishConfigRollbackEvent(eventStream, error);
94
+ publishConfigFailedEvent(eventStream, error);
95
+ const details = toErrorResponse(error);
96
+ return Response.json(details.body, { status: details.status });
97
+ }
98
+
99
+ function publishConfigUpdatedEvent(eventStream: RuntimeEventStream, result: ApplyConfigResult) {
100
+ eventStream.publish(
101
+ createConfigUpdatedEvent(
102
+ {
103
+ hash: result.snapshot.hash,
104
+ path: result.snapshot.path,
105
+ providerCount: result.semantic.providerCount,
106
+ modelCount: result.semantic.modelCount,
107
+ smokeTestSessionId: result.smokeTest?.sessionId ?? null,
108
+ smokeTestResponse: result.smokeTest?.responseText ?? null,
109
+ },
110
+ "api",
111
+ ),
112
+ );
113
+ }
114
+
115
+ function publishConfigFailedEvent(eventStream: RuntimeEventStream, error: unknown) {
116
+ const details = toErrorResponse(error);
117
+ eventStream.publish(
118
+ createConfigUpdateFailedEvent(
119
+ {
120
+ stage: String((details.body as { stage?: unknown }).stage ?? "unknown"),
121
+ message: String((details.body as { error?: unknown }).error ?? "Config update failed"),
122
+ },
123
+ "api",
124
+ ),
125
+ );
126
+ }
127
+
128
+ function publishConfigRollbackEvent(eventStream: RuntimeEventStream, error: unknown) {
129
+ if (!(error instanceof ConfigApplyError)) return;
130
+ if (error.stage !== "smoke" && error.stage !== "rollback") return;
131
+ const details = (error.details ?? {}) as Record<string, unknown>;
132
+ if (details.rolledBack !== true && error.stage !== "rollback") return;
133
+ eventStream.publish(
134
+ createConfigRolledBackEvent(
135
+ {
136
+ attemptedHash: typeof details.attemptedHash === "string" ? details.attemptedHash : null,
137
+ restoredHash: typeof details.restoredHash === "string" ? details.restoredHash : null,
138
+ message: error.message,
139
+ },
140
+ "api",
141
+ ),
142
+ );
143
+ }
144
+
145
+ async function respondWithConfigMutation(
146
+ eventStream: RuntimeEventStream,
147
+ run: () => Promise<ApplyConfigResult>,
148
+ onSuccess: (result: ApplyConfigResult) => Response,
149
+ ) {
150
+ try {
151
+ const result = await run();
152
+ publishConfigUpdatedEvent(eventStream, result);
153
+ return onSuccess(result);
154
+ } catch (error) {
155
+ return configErrorResponse(eventStream, error);
156
+ }
157
+ }
158
+
159
+ function publishSkillsCatalogUpdated(eventStream: RuntimeEventStream, revision: string) {
160
+ eventStream.publish(createSkillsCatalogUpdatedEvent({ revision }, "api"));
161
+ }
162
+
163
+ async function applyMcpConfigUpdate(eventStream: RuntimeEventStream, req: Request) {
164
+ const body = (await req.json()) as Record<string, unknown>;
165
+ const parsedServers = z.array(configuredMcpServerSchema).safeParse(body.servers);
166
+ if (parsedServers.success) {
167
+ const servers = parsedServers.data;
168
+ const enabledIds = normalizeMcpIds(servers.filter(server => server.enabled).map(server => server.id));
169
+ return respondWithConfigMutation(
170
+ eventStream,
171
+ () =>
172
+ applyConfigPatch({
173
+ patch: { ui: { mcpServers: servers, mcps: enabledIds } },
174
+ expectedHash: typeof body.expectedHash === "string" ? body.expectedHash : undefined,
175
+ runSmokeTest: true,
176
+ }),
177
+ result =>
178
+ Response.json({
179
+ mcps: resolveConfiguredMcpIds(result.snapshot.config),
180
+ servers: resolveConfiguredMcpServers(result.snapshot.config),
181
+ hash: result.snapshot.hash,
182
+ smokeTest: result.smokeTest,
183
+ }),
184
+ );
185
+ }
186
+
187
+ const values = parseStringListBody(body, "mcps");
188
+ if (!values) {
189
+ return Response.json({ error: "mcps must be a string array or servers must be a valid MCP config array" }, { status: 400 });
190
+ }
191
+
192
+ const current = getConfigSnapshot();
193
+ const enabled = new Set(values);
194
+ const updatedServers = current.config.ui.mcpServers.map(server => ({
195
+ ...server,
196
+ enabled: enabled.has(server.id),
197
+ }));
198
+ return respondWithConfigMutation(
199
+ eventStream,
200
+ () =>
201
+ applyConfigPatch({
202
+ patch: { ui: { mcps: values, mcpServers: updatedServers } },
203
+ expectedHash: typeof body.expectedHash === "string" ? body.expectedHash : undefined,
204
+ runSmokeTest: true,
205
+ }),
206
+ result =>
207
+ Response.json({
208
+ mcps: resolveConfiguredMcpIds(result.snapshot.config),
209
+ servers: resolveConfiguredMcpServers(result.snapshot.config),
210
+ hash: result.snapshot.hash,
211
+ smokeTest: result.smokeTest,
212
+ }),
213
+ );
214
+ }
215
+
216
+ async function getOpencodeAgents() {
217
+ try {
218
+ const payload = await listOpencodeAgentTypes();
219
+ return Response.json({
220
+ agentTypes: payload.agentTypes,
221
+ hash: payload.hash,
222
+ storage: payload.storage,
223
+ source: "opencode",
224
+ });
225
+ } catch (error) {
226
+ const storage = getOpencodeAgentStorageInfo();
227
+ const message = error instanceof Error ? error.message : "Failed to load OpenCode agent definitions";
228
+ return Response.json(
229
+ {
230
+ agentTypes: [],
231
+ hash: "",
232
+ storage,
233
+ source: "opencode",
234
+ error: message,
235
+ },
236
+ { status: 502 },
237
+ );
238
+ }
239
+ }
240
+
241
+ async function runMcpAction(
242
+ req: Request & { params: { id: string } },
243
+ action: "connect" | "disconnect" | "authStart" | "authRemove",
244
+ ) {
245
+ const id = req.params.id.trim();
246
+ if (!id) {
247
+ return Response.json({ error: "MCP id is required" }, { status: 400 });
248
+ }
249
+
250
+ try {
251
+ const result = await runRuntimeMcpActionForCurrentConfig(id, action);
252
+ return Response.json(result);
253
+ } catch (error) {
254
+ return Response.json(
255
+ { error: error instanceof Error ? error.message : `Failed to ${action} MCP server ${id}` },
256
+ { status: 502 },
257
+ );
258
+ }
259
+ }
260
+
261
+ async function importManagedSkill(eventStream: RuntimeEventStream, req: Request) {
262
+ const body = (await req.json()) as Record<string, unknown>;
263
+ const rawId = typeof body.id === "string" ? body.id : "";
264
+ const content = typeof body.content === "string" ? body.content : "";
265
+ const enable = typeof body.enable === "boolean" ? body.enable : true;
266
+
267
+ try {
268
+ const imported = await importManagedSkillWithConfigUpdate({
269
+ rawId,
270
+ content,
271
+ enable,
272
+ expectedHash: typeof body.expectedHash === "string" ? body.expectedHash : undefined,
273
+ });
274
+ publishSkillsCatalogUpdated(eventStream, imported.hash);
275
+
276
+ return Response.json({
277
+ imported: {
278
+ id: imported.imported.id,
279
+ filePath: imported.imported.filePath,
280
+ },
281
+ skills: imported.skills,
282
+ hash: imported.hash,
283
+ });
284
+ } catch (error) {
285
+ if (error instanceof Error && error.message.includes("catalog has changed")) {
286
+ return Response.json({ error: error.message }, { status: 409 });
287
+ }
288
+ if (error instanceof Error && error.message.includes("already exists")) {
289
+ return Response.json({ error: error.message }, { status: 409 });
290
+ }
291
+ if (error instanceof Error && error.message.includes("skill id")) {
292
+ return Response.json({ error: error.message }, { status: 400 });
293
+ }
294
+ return configErrorResponse(eventStream, error);
295
+ }
296
+ }
297
+
298
+ const openclawImportSchema = z.object({
299
+ source: z.discriminatedUnion("mode", [
300
+ z.object({
301
+ mode: z.literal("local"),
302
+ path: z.string().min(1),
303
+ }),
304
+ z.object({
305
+ mode: z.literal("git"),
306
+ url: z.string().min(1),
307
+ ref: z.string().optional(),
308
+ }),
309
+ ]),
310
+ targetDirectory: z.string().optional(),
311
+ });
312
+
313
+ async function importOpenclawBootstrap(req: Request) {
314
+ const body = (await req.json()) as Record<string, unknown>;
315
+ let source: { mode: "local"; path: string } | { mode: "git"; url: string; ref?: string };
316
+ let targetDirectory: string | undefined;
317
+
318
+ if (body.source && typeof body.source === "object") {
319
+ const parsed = openclawImportSchema.safeParse(body);
320
+ if (!parsed.success) {
321
+ return Response.json(
322
+ {
323
+ error: "Invalid import request",
324
+ issues: parsed.error.issues.map(issue => ({
325
+ path: issue.path.join("."),
326
+ message: issue.message,
327
+ })),
328
+ },
329
+ { status: 400 },
330
+ );
331
+ }
332
+ source = parsed.data.source;
333
+ targetDirectory = parsed.data.targetDirectory;
334
+ } else {
335
+ const sourceDirectory = typeof body.sourceDirectory === "string" ? body.sourceDirectory.trim() : "";
336
+ if (!sourceDirectory) {
337
+ return Response.json({ error: "source.path or sourceDirectory is required" }, { status: 400 });
338
+ }
339
+ source = { mode: "local", path: sourceDirectory };
340
+ }
341
+
342
+ try {
343
+ const migration = await migrateOpenclawWorkspace({
344
+ source:
345
+ source.mode === "local"
346
+ ? { mode: "local", path: source.path }
347
+ : { mode: "git", url: source.url, ref: source.ref },
348
+ targetDirectory,
349
+ });
350
+ let memorySync: { attempted: boolean; completed: boolean; error?: string | null } = {
351
+ attempted: false,
352
+ completed: false,
353
+ error: null,
354
+ };
355
+ try {
356
+ await syncMemoryIndex();
357
+ memorySync = { attempted: true, completed: true, error: null };
358
+ } catch (error) {
359
+ memorySync = {
360
+ attempted: true,
361
+ completed: false,
362
+ error: error instanceof Error ? error.message : String(error),
363
+ };
364
+ }
365
+ return Response.json({ migration, memorySync });
366
+ } catch (error) {
367
+ const message = error instanceof Error ? error.message : "Failed to import OpenClaw workspace files";
368
+ return Response.json({ error: message }, { status: 422 });
369
+ }
370
+ }
371
+
372
+ export function createConfigRoutes(eventStream: RuntimeEventStream) {
373
+ return {
374
+ "/api/config": {
375
+ GET: () => {
376
+ const snapshot = getConfigSnapshot();
377
+ return Response.json(snapshot);
378
+ },
379
+ PATCH: async (req: Request) => {
380
+ const body = (await req.json()) as {
381
+ patch?: unknown;
382
+ expectedHash?: unknown;
383
+ runSmokeTest?: unknown;
384
+ };
385
+ return respondWithConfigMutation(
386
+ eventStream,
387
+ () =>
388
+ applyConfigPatch({
389
+ patch: body.patch,
390
+ expectedHash: typeof body.expectedHash === "string" ? body.expectedHash : undefined,
391
+ runSmokeTest: typeof body.runSmokeTest === "boolean" ? body.runSmokeTest : true,
392
+ }),
393
+ result => Response.json(result),
394
+ );
395
+ },
396
+ PUT: async (req: Request) => {
397
+ const body = (await req.json()) as {
398
+ config?: unknown;
399
+ expectedHash?: unknown;
400
+ runSmokeTest?: unknown;
401
+ };
402
+ return respondWithConfigMutation(
403
+ eventStream,
404
+ () =>
405
+ replaceConfig({
406
+ config: typeof body === "object" && body !== null && "config" in body ? body.config : body,
407
+ expectedHash: typeof body.expectedHash === "string" ? body.expectedHash : undefined,
408
+ runSmokeTest: typeof body.runSmokeTest === "boolean" ? body.runSmokeTest : true,
409
+ }),
410
+ result => Response.json(result),
411
+ );
412
+ },
413
+ },
414
+
415
+ "/api/config/patch-safe": {
416
+ POST: async (req: Request) => {
417
+ const body = (await req.json()) as {
418
+ patch?: unknown;
419
+ expectedHash?: unknown;
420
+ runSmokeTest?: unknown;
421
+ };
422
+ return respondWithConfigMutation(
423
+ eventStream,
424
+ () =>
425
+ applyConfigPatchSafe({
426
+ patch: body.patch,
427
+ expectedHash: typeof body.expectedHash === "string" ? body.expectedHash : undefined,
428
+ runSmokeTest: typeof body.runSmokeTest === "boolean" ? body.runSmokeTest : true,
429
+ }),
430
+ result => Response.json(result),
431
+ );
432
+ },
433
+ },
434
+
435
+ "/api/config/replace-safe": {
436
+ POST: async (req: Request) => {
437
+ const body = (await req.json()) as {
438
+ config?: unknown;
439
+ expectedHash?: unknown;
440
+ runSmokeTest?: unknown;
441
+ };
442
+ return respondWithConfigMutation(
443
+ eventStream,
444
+ () =>
445
+ replaceConfigSafe({
446
+ config: body.config,
447
+ expectedHash: typeof body.expectedHash === "string" ? body.expectedHash : undefined,
448
+ runSmokeTest: typeof body.runSmokeTest === "boolean" ? body.runSmokeTest : true,
449
+ }),
450
+ result => Response.json(result),
451
+ );
452
+ },
453
+ },
454
+
455
+ "/api/config/skills": {
456
+ GET: () => {
457
+ const current = getEnabledSkillsFromCatalog();
458
+ return Response.json({
459
+ skills: current.skills,
460
+ hash: current.hash,
461
+ managedPath: current.managedPath,
462
+ disabledPath: current.disabledPath,
463
+ });
464
+ },
465
+ PUT: async (req: Request) => {
466
+ const body = (await req.json()) as Record<string, unknown>;
467
+ const values = parseStringListBody(body, "skills");
468
+ if (!values) {
469
+ return Response.json({ error: "skills must be a string array" }, { status: 400 });
470
+ }
471
+ try {
472
+ const result = await setEnabledSkillsFromCatalog({
473
+ skills: values,
474
+ expectedHash: typeof body.expectedHash === "string" ? body.expectedHash : undefined,
475
+ });
476
+ publishSkillsCatalogUpdated(eventStream, result.hash);
477
+ return Response.json(result);
478
+ } catch (error) {
479
+ if (error instanceof Error && error.message.includes("catalog has changed")) {
480
+ return Response.json({ error: error.message }, { status: 409 });
481
+ }
482
+ return Response.json({ error: error instanceof Error ? error.message : "Failed to update skills" }, { status: 500 });
483
+ }
484
+ },
485
+ },
486
+
487
+ "/api/config/skills/catalog": {
488
+ GET: async () => {
489
+ const result = await loadRuntimeSkillCatalog();
490
+ return Response.json(result.payload, { status: result.status });
491
+ },
492
+ },
493
+
494
+ "/api/config/skills/import": {
495
+ POST: async (req: Request) => importManagedSkill(eventStream, req),
496
+ },
497
+
498
+ "/api/config/opencode/bootstrap/import-openclaw": {
499
+ POST: async (req: Request) => importOpenclawBootstrap(req),
500
+ },
501
+
502
+ "/api/config/mcps": {
503
+ GET: () => {
504
+ const snapshot = getConfigSnapshot();
505
+ return Response.json({
506
+ mcps: resolveConfiguredMcpIds(snapshot.config),
507
+ servers: resolveConfiguredMcpServers(snapshot.config),
508
+ hash: snapshot.hash,
509
+ });
510
+ },
511
+ PUT: async (req: Request) => applyMcpConfigUpdate(eventStream, req),
512
+ },
513
+
514
+ "/api/config/mcps/catalog": {
515
+ GET: async () => {
516
+ const result = await loadRuntimeMcpCatalog();
517
+ return Response.json(result.payload, { status: result.status });
518
+ },
519
+ },
520
+
521
+ "/api/config/mcps/:id/connect": {
522
+ POST: async (req: Request & { params: { id: string } }) => runMcpAction(req, "connect"),
523
+ },
524
+
525
+ "/api/config/mcps/:id/disconnect": {
526
+ POST: async (req: Request & { params: { id: string } }) => runMcpAction(req, "disconnect"),
527
+ },
528
+
529
+ "/api/config/mcps/:id/auth/start": {
530
+ POST: async (req: Request & { params: { id: string } }) => runMcpAction(req, "authStart"),
531
+ },
532
+
533
+ "/api/config/mcps/:id/auth/remove": {
534
+ POST: async (req: Request & { params: { id: string } }) => runMcpAction(req, "authRemove"),
535
+ },
536
+
537
+ "/api/opencode/agents": {
538
+ GET: async () => getOpencodeAgents(),
539
+ PATCH: async (req: Request) => {
540
+ const body = (await req.json()) as Record<string, unknown>;
541
+ const expectedHash = typeof body.expectedHash === "string" ? body.expectedHash.trim() : "";
542
+ if (!expectedHash) {
543
+ return Response.json({ error: "expectedHash is required" }, { status: 400 });
544
+ }
545
+ const validation = await validateOpencodeAgentPatch({
546
+ upserts: body.upserts,
547
+ deletes: body.deletes,
548
+ });
549
+ if (!validation.ok) {
550
+ return Response.json(
551
+ {
552
+ error: "Agent patch validation failed",
553
+ issues: validation.issues,
554
+ warnings: validation.warnings,
555
+ },
556
+ { status: 400 },
557
+ );
558
+ }
559
+ try {
560
+ const result = await patchOpencodeAgentTypes({
561
+ upserts: validation.normalized.upserts,
562
+ deletes: validation.normalized.deletes,
563
+ expectedHash,
564
+ });
565
+ if (!result.ok) {
566
+ return Response.json(
567
+ {
568
+ error: result.error,
569
+ hash: result.currentHash,
570
+ },
571
+ { status: result.status },
572
+ );
573
+ }
574
+ return Response.json({
575
+ agentTypes: result.agentTypes,
576
+ hash: result.hash,
577
+ storage: result.storage,
578
+ applied: result.applied,
579
+ });
580
+ } catch (error) {
581
+ return Response.json(
582
+ {
583
+ error: error instanceof Error ? error.message : "Failed to update OpenCode agent definitions",
584
+ },
585
+ { status: 502 },
586
+ );
587
+ }
588
+ },
589
+ },
590
+
591
+ "/api/opencode/agents/validate": {
592
+ POST: async (req: Request) => {
593
+ const body = (await req.json()) as Record<string, unknown>;
594
+ const validation = await validateOpencodeAgentPatch({
595
+ upserts: body.upserts,
596
+ deletes: body.deletes,
597
+ });
598
+ return Response.json(validation);
599
+ },
600
+ },
601
+ };
602
+ }