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,715 @@
1
+ import { tool, type Plugin, type ToolContext } from "@opencode-ai/plugin"
2
+
3
+ const z = tool.schema
4
+
5
+ type JsonObject = Record<string, unknown>
6
+
7
+ type JsonSchema = {
8
+ type?: string
9
+ description?: string
10
+ properties?: Record<string, JsonSchema>
11
+ items?: JsonSchema
12
+ }
13
+
14
+ function resolveApiBaseUrl(...envKeys: string[]) {
15
+ for (const key of envKeys) {
16
+ const value = process.env[key]?.trim()
17
+ if (value) return value.replace(/\/+$/, "")
18
+ }
19
+
20
+ const port = process.env.AGENT_MOCKINGBIRD_PORT?.trim() || process.env.PORT?.trim() || "3001"
21
+ return `http://127.0.0.1:${port}`
22
+ }
23
+
24
+ async function requestJson(pathname: string, init?: RequestInit) {
25
+ const response = await fetch(`${resolveApiBaseUrl(
26
+ "AGENT_MOCKINGBIRD_CONFIG_API_BASE_URL",
27
+ "AGENT_MOCKINGBIRD_MEMORY_API_BASE_URL",
28
+ "AGENT_MOCKINGBIRD_CRON_API_BASE_URL",
29
+ )}${pathname}`, init)
30
+ const payload = (await response.json()) as JsonObject
31
+ if (!response.ok) {
32
+ const error = typeof payload.error === "string" ? payload.error : `Request failed (${response.status})`
33
+ throw new Error(error)
34
+ }
35
+ return payload
36
+ }
37
+
38
+ const systemPromptCache = {
39
+ value: "",
40
+ expiresAtMs: 0,
41
+ }
42
+
43
+ const compactionContextCache = {
44
+ prompt: "",
45
+ value: [] as string[],
46
+ expiresAtMs: 0,
47
+ }
48
+
49
+ type CompactionPayload = {
50
+ prompt?: string
51
+ context: string[]
52
+ }
53
+
54
+ type SessionScope = {
55
+ localSessionId: string | null
56
+ isMain: boolean
57
+ kind: "main" | "cron" | "heartbeat" | "other"
58
+ heartbeat: boolean
59
+ cronJobId: string | null
60
+ cronJobName: string | null
61
+ }
62
+
63
+ const sessionScopeCache = new Map<string, { value: SessionScope; expiresAtMs: number }>()
64
+
65
+ async function postJson(pathname: string, body: unknown, envKeys: string[] = []) {
66
+ const response = await fetch(`${resolveApiBaseUrl(...envKeys)}${pathname}`, {
67
+ method: "POST",
68
+ headers: { "Content-Type": "application/json" },
69
+ body: JSON.stringify(body),
70
+ })
71
+ const payload = (await response.json()) as JsonObject
72
+ return {
73
+ ok: response.ok,
74
+ status: response.status,
75
+ payload,
76
+ }
77
+ }
78
+
79
+ function toPreview(snippet: string) {
80
+ const compact = snippet
81
+ .replace(/^###\s+\[memory:[^\n]+\]\n?/i, "")
82
+ .replace(/^meta:[^\n]*\n?/i, "")
83
+ .trim()
84
+ if (compact.length <= 280) return compact
85
+ return `${compact.slice(0, 280).trimEnd()}...`
86
+ }
87
+
88
+ function applyPropertyDescription(parameters: unknown, propertyName: string, description: string) {
89
+ if (!parameters || typeof parameters !== "object") return
90
+ const schema = parameters as JsonSchema
91
+ const property = schema.properties?.[propertyName]
92
+ if (!property) return
93
+ property.description = description
94
+ }
95
+
96
+ const toolDescriptionOverrides: Record<string, string> = {
97
+ question:
98
+ "Ask the user a short structured question when multiple-choice or explicit clarification will unblock the assistant faster than plain text.",
99
+ task:
100
+ "Delegate a bounded subtask to a specialized agent when parallel work or focused expertise will help the assistant move faster.",
101
+ bash:
102
+ "Run a shell command in the workspace when direct terminal execution is the fastest way to inspect, verify, or change something.",
103
+ read:
104
+ "Read a file or directory directly when the assistant needs exact local context before acting.",
105
+ write:
106
+ "Create a new file when the assistant needs to add fresh workspace content.",
107
+ edit:
108
+ "Make targeted edits to an existing file when a focused change is enough.",
109
+ apply_patch:
110
+ "Apply a precise patch to workspace files when the assistant needs controlled code or text edits.",
111
+ list:
112
+ "List files in a directory to quickly inspect local workspace structure.",
113
+ glob:
114
+ "Find files by path pattern when the assistant knows roughly where something should live.",
115
+ grep:
116
+ "Search local file contents for exact text or patterns when identifying relevant code or documents.",
117
+ websearch:
118
+ "Search the web for current external information when local context is insufficient and recency matters.",
119
+ webfetch:
120
+ "Fetch and read a specific URL when the assistant already knows which external page or API response is needed.",
121
+ todoread:
122
+ "Read the assistant todo list to understand current planned work.",
123
+ todowrite:
124
+ "Update the assistant todo list when tracking multi-step work would keep execution organized.",
125
+ }
126
+
127
+ function rewriteToolDefinition(toolID: string, output: { description: string; parameters: unknown }) {
128
+ const description = toolDescriptionOverrides[toolID]
129
+ if (description) {
130
+ output.description = description
131
+ }
132
+
133
+ if (toolID === "question") {
134
+ applyPropertyDescription(
135
+ output.parameters,
136
+ "questions",
137
+ "One or more short user-facing questions to ask when structured clarification is needed.",
138
+ )
139
+ }
140
+
141
+ if (toolID === "task") {
142
+ applyPropertyDescription(output.parameters, "description", "A short summary of the delegated subtask.")
143
+ applyPropertyDescription(output.parameters, "prompt", "Exact instructions for the specialized agent to complete.")
144
+ applyPropertyDescription(
145
+ output.parameters,
146
+ "subagent_type",
147
+ "The specialist agent type that should handle this delegated subtask.",
148
+ )
149
+ }
150
+
151
+ if (toolID === "bash") {
152
+ applyPropertyDescription(output.parameters, "description", "Short explanation of why this command is being run.")
153
+ }
154
+ }
155
+
156
+ async function fetchSystemPrompt() {
157
+ const now = Date.now()
158
+ if (systemPromptCache.expiresAtMs > now) {
159
+ return systemPromptCache.value
160
+ }
161
+
162
+ const payload = await requestJson("/api/mockingbird/runtime/system-prompt")
163
+ const system = typeof payload.system === "string" ? payload.system : ""
164
+ systemPromptCache.value = system
165
+ systemPromptCache.expiresAtMs = now + 5_000
166
+ return system
167
+ }
168
+
169
+ async function fetchCompactionContext(sessionID?: string): Promise<CompactionPayload> {
170
+ const now = Date.now()
171
+ const cacheKey = sessionID?.trim() || "__default__"
172
+ if (compactionContextCache.expiresAtMs > now && cacheKey === "__default__") {
173
+ return {
174
+ prompt: compactionContextCache.prompt || undefined,
175
+ context: compactionContextCache.value,
176
+ }
177
+ }
178
+
179
+ const search = sessionID?.trim() ? `?sessionId=${encodeURIComponent(sessionID.trim())}` : ""
180
+ const payload = await requestJson(`/api/mockingbird/runtime/compaction-context${search}`)
181
+ const prompt = typeof payload.prompt === "string" && payload.prompt.trim().length > 0 ? payload.prompt : undefined
182
+ const context = Array.isArray(payload.context)
183
+ ? payload.context.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
184
+ : []
185
+ if (cacheKey === "__default__") {
186
+ compactionContextCache.prompt = prompt ?? ""
187
+ compactionContextCache.value = context
188
+ compactionContextCache.expiresAtMs = now + 5_000
189
+ }
190
+ return { prompt, context }
191
+ }
192
+
193
+ async function fetchSessionScope(sessionID?: string) {
194
+ if (!sessionID?.trim()) {
195
+ return {
196
+ localSessionId: null,
197
+ isMain: false,
198
+ kind: "other" as const,
199
+ heartbeat: false,
200
+ cronJobId: null,
201
+ cronJobName: null,
202
+ }
203
+ }
204
+
205
+ const cacheKey = sessionID.trim()
206
+ const now = Date.now()
207
+ const cached = sessionScopeCache.get(cacheKey)
208
+ if (cached && cached.expiresAtMs > now) {
209
+ return cached.value
210
+ }
211
+
212
+ const payload = await requestJson(`/api/mockingbird/runtime/session-scope?sessionId=${encodeURIComponent(cacheKey)}`)
213
+ const kind: SessionScope["kind"] =
214
+ payload.kind === "main" || payload.kind === "cron" || payload.kind === "heartbeat" || payload.kind === "other"
215
+ ? payload.kind
216
+ : "other"
217
+ const value = {
218
+ localSessionId: typeof payload.localSessionId === "string" && payload.localSessionId.trim()
219
+ ? payload.localSessionId
220
+ : null,
221
+ isMain: payload.isMain === true,
222
+ kind,
223
+ heartbeat: payload.heartbeat === true,
224
+ cronJobId: typeof payload.cronJobId === "string" && payload.cronJobId.trim() ? payload.cronJobId : null,
225
+ cronJobName: typeof payload.cronJobName === "string" && payload.cronJobName.trim() ? payload.cronJobName : null,
226
+ }
227
+ sessionScopeCache.set(cacheKey, {
228
+ value,
229
+ expiresAtMs: now + 5_000,
230
+ })
231
+ return value
232
+ }
233
+
234
+ function buildMainThreadNote() {
235
+ return [
236
+ "Thread policy:",
237
+ "- This is the main/root conversation thread.",
238
+ "- Prefer doing work directly in this thread unless delegation materially improves speed or focus.",
239
+ "- Treat this thread as the primary durable context for the user.",
240
+ ].join("\n")
241
+ }
242
+
243
+ function buildCronThreadNote(sessionScope: SessionScope) {
244
+ const jobLabel = sessionScope.cronJobName
245
+ ? `${sessionScope.cronJobName} (${sessionScope.cronJobId ?? "cron"})`
246
+ : sessionScope.cronJobId ?? "this cron job"
247
+ return [
248
+ "Thread policy:",
249
+ `- This thread belongs to cron job ${jobLabel}.`,
250
+ "- Keep work focused on this cron job's ongoing context and prior runs.",
251
+ "- Do not act like this is the main user-facing conversation thread.",
252
+ "- If user attention or a decision is needed, call notify_main_thread with a concise prompt for main.",
253
+ ].join("\n")
254
+ }
255
+
256
+ function buildHeartbeatThreadNote() {
257
+ return [
258
+ "Thread policy:",
259
+ "- This thread belongs to heartbeat.",
260
+ "- Treat the main/root conversation as the durable source of user context.",
261
+ "- The standard tool surface remains available in this thread.",
262
+ "- Use any available tool when it materially helps the heartbeat do useful work.",
263
+ "- Do not act like this is the main user-facing conversation thread.",
264
+ "- If user attention or a decision is needed, call notify_main_thread with a concise prompt for main.",
265
+ ].join("\n")
266
+ }
267
+
268
+ const scheduleKindSchema = z.enum(["at", "every", "cron"])
269
+ const runModeSchema = z.enum(["background", "conditional_agent", "agent"])
270
+ const payloadSchema = z.record(z.string(), z.unknown())
271
+
272
+ const jobCreateSchema = z.object({
273
+ id: z.string().min(1).optional(),
274
+ name: z.string().min(1),
275
+ enabled: z.boolean().optional(),
276
+ scheduleKind: scheduleKindSchema,
277
+ scheduleExpr: z.string().min(1).nullable().optional(),
278
+ everyMs: z.number().int().positive().nullable().optional(),
279
+ atIso: z.string().min(1).nullable().optional(),
280
+ timezone: z.string().min(1).nullable().optional(),
281
+ runMode: runModeSchema,
282
+ handlerKey: z.string().min(1).nullable().optional(),
283
+ conditionModulePath: z.string().min(1).nullable().optional(),
284
+ conditionDescription: z.string().min(1).nullable().optional(),
285
+ agentPromptTemplate: z.string().min(1).nullable().optional(),
286
+ agentModelOverride: z.string().min(1).nullable().optional(),
287
+ maxAttempts: z.number().int().positive().optional(),
288
+ retryBackoffMs: z.number().int().positive().optional(),
289
+ payload: payloadSchema.optional(),
290
+ })
291
+
292
+ const jobPatchSchema = z.object({
293
+ name: z.string().min(1).optional(),
294
+ enabled: z.boolean().optional(),
295
+ scheduleKind: scheduleKindSchema.optional(),
296
+ scheduleExpr: z.string().min(1).nullable().optional(),
297
+ everyMs: z.number().int().positive().nullable().optional(),
298
+ atIso: z.string().min(1).nullable().optional(),
299
+ timezone: z.string().min(1).nullable().optional(),
300
+ runMode: runModeSchema.optional(),
301
+ handlerKey: z.string().min(1).nullable().optional(),
302
+ conditionModulePath: z.string().min(1).nullable().optional(),
303
+ conditionDescription: z.string().min(1).nullable().optional(),
304
+ agentPromptTemplate: z.string().min(1).nullable().optional(),
305
+ agentModelOverride: z.string().min(1).nullable().optional(),
306
+ maxAttempts: z.number().int().positive().optional(),
307
+ retryBackoffMs: z.number().int().positive().optional(),
308
+ payload: payloadSchema.optional(),
309
+ })
310
+
311
+ const cronArgsSchema = z.discriminatedUnion("action", [
312
+ z.object({ action: z.literal("list_jobs") }),
313
+ z.object({ action: z.literal("list_handlers") }),
314
+ z.object({ action: z.literal("health") }),
315
+ z.object({ action: z.literal("get_job"), jobId: z.string().min(1) }),
316
+ z.object({ action: z.literal("create_job"), job: jobCreateSchema }),
317
+ z.object({ action: z.literal("upsert_job"), job: jobCreateSchema }),
318
+ z.object({ action: z.literal("update_job"), jobId: z.string().min(1), patch: jobPatchSchema }),
319
+ z.object({ action: z.literal("enable_job"), jobId: z.string().min(1) }),
320
+ z.object({ action: z.literal("disable_job"), jobId: z.string().min(1) }),
321
+ z.object({ action: z.literal("describe_contract") }),
322
+ z.object({ action: z.literal("delete_job"), jobId: z.string().min(1) }),
323
+ z.object({ action: z.literal("run_job_now"), jobId: z.string().min(1) }),
324
+ z.object({ action: z.literal("list_instances"), jobId: z.string().min(1).optional(), limit: z.number().int().positive().optional() }),
325
+ z.object({ action: z.literal("list_steps"), instanceId: z.string().min(1) }),
326
+ ])
327
+
328
+ const agentTypeSchema = z.object({
329
+ id: z.string().min(1),
330
+ name: z.string().min(1).optional(),
331
+ description: z.string().min(1).optional(),
332
+ prompt: z.string().min(1).optional(),
333
+ model: z.string().min(1).optional(),
334
+ variant: z.string().min(1).optional(),
335
+ mode: z.enum(["subagent", "primary", "all"]).optional(),
336
+ hidden: z.boolean().optional(),
337
+ disable: z.boolean().optional(),
338
+ temperature: z.number().optional(),
339
+ topP: z.number().optional(),
340
+ steps: z.number().int().positive().optional(),
341
+ permission: z.unknown().optional(),
342
+ options: z.record(z.string(), z.unknown()).optional(),
343
+ })
344
+
345
+ const agentArgsSchema = z.discriminatedUnion("action", [
346
+ z.object({ action: z.literal("list") }),
347
+ z.object({
348
+ action: z.literal("validate_patch"),
349
+ upserts: z.array(agentTypeSchema).default([]),
350
+ deletes: z.array(z.string().min(1)).default([]),
351
+ }),
352
+ z.object({
353
+ action: z.literal("apply_patch"),
354
+ upserts: z.array(agentTypeSchema).default([]),
355
+ deletes: z.array(z.string().min(1)).default([]),
356
+ expectedHash: z.string().min(1),
357
+ }),
358
+ ])
359
+
360
+ const configArgsSchema = z.discriminatedUnion("action", [
361
+ z.object({ action: z.literal("get_config") }),
362
+ z.object({
363
+ action: z.literal("patch_config"),
364
+ patch: z.unknown(),
365
+ expectedHash: z.string().min(1),
366
+ runSmokeTest: z.boolean().optional(),
367
+ }),
368
+ z.object({
369
+ action: z.literal("replace_config"),
370
+ config: z.unknown(),
371
+ expectedHash: z.string().min(1),
372
+ runSmokeTest: z.boolean().optional(),
373
+ }),
374
+ ])
375
+
376
+ const memorySearchTool = tool({
377
+ description: "Search memory for relevant prior context.",
378
+ args: {
379
+ query: z.string().min(1).describe("Natural language memory query"),
380
+ maxResults: z.number().int().min(1).max(20).optional(),
381
+ minScore: z.number().min(0).max(1).optional(),
382
+ debug: z.boolean().optional().describe("Include retrieval debug details."),
383
+ },
384
+ async execute(args: { query: string; maxResults?: number; minScore?: number; debug?: boolean }) {
385
+ const response = await postJson(
386
+ "/api/mockingbird/memory/retrieve",
387
+ {
388
+ query: args.query,
389
+ maxResults: args.maxResults,
390
+ minScore: args.minScore,
391
+ debug: args.debug,
392
+ },
393
+ ["AGENT_MOCKINGBIRD_MEMORY_API_BASE_URL"],
394
+ )
395
+ if (!response.ok) {
396
+ const error = typeof response.payload.error === "string" ? response.payload.error : `Request failed (${response.status})`
397
+ throw new Error(error)
398
+ }
399
+
400
+ const results = Array.isArray(response.payload.results) ? response.payload.results : []
401
+ const compactResults = results.map((result) => {
402
+ const value = result as JsonObject
403
+ const snippet = typeof value.snippet === "string" ? value.snippet : ""
404
+ return {
405
+ id: value.id,
406
+ score: value.score,
407
+ citation: value.citation,
408
+ path: value.path,
409
+ startLine: value.startLine,
410
+ endLine: value.endLine,
411
+ preview: toPreview(snippet),
412
+ snippet: toPreview(snippet),
413
+ }
414
+ })
415
+
416
+ return JSON.stringify({
417
+ ok: true,
418
+ query: args.query,
419
+ count: compactResults.length,
420
+ results: compactResults,
421
+ debug: args.debug ? response.payload.debug : undefined,
422
+ })
423
+ },
424
+ })
425
+
426
+ const memoryGetTool = tool({
427
+ description: "Read a safe slice of canonical markdown memory files by path and line window.",
428
+ args: {
429
+ path: z.string().min(1).describe("Memory path such as MEMORY.md or memory/2026-02-17.md"),
430
+ from: z.number().int().min(1).optional().describe("Start line number (1-based)"),
431
+ lines: z.number().int().min(1).max(400).optional().describe("Number of lines to return"),
432
+ },
433
+ async execute(args: { path: string; from?: number; lines?: number }) {
434
+ const response = await postJson(
435
+ "/api/mockingbird/memory/read",
436
+ {
437
+ path: args.path,
438
+ from: args.from,
439
+ lines: args.lines,
440
+ },
441
+ ["AGENT_MOCKINGBIRD_MEMORY_API_BASE_URL"],
442
+ )
443
+ if (!response.ok) {
444
+ const error = typeof response.payload.error === "string" ? response.payload.error : `Request failed (${response.status})`
445
+ throw new Error(error)
446
+ }
447
+
448
+ return JSON.stringify({
449
+ ok: true,
450
+ path: response.payload.path,
451
+ text: response.payload.text,
452
+ })
453
+ },
454
+ })
455
+
456
+ const memoryRememberTool = tool({
457
+ description: "Persist a memory note so it can be retrieved later.",
458
+ args: {
459
+ content: z.string().min(1),
460
+ confidence: z.number().min(0).max(1).optional(),
461
+ source: z.enum(["assistant", "user", "system"]).optional(),
462
+ entities: z.array(z.string()).optional(),
463
+ supersedes: z.array(z.string()).optional(),
464
+ topic: z.string().optional(),
465
+ },
466
+ async execute(args: {
467
+ content: string
468
+ confidence?: number
469
+ source?: "assistant" | "user" | "system"
470
+ entities?: string[]
471
+ supersedes?: string[]
472
+ topic?: string
473
+ }, context: ToolContext) {
474
+ const response = await postJson(
475
+ "/api/mockingbird/memory/remember",
476
+ {
477
+ ...args,
478
+ source: args.source ?? "assistant",
479
+ sessionId: context.sessionID,
480
+ },
481
+ ["AGENT_MOCKINGBIRD_MEMORY_API_BASE_URL"],
482
+ )
483
+
484
+ if (!response.ok && response.status !== 422) {
485
+ const error = typeof response.payload.error === "string" ? response.payload.error : `Request failed (${response.status})`
486
+ throw new Error(error)
487
+ }
488
+
489
+ return JSON.stringify({
490
+ ok: response.ok,
491
+ status: response.status,
492
+ result: response.payload,
493
+ })
494
+ },
495
+ })
496
+
497
+ const cronManagerTool = tool({
498
+ description: "Manage Agent Mockingbird cron jobs (list/create/update/run/delete/inspect).",
499
+ args: {
500
+ action: z.enum([
501
+ "list_jobs",
502
+ "list_handlers",
503
+ "health",
504
+ "get_job",
505
+ "create_job",
506
+ "upsert_job",
507
+ "update_job",
508
+ "enable_job",
509
+ "disable_job",
510
+ "describe_contract",
511
+ "delete_job",
512
+ "run_job_now",
513
+ "list_instances",
514
+ "list_steps",
515
+ ]),
516
+ jobId: z.string().optional(),
517
+ instanceId: z.string().optional(),
518
+ limit: z.number().int().positive().optional(),
519
+ job: z.unknown().optional(),
520
+ patch: z.unknown().optional(),
521
+ },
522
+ async execute(rawArgs) {
523
+ const args = cronArgsSchema.parse(rawArgs)
524
+ const response = await postJson("/api/mockingbird/cron/manage", args, [
525
+ "AGENT_MOCKINGBIRD_CRON_API_BASE_URL",
526
+ "AGENT_MOCKINGBIRD_MEMORY_API_BASE_URL",
527
+ ])
528
+ if (!response.ok) {
529
+ const error = typeof response.payload.error === "string" ? response.payload.error : `Request failed (${response.status})`
530
+ throw new Error(error)
531
+ }
532
+ return JSON.stringify({
533
+ ok: true,
534
+ ...response.payload,
535
+ })
536
+ },
537
+ })
538
+
539
+ const notifyMainThreadTool = tool({
540
+ description: "Escalate a concise prompt from a cron worker thread into the main/root conversation.",
541
+ args: {
542
+ prompt: z.string().min(1),
543
+ severity: z.enum(["info", "warn", "critical"]).optional(),
544
+ },
545
+ async execute(args: { prompt: string; severity?: "info" | "warn" | "critical" }, context: ToolContext) {
546
+ const response = await postJson("/api/mockingbird/runtime/notify-main-thread", {
547
+ sessionId: context.sessionID,
548
+ prompt: args.prompt,
549
+ severity: args.severity,
550
+ })
551
+ if (!response.ok) {
552
+ const error = typeof response.payload.error === "string" ? response.payload.error : `Request failed (${response.status})`
553
+ throw new Error(error)
554
+ }
555
+ return JSON.stringify({
556
+ ok: true,
557
+ ...response.payload,
558
+ })
559
+ },
560
+ })
561
+
562
+ const agentTypeManagerTool = tool({
563
+ description:
564
+ "Manage OpenCode agent definitions through Agent Mockingbird's OpenCode-backed APIs with validation and hash conflict detection.",
565
+ args: {
566
+ action: z.enum(["list", "validate_patch", "apply_patch"]),
567
+ upserts: z.array(z.unknown()).optional(),
568
+ deletes: z.array(z.string().min(1)).optional(),
569
+ expectedHash: z.string().min(1).optional(),
570
+ },
571
+ async execute(rawArgs: {
572
+ action: "list" | "validate_patch" | "apply_patch"
573
+ upserts?: unknown[]
574
+ deletes?: string[]
575
+ expectedHash?: string
576
+ }) {
577
+ const args = agentArgsSchema.parse(rawArgs)
578
+
579
+ if (args.action === "list") {
580
+ const payload = await requestJson("/api/mockingbird/agents")
581
+ return JSON.stringify({ ok: true, ...payload })
582
+ }
583
+
584
+ if (args.action === "validate_patch") {
585
+ const payload = await requestJson("/api/mockingbird/agents/validate", {
586
+ method: "POST",
587
+ headers: { "Content-Type": "application/json" },
588
+ body: JSON.stringify({
589
+ upserts: args.upserts,
590
+ deletes: args.deletes,
591
+ }),
592
+ })
593
+ return JSON.stringify({ ok: true, ...payload })
594
+ }
595
+
596
+ const payload = await requestJson("/api/mockingbird/agents", {
597
+ method: "PATCH",
598
+ headers: { "Content-Type": "application/json" },
599
+ body: JSON.stringify({
600
+ upserts: args.upserts,
601
+ deletes: args.deletes,
602
+ expectedHash: args.expectedHash,
603
+ }),
604
+ })
605
+ return JSON.stringify({ ok: true, ...payload })
606
+ },
607
+ })
608
+
609
+ const configManagerTool = tool({
610
+ description:
611
+ "Read or update Agent Mockingbird managed config through validated APIs with hash conflict detection and optional smoke tests.",
612
+ args: {
613
+ action: z.enum(["get_config", "patch_config", "replace_config"]),
614
+ patch: z.unknown().optional(),
615
+ config: z.unknown().optional(),
616
+ expectedHash: z.string().min(1).optional(),
617
+ runSmokeTest: z.boolean().optional(),
618
+ },
619
+ async execute(rawArgs: {
620
+ action: "get_config" | "patch_config" | "replace_config"
621
+ patch?: unknown
622
+ config?: unknown
623
+ expectedHash?: string
624
+ runSmokeTest?: boolean
625
+ }) {
626
+ const args = configArgsSchema.parse(rawArgs)
627
+
628
+ if (args.action === "get_config") {
629
+ const payload = await requestJson("/api/mockingbird/runtime/config")
630
+ return JSON.stringify({ ok: true, ...payload })
631
+ }
632
+
633
+ if (args.action === "patch_config") {
634
+ const payload = await requestJson("/api/mockingbird/runtime/config", {
635
+ method: "PATCH",
636
+ headers: { "Content-Type": "application/json" },
637
+ body: JSON.stringify({
638
+ patch: args.patch,
639
+ expectedHash: args.expectedHash,
640
+ }),
641
+ })
642
+ return JSON.stringify({ ok: true, ...payload })
643
+ }
644
+
645
+ const payload = await requestJson("/api/mockingbird/runtime/config/replace", {
646
+ method: "POST",
647
+ headers: { "Content-Type": "application/json" },
648
+ body: JSON.stringify({
649
+ config: args.config,
650
+ expectedHash: args.expectedHash,
651
+ }),
652
+ })
653
+ return JSON.stringify({ ok: true, ...payload })
654
+ },
655
+ })
656
+
657
+ const AgentMockingbirdPlugin: Plugin = async () => {
658
+ return {
659
+ "experimental.chat.system.transform": async (_input, output) => {
660
+ const system = await fetchSystemPrompt()
661
+ if (!system.trim()) return
662
+ output.system.push(system)
663
+ const sessionScope = await fetchSessionScope(_input.sessionID)
664
+ if (sessionScope.isMain) {
665
+ output.system.push(buildMainThreadNote())
666
+ } else if (sessionScope.kind === "cron") {
667
+ output.system.push(buildCronThreadNote(sessionScope))
668
+ } else if (sessionScope.kind === "heartbeat") {
669
+ output.system.push(buildHeartbeatThreadNote())
670
+ }
671
+ },
672
+ "experimental.session.compacting": async (_input, output) => {
673
+ const compaction = await fetchCompactionContext(_input.sessionID)
674
+ if (compaction.prompt) {
675
+ output.prompt = compaction.prompt
676
+ return
677
+ }
678
+ if (compaction.context.length === 0) return
679
+ output.context.push(...compaction.context)
680
+ },
681
+ "tool.definition": async (input, output) => {
682
+ rewriteToolDefinition(input.toolID, output)
683
+ },
684
+ "shell.env": async (_input, output) => {
685
+ const defaultBaseUrl = resolveApiBaseUrl(
686
+ "AGENT_MOCKINGBIRD_CONFIG_API_BASE_URL",
687
+ "AGENT_MOCKINGBIRD_MEMORY_API_BASE_URL",
688
+ "AGENT_MOCKINGBIRD_CRON_API_BASE_URL",
689
+ )
690
+ output.env.AGENT_MOCKINGBIRD_CONFIG_API_BASE_URL ??=
691
+ process.env.AGENT_MOCKINGBIRD_CONFIG_API_BASE_URL?.trim() || defaultBaseUrl
692
+ output.env.AGENT_MOCKINGBIRD_MEMORY_API_BASE_URL ??=
693
+ process.env.AGENT_MOCKINGBIRD_MEMORY_API_BASE_URL?.trim() || defaultBaseUrl
694
+ output.env.AGENT_MOCKINGBIRD_CRON_API_BASE_URL ??=
695
+ process.env.AGENT_MOCKINGBIRD_CRON_API_BASE_URL?.trim() ||
696
+ process.env.AGENT_MOCKINGBIRD_MEMORY_API_BASE_URL?.trim() ||
697
+ defaultBaseUrl
698
+ if (process.env.AGENT_MOCKINGBIRD_PORT?.trim()) {
699
+ output.env.AGENT_MOCKINGBIRD_PORT ??= process.env.AGENT_MOCKINGBIRD_PORT.trim()
700
+ }
701
+ },
702
+ tool: {
703
+ memory_search: memorySearchTool,
704
+ memory_get: memoryGetTool,
705
+ memory_remember: memoryRememberTool,
706
+ cron_manager: cronManagerTool,
707
+ notify_main_thread: notifyMainThreadTool,
708
+ agent_type_manager: agentTypeManagerTool,
709
+ config_manager: configManagerTool,
710
+ },
711
+ }
712
+ }
713
+
714
+ export { AgentMockingbirdPlugin }
715
+ export default AgentMockingbirdPlugin