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,92 @@
1
+ interface BoundedQueueOptions<T> {
2
+ maxSize: number;
3
+ drainDelayMs?: number;
4
+ tryWrite: (value: T) => boolean;
5
+ onOverflow: () => void;
6
+ onWriteError?: (error: unknown) => void;
7
+ }
8
+
9
+ export interface BoundedQueue<T> {
10
+ enqueue: (value: T) => boolean;
11
+ close: () => void;
12
+ size: () => number;
13
+ }
14
+
15
+ export function createBoundedQueue<T>(options: BoundedQueueOptions<T>): BoundedQueue<T> {
16
+ const drainDelayMs = Math.max(1, options.drainDelayMs ?? 25);
17
+ const queue: T[] = [];
18
+ let closed = false;
19
+ let draining = false;
20
+ let drainTimer: ReturnType<typeof setTimeout> | null = null;
21
+
22
+ const clearDrainTimer = () => {
23
+ if (!drainTimer) return;
24
+ clearTimeout(drainTimer);
25
+ drainTimer = null;
26
+ };
27
+
28
+ const scheduleDrain = () => {
29
+ if (closed || draining || drainTimer) return;
30
+ drainTimer = setTimeout(() => {
31
+ drainTimer = null;
32
+ drain();
33
+ }, drainDelayMs);
34
+ };
35
+
36
+ const handleWriteError = (error: unknown) => {
37
+ if (closed) return;
38
+ closed = true;
39
+ queue.length = 0;
40
+ clearDrainTimer();
41
+ options.onWriteError?.(error);
42
+ };
43
+
44
+ const drain = () => {
45
+ if (closed || draining) return;
46
+ draining = true;
47
+ try {
48
+ while (!closed && queue.length > 0) {
49
+ const next = queue[0] as T;
50
+ let wrote = false;
51
+ try {
52
+ wrote = options.tryWrite(next);
53
+ } catch (error) {
54
+ handleWriteError(error);
55
+ return;
56
+ }
57
+ if (!wrote) {
58
+ scheduleDrain();
59
+ return;
60
+ }
61
+ queue.shift();
62
+ }
63
+ } finally {
64
+ draining = false;
65
+ }
66
+ };
67
+
68
+ return {
69
+ enqueue(value: T) {
70
+ if (closed) return false;
71
+ if (queue.length >= options.maxSize) {
72
+ closed = true;
73
+ queue.length = 0;
74
+ clearDrainTimer();
75
+ options.onOverflow();
76
+ return false;
77
+ }
78
+ queue.push(value);
79
+ drain();
80
+ scheduleDrain();
81
+ return true;
82
+ },
83
+ close() {
84
+ closed = true;
85
+ queue.length = 0;
86
+ clearDrainTimer();
87
+ },
88
+ size() {
89
+ return queue.length;
90
+ },
91
+ };
92
+ }
@@ -0,0 +1,40 @@
1
+ import type { MemoryRecordSource } from "../memory/types";
2
+
3
+ export function parseStringListBody(body: unknown, field: string): string[] | null {
4
+ if (!body || typeof body !== "object") return null;
5
+ const value = (body as Record<string, unknown>)[field];
6
+ if (!Array.isArray(value) || value.some(item => typeof item !== "string")) {
7
+ return null;
8
+ }
9
+ return value;
10
+ }
11
+
12
+ export function parseMemoryRememberBody(body: unknown) {
13
+ if (!body || typeof body !== "object") return null;
14
+ const value = body as Record<string, unknown>;
15
+ const source = typeof value.source === "string" ? value.source.trim() : "user";
16
+ const content = typeof value.content === "string" ? value.content.trim() : "";
17
+ const sessionId = typeof value.sessionId === "string" ? value.sessionId.trim() : undefined;
18
+ const topic = typeof value.topic === "string" ? value.topic.trim() : undefined;
19
+ const ttl = typeof value.ttl === "number" ? value.ttl : undefined;
20
+ const entities = Array.isArray(value.entities)
21
+ ? value.entities.filter((item): item is string => typeof item === "string")
22
+ : [];
23
+ const supersedes = Array.isArray(value.supersedes)
24
+ ? value.supersedes.filter((item): item is string => typeof item === "string")
25
+ : [];
26
+ const confidence = typeof value.confidence === "number" ? value.confidence : undefined;
27
+ if (!content) return null;
28
+ const allowedSources = ["user", "assistant", "system"] as const;
29
+ if (!allowedSources.includes(source as MemoryRecordSource)) return null;
30
+ return {
31
+ source: source as MemoryRecordSource,
32
+ content,
33
+ entities,
34
+ supersedes,
35
+ confidence,
36
+ sessionId: sessionId || undefined,
37
+ topic: topic || undefined,
38
+ ttl,
39
+ };
40
+ }
@@ -0,0 +1,61 @@
1
+ interface RouteRequest<TParams extends Record<string, string> = Record<string, string>>
2
+ extends Request {
3
+ params: TParams;
4
+ }
5
+
6
+ type RouteMethod<TParams extends Record<string, string> = Record<string, string>> = {
7
+ bivarianceHack(req: RouteRequest<TParams>): Response | Promise<Response>;
8
+ }["bivarianceHack"];
9
+
10
+ interface RouteHandler {
11
+ GET?: RouteMethod;
12
+ POST?: RouteMethod;
13
+ PUT?: RouteMethod;
14
+ PATCH?: RouteMethod;
15
+ DELETE?: RouteMethod;
16
+ OPTIONS?: RouteMethod;
17
+ }
18
+
19
+ export type RouteEntry = RouteHandler | RouteMethod;
20
+
21
+ export type RouteTable = Record<string, RouteEntry>;
22
+
23
+ function matchPattern(pattern: string, pathname: string) {
24
+ const patternParts = pattern.split("/").filter(Boolean);
25
+ const pathParts = pathname.split("/").filter(Boolean);
26
+ if (patternParts.length !== pathParts.length) return null;
27
+
28
+ const params: Record<string, string> = {};
29
+ for (let index = 0; index < patternParts.length; index += 1) {
30
+ const patternPart = patternParts[index]!;
31
+ const pathPart = pathParts[index]!;
32
+ if (patternPart.startsWith(":")) {
33
+ params[patternPart.slice(1)] = decodeURIComponent(pathPart);
34
+ continue;
35
+ }
36
+ if (patternPart !== pathPart) return null;
37
+ }
38
+ return params;
39
+ }
40
+
41
+ export async function dispatchRoute(table: RouteTable, req: Request) {
42
+ const url = new URL(req.url);
43
+ const pathname = url.pathname;
44
+ const method = req.method.toUpperCase() as keyof RouteHandler;
45
+
46
+ for (const [pattern, handlers] of Object.entries(table)) {
47
+ const params = matchPattern(pattern, pathname);
48
+ if (!params) continue;
49
+ const request = Object.assign(req, { params }) as RouteRequest;
50
+ if (typeof handlers === "function") {
51
+ return handlers(request);
52
+ }
53
+ const handler = handlers[method];
54
+ if (!handler) {
55
+ return new Response("Method not allowed", { status: 405 });
56
+ }
57
+ return handler(request);
58
+ }
59
+
60
+ return null;
61
+ }
@@ -0,0 +1,67 @@
1
+ import {
2
+ getOpencodeAgentStorageInfo,
3
+ listOpencodeAgentTypes,
4
+ patchOpencodeAgentTypes,
5
+ validateOpencodeAgentPatch,
6
+ } from "../../agents/opencodeConfig";
7
+
8
+ export function createAgentRoutes() {
9
+ return {
10
+ "/api/mockingbird/agents": {
11
+ GET: async () => {
12
+ try {
13
+ const payload = await listOpencodeAgentTypes();
14
+ return Response.json(payload);
15
+ } catch (error) {
16
+ const storage = getOpencodeAgentStorageInfo();
17
+ const message = error instanceof Error ? error.message : "Failed to load OpenCode agents";
18
+ return Response.json(
19
+ {
20
+ agentTypes: [],
21
+ hash: "",
22
+ storage,
23
+ error: message,
24
+ },
25
+ { status: 502 },
26
+ );
27
+ }
28
+ },
29
+ PATCH: async (req: Request) => {
30
+ const body = (await req.json()) as {
31
+ upserts?: unknown;
32
+ deletes?: unknown;
33
+ expectedHash?: unknown;
34
+ };
35
+ if (typeof body.expectedHash !== "string" || !body.expectedHash.trim()) {
36
+ return Response.json({ error: "expectedHash is required" }, { status: 400 });
37
+ }
38
+
39
+ const validation = await validateOpencodeAgentPatch({
40
+ upserts: body.upserts ?? [],
41
+ deletes: body.deletes ?? [],
42
+ });
43
+ if (!validation.ok) {
44
+ return Response.json(validation, { status: 422 });
45
+ }
46
+
47
+ const result = await patchOpencodeAgentTypes({
48
+ upserts: validation.normalized.upserts,
49
+ deletes: validation.normalized.deletes,
50
+ expectedHash: body.expectedHash,
51
+ });
52
+ return Response.json(result, { status: result.ok ? 200 : result.status });
53
+ },
54
+ },
55
+
56
+ "/api/mockingbird/agents/validate": {
57
+ POST: async (req: Request) => {
58
+ const body = (await req.json()) as { upserts?: unknown; deletes?: unknown };
59
+ const result = await validateOpencodeAgentPatch({
60
+ upserts: body.upserts ?? [],
61
+ deletes: body.deletes ?? [],
62
+ });
63
+ return Response.json(result, { status: result.ok ? 200 : 422 });
64
+ },
65
+ },
66
+ };
67
+ }
@@ -0,0 +1,203 @@
1
+ import type { RuntimeEngine , RuntimeInputPart } from "../../contracts/runtime";
2
+ import { getSessionById } from "../../db/repository";
3
+
4
+ function parseBoolean(value: string | null) {
5
+ if (!value) return false;
6
+ const normalized = value.trim().toLowerCase();
7
+ return normalized === "1" || normalized === "true" || normalized === "yes";
8
+ }
9
+
10
+ function normalizeRuntimeInputParts(value: unknown): RuntimeInputPart[] {
11
+ if (!Array.isArray(value)) return [];
12
+ const parts: RuntimeInputPart[] = [];
13
+ for (const raw of value) {
14
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
15
+ const record = raw as Record<string, unknown>;
16
+ if (record.type === "text") {
17
+ const text = typeof record.text === "string" ? record.text : "";
18
+ if (!text.trim()) continue;
19
+ parts.push({ type: "text", text });
20
+ continue;
21
+ }
22
+ if (record.type === "file") {
23
+ const mime = typeof record.mime === "string" ? record.mime.trim() : "";
24
+ const url = typeof record.url === "string" ? record.url.trim() : "";
25
+ const filename = typeof record.filename === "string" ? record.filename.trim() || undefined : undefined;
26
+ if (!mime || !url) continue;
27
+ parts.push({ type: "file", mime, url, filename });
28
+ }
29
+ }
30
+ return parts;
31
+ }
32
+
33
+ export function createBackgroundRoutes(runtime: RuntimeEngine) {
34
+ return {
35
+ "/api/background": {
36
+ GET: async (req: Request) => {
37
+ if (!runtime.listBackgroundRuns) {
38
+ return Response.json({ error: "Runtime does not support background listing" }, { status: 501 });
39
+ }
40
+ try {
41
+ const url = new URL(req.url);
42
+ const parentSessionId = url.searchParams.get("sessionId")?.trim() || undefined;
43
+ const limitRaw = Number(url.searchParams.get("limit") ?? "100");
44
+ const limit = Number.isFinite(limitRaw) ? Math.max(1, Math.min(500, Math.floor(limitRaw))) : 100;
45
+ const inFlightOnly = parseBoolean(url.searchParams.get("inFlightOnly"));
46
+ const runs = await runtime.listBackgroundRuns({
47
+ parentSessionId,
48
+ limit,
49
+ inFlightOnly,
50
+ });
51
+ return Response.json({ runs });
52
+ } catch (error) {
53
+ const message = error instanceof Error ? error.message : "Failed to list background runs";
54
+ return Response.json({ error: message }, { status: 502 });
55
+ }
56
+ },
57
+ POST: async (req: Request) => {
58
+ if (!runtime.spawnBackgroundSession) {
59
+ return Response.json({ error: "Runtime does not support background sessions" }, { status: 501 });
60
+ }
61
+
62
+ const body = (await req.json()) as {
63
+ sessionId?: string;
64
+ title?: string;
65
+ requestedBy?: string;
66
+ prompt?: string;
67
+ parts?: RuntimeInputPart[];
68
+ model?: string;
69
+ system?: string;
70
+ agent?: string;
71
+ noReply?: boolean;
72
+ };
73
+ const sessionId = body.sessionId?.trim();
74
+ if (!sessionId) {
75
+ return Response.json({ error: "sessionId is required" }, { status: 400 });
76
+ }
77
+ if (!getSessionById(sessionId)) {
78
+ return Response.json({ error: "Unknown session" }, { status: 404 });
79
+ }
80
+
81
+ const parts = normalizeRuntimeInputParts(body.parts);
82
+ try {
83
+ const run = await runtime.spawnBackgroundSession({
84
+ parentSessionId: sessionId,
85
+ title: body.title,
86
+ requestedBy: body.requestedBy,
87
+ prompt: body.prompt,
88
+ });
89
+ if (body.prompt?.trim() || parts.length > 0) {
90
+ if (!runtime.promptBackgroundAsync) {
91
+ return Response.json({ error: "Runtime does not support background prompting" }, { status: 501 });
92
+ }
93
+ const updated = await runtime.promptBackgroundAsync({
94
+ runId: run.runId,
95
+ content: body.prompt?.trim() ?? "",
96
+ parts,
97
+ model: body.model,
98
+ system: body.system,
99
+ agent: body.agent,
100
+ noReply: body.noReply,
101
+ });
102
+ return Response.json({ run: updated }, { status: 202 });
103
+ }
104
+ return Response.json({ run }, { status: 202 });
105
+ } catch (error) {
106
+ const message = error instanceof Error ? error.message : "Failed to spawn background run";
107
+ return Response.json({ error: message }, { status: 502 });
108
+ }
109
+ },
110
+ },
111
+
112
+ "/api/background/:id": {
113
+ GET: async (req: Request & { params: { id: string } }) => {
114
+ if (!runtime.getBackgroundStatus) {
115
+ return Response.json({ error: "Runtime does not support background status" }, { status: 501 });
116
+ }
117
+ try {
118
+ const run = await runtime.getBackgroundStatus(req.params.id);
119
+ if (!run) {
120
+ return Response.json({ error: "Unknown background run" }, { status: 404 });
121
+ }
122
+ return Response.json({ run });
123
+ } catch (error) {
124
+ const message = error instanceof Error ? error.message : "Failed to fetch background run";
125
+ return Response.json({ error: message }, { status: 502 });
126
+ }
127
+ },
128
+ },
129
+
130
+ "/api/background/:id/abort": {
131
+ POST: async (req: Request & { params: { id: string } }) => {
132
+ if (!runtime.abortBackground) {
133
+ return Response.json({ error: "Runtime does not support background abort" }, { status: 501 });
134
+ }
135
+ try {
136
+ const aborted = await runtime.abortBackground(req.params.id);
137
+ return Response.json({ aborted });
138
+ } catch (error) {
139
+ const message = error instanceof Error ? error.message : "Failed to abort background run";
140
+ return Response.json({ error: message }, { status: 502 });
141
+ }
142
+ },
143
+ },
144
+
145
+ "/api/background/:id/steer": {
146
+ POST: async (req: Request & { params: { id: string } }) => {
147
+ if (!runtime.promptBackgroundAsync) {
148
+ return Response.json({ error: "Runtime does not support background steering" }, { status: 501 });
149
+ }
150
+ const body = (await req.json()) as {
151
+ content?: string;
152
+ parts?: RuntimeInputPart[];
153
+ model?: string;
154
+ system?: string;
155
+ agent?: string;
156
+ noReply?: boolean;
157
+ };
158
+ const content = body.content?.trim();
159
+ const parts = normalizeRuntimeInputParts(body.parts);
160
+ if (!content && parts.length === 0) {
161
+ return Response.json({ error: "content or parts is required" }, { status: 400 });
162
+ }
163
+ try {
164
+ const run = await runtime.promptBackgroundAsync({
165
+ runId: req.params.id,
166
+ content: content ?? "",
167
+ parts,
168
+ model: body.model,
169
+ system: body.system,
170
+ agent: body.agent,
171
+ noReply: body.noReply,
172
+ });
173
+ return Response.json({ run }, { status: 202 });
174
+ } catch (error) {
175
+ const message = error instanceof Error ? error.message : "Failed to steer background run";
176
+ return Response.json({ error: message }, { status: 502 });
177
+ }
178
+ },
179
+ },
180
+
181
+ "/api/sessions/:id/background": {
182
+ GET: async (req: Request & { params: { id: string } }) => {
183
+ if (!runtime.listBackgroundRuns) {
184
+ return Response.json({ error: "Runtime does not support background listing" }, { status: 501 });
185
+ }
186
+ const session = getSessionById(req.params.id);
187
+ if (!session) {
188
+ return Response.json({ error: "Unknown session" }, { status: 404 });
189
+ }
190
+ try {
191
+ const runs = await runtime.listBackgroundRuns({
192
+ parentSessionId: req.params.id,
193
+ limit: 200,
194
+ });
195
+ return Response.json({ runs });
196
+ } catch (error) {
197
+ const message = error instanceof Error ? error.message : "Failed to list session background runs";
198
+ return Response.json({ error: message }, { status: 502 });
199
+ }
200
+ },
201
+ },
202
+ };
203
+ }
@@ -0,0 +1,107 @@
1
+ import type { RuntimeEngine } from "../../contracts/runtime";
2
+ import { getSessionById } from "../../db/repository";
3
+ import { RuntimeSessionBusyError, RuntimeSessionNotFoundError, RuntimeTurnTimeoutError } from "../../runtime";
4
+
5
+ function preflightFailureStatusCode(name: string | undefined, message: string | undefined) {
6
+ if (
7
+ name === "RuntimeProviderAuthError" ||
8
+ name === "RuntimeProviderQuotaError" ||
9
+ name === "RuntimeProviderRateLimitError"
10
+ ) {
11
+ return 502;
12
+ }
13
+ if (message && /timed out/i.test(message)) {
14
+ return 504;
15
+ }
16
+ return 503;
17
+ }
18
+
19
+ export function createChatRoutes(runtime: RuntimeEngine) {
20
+ return {
21
+ "/api/chat": {
22
+ POST: async (req: Request) => {
23
+ const body = (await req.json()) as { sessionId?: string; content?: string };
24
+ const content = body.content?.trim();
25
+ if (!body.sessionId || !content) {
26
+ return Response.json({ error: "sessionId and content are required" }, { status: 400 });
27
+ }
28
+
29
+ if (runtime.checkHealth) {
30
+ try {
31
+ const health = await runtime.checkHealth();
32
+ if (!health.ok) {
33
+ const message = health.error?.message ?? "Runtime health check failed";
34
+ const status = preflightFailureStatusCode(health.error?.name, health.error?.message);
35
+ return Response.json(
36
+ {
37
+ error: `Runtime preflight failed: ${message}`,
38
+ health,
39
+ },
40
+ { status },
41
+ );
42
+ }
43
+ } catch (error) {
44
+ const message = error instanceof Error ? error.message : "Runtime health check failed";
45
+ return Response.json({ error: `Runtime preflight failed: ${message}` }, { status: 503 });
46
+ }
47
+ }
48
+
49
+ let ack;
50
+ try {
51
+ ack = await runtime.sendUserMessage({
52
+ sessionId: body.sessionId,
53
+ content,
54
+ });
55
+ } catch (error) {
56
+ if (error instanceof RuntimeSessionNotFoundError) {
57
+ return Response.json({ error: "Unknown session" }, { status: 404 });
58
+ }
59
+ if (error instanceof RuntimeSessionBusyError) {
60
+ return Response.json({ error: "Session is already processing a request" }, { status: 409 });
61
+ }
62
+ if (error instanceof RuntimeTurnTimeoutError) {
63
+ return Response.json({ error: "Runtime timed out waiting for OpenCode to finish" }, { status: 504 });
64
+ }
65
+ const message = error instanceof Error ? error.message : "Runtime request failed";
66
+ return Response.json({ error: message }, { status: 502 });
67
+ }
68
+
69
+ const session = getSessionById(ack.sessionId);
70
+ if (!session) {
71
+ return Response.json({ error: "Unknown session" }, { status: 404 });
72
+ }
73
+
74
+ return Response.json({
75
+ messages: ack.messages,
76
+ session,
77
+ });
78
+ },
79
+ },
80
+ "/api/chat/:id/abort": {
81
+ POST: async (req: Request & { params: { id: string } }) => {
82
+ if (!runtime.abortSession) {
83
+ return Response.json({ error: "Runtime does not support abort" }, { status: 501 });
84
+ }
85
+ const session = getSessionById(req.params.id);
86
+ if (!session) {
87
+ return Response.json({ error: "Unknown session" }, { status: 404 });
88
+ }
89
+ const aborted = await runtime.abortSession(session.id);
90
+ return Response.json({ aborted });
91
+ },
92
+ },
93
+ "/api/chat/:id/compact": {
94
+ POST: async (req: Request & { params: { id: string } }) => {
95
+ if (!runtime.compactSession) {
96
+ return Response.json({ error: "Runtime does not support compaction" }, { status: 501 });
97
+ }
98
+ const session = getSessionById(req.params.id);
99
+ if (!session) {
100
+ return Response.json({ error: "Unknown session" }, { status: 404 });
101
+ }
102
+ const compacted = await runtime.compactSession(session.id);
103
+ return Response.json({ compacted });
104
+ },
105
+ },
106
+ };
107
+ }