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,362 @@
1
+ import { parse as parseJsonc } from "jsonc-parser";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import path from "node:path";
4
+
5
+ import type { AgentMockingbirdConfig } from "../config/schema";
6
+ import { getConfigSnapshot } from "../config/service";
7
+ import {
8
+ resolveOpencodeConfigDir,
9
+ resolveOpencodeWorkspaceDir,
10
+ } from "../workspace/resolve";
11
+
12
+ const DEFAULT_BOOTSTRAP_FILE_NAMES = [
13
+ "AGENTS.md",
14
+ "SOUL.md",
15
+ "TOOLS.md",
16
+ "IDENTITY.md",
17
+ "USER.md",
18
+ "HEARTBEAT.md",
19
+ "BOOTSTRAP.md",
20
+ "MEMORY.md",
21
+ "memory.md",
22
+ ] as const;
23
+
24
+ const SUBAGENT_BOOTSTRAP_ALLOWLIST = new Set<string>(["AGENTS.md", "TOOLS.md"]);
25
+ const MIN_BOOTSTRAP_FILE_BUDGET_CHARS = 64;
26
+ const BOOTSTRAP_HEAD_RATIO = 0.7;
27
+ const BOOTSTRAP_TAIL_RATIO = 0.2;
28
+
29
+ const IDENTITY_PLACEHOLDER_VALUES = new Set([
30
+ "pick something you like",
31
+ "ai? robot? familiar? ghost in the machine? something weirder?",
32
+ "how do you come across? sharp? warm? chaotic? calm?",
33
+ "your signature - pick one that feels right",
34
+ "workspace-relative path, http(s) url, or data uri",
35
+ ]);
36
+
37
+ type BootstrapFileName = (typeof DEFAULT_BOOTSTRAP_FILE_NAMES)[number];
38
+
39
+ interface WorkspaceIdentityProfile {
40
+ name?: string;
41
+ emoji?: string;
42
+ theme?: string;
43
+ creature?: string;
44
+ vibe?: string;
45
+ avatar?: string;
46
+ }
47
+
48
+ interface BootstrapConfig {
49
+ enabled: boolean;
50
+ maxCharsPerFile: number;
51
+ maxCharsTotal: number;
52
+ subagentMinimal: boolean;
53
+ includeAgentPrompt: boolean;
54
+ }
55
+
56
+ interface AgentRuntimeDetails {
57
+ mode?: string;
58
+ prompt?: string;
59
+ }
60
+
61
+ interface LoadedBootstrapFile {
62
+ name: BootstrapFileName;
63
+ path: string;
64
+ content: string;
65
+ missing: boolean;
66
+ truncated: boolean;
67
+ originalLength: number;
68
+ }
69
+
70
+ interface WorkspaceBootstrapPromptContext {
71
+ section: string | null;
72
+ workspaceDir: string;
73
+ mode: "full" | "minimal";
74
+ hasSoul: boolean;
75
+ files: LoadedBootstrapFile[];
76
+ identity: WorkspaceIdentityProfile | null;
77
+ agentPrompt: string | null;
78
+ agentPromptSource: string | null;
79
+ }
80
+
81
+ function resolveWorkspaceDir(config: AgentMockingbirdConfig): string {
82
+ return resolveOpencodeWorkspaceDir(config);
83
+ }
84
+
85
+ function resolveBootstrapConfig(
86
+ config: AgentMockingbirdConfig,
87
+ ): BootstrapConfig {
88
+ const source = config.runtime.opencode.bootstrap;
89
+ return {
90
+ enabled: source.enabled === true,
91
+ maxCharsPerFile: source.maxCharsPerFile,
92
+ maxCharsTotal: source.maxCharsTotal,
93
+ subagentMinimal: source.subagentMinimal === true,
94
+ includeAgentPrompt: source.includeAgentPrompt === true,
95
+ };
96
+ }
97
+
98
+ function normalizeIdentityValue(value: string): string {
99
+ let normalized = value.trim();
100
+ normalized = normalized.replace(/^[*_]+|[*_]+$/g, "").trim();
101
+ if (normalized.startsWith("(") && normalized.endsWith(")")) {
102
+ normalized = normalized.slice(1, -1).trim();
103
+ }
104
+ normalized = normalized.replace(/[\u2013\u2014]/g, "-");
105
+ normalized = normalized.replace(/\s+/g, " ").toLowerCase();
106
+ return normalized;
107
+ }
108
+
109
+ function isIdentityPlaceholder(value: string): boolean {
110
+ return IDENTITY_PLACEHOLDER_VALUES.has(normalizeIdentityValue(value));
111
+ }
112
+
113
+ function parseIdentityMarkdown(content: string): WorkspaceIdentityProfile {
114
+ const identity: WorkspaceIdentityProfile = {};
115
+ const lines = content.split(/\r?\n/);
116
+ for (const line of lines) {
117
+ const cleaned = line.trim().replace(/^\s*-\s*/, "");
118
+ const colonIndex = cleaned.indexOf(":");
119
+ if (colonIndex === -1) continue;
120
+ const label = cleaned
121
+ .slice(0, colonIndex)
122
+ .replace(/[*_]/g, "")
123
+ .trim()
124
+ .toLowerCase();
125
+ const value = cleaned
126
+ .slice(colonIndex + 1)
127
+ .replace(/^[*_]+|[*_]+$/g, "")
128
+ .trim();
129
+ if (!value || isIdentityPlaceholder(value)) continue;
130
+ if (label === "name") identity.name = value;
131
+ if (label === "emoji") identity.emoji = value;
132
+ if (label === "theme") identity.theme = value;
133
+ if (label === "creature") identity.creature = value;
134
+ if (label === "vibe") identity.vibe = value;
135
+ if (label === "avatar") identity.avatar = value;
136
+ }
137
+ return identity;
138
+ }
139
+
140
+ function identityHasValues(identity: WorkspaceIdentityProfile): boolean {
141
+ return Boolean(
142
+ identity.name ||
143
+ identity.emoji ||
144
+ identity.theme ||
145
+ identity.creature ||
146
+ identity.vibe ||
147
+ identity.avatar,
148
+ );
149
+ }
150
+
151
+ function loadWorkspaceIdentityProfile(
152
+ config: AgentMockingbirdConfig = getConfigSnapshot().config,
153
+ ) {
154
+ const workspaceDir = resolveWorkspaceDir(config);
155
+ const identityPath = path.join(workspaceDir, "IDENTITY.md");
156
+ if (!existsSync(identityPath)) return null;
157
+ try {
158
+ const raw = readFileSync(identityPath, "utf8");
159
+ const parsed = parseIdentityMarkdown(raw);
160
+ return identityHasValues(parsed) ? parsed : null;
161
+ } catch {
162
+ return null;
163
+ }
164
+ }
165
+
166
+ function clampToBudget(content: string, budget: number): string {
167
+ if (budget <= 0) return "";
168
+ if (content.length <= budget) return content;
169
+ if (budget <= 3) {
170
+ return content.slice(0, budget);
171
+ }
172
+ return `${content.slice(0, budget - 1)}…`;
173
+ }
174
+
175
+ function trimBootstrapContent(
176
+ content: string,
177
+ fileName: string,
178
+ maxChars: number,
179
+ ) {
180
+ const trimmed = content.trimEnd();
181
+ if (trimmed.length <= maxChars) {
182
+ return {
183
+ content: trimmed,
184
+ truncated: false,
185
+ originalLength: trimmed.length,
186
+ };
187
+ }
188
+
189
+ const headChars = Math.floor(maxChars * BOOTSTRAP_HEAD_RATIO);
190
+ const tailChars = Math.floor(maxChars * BOOTSTRAP_TAIL_RATIO);
191
+ const marker = [
192
+ "",
193
+ `[...truncated, read ${fileName} for full content...]`,
194
+ `…(truncated ${fileName}: kept ${headChars}+${tailChars} chars of ${trimmed.length})…`,
195
+ "",
196
+ ].join("\n");
197
+
198
+ return {
199
+ content: [
200
+ trimmed.slice(0, headChars),
201
+ marker,
202
+ trimmed.slice(-tailChars),
203
+ ].join("\n"),
204
+ truncated: true,
205
+ originalLength: trimmed.length,
206
+ };
207
+ }
208
+
209
+ function resolveOpencodeConfigFilePath(config: AgentMockingbirdConfig): string {
210
+ return path.join(resolveOpencodeConfigDir(config), "opencode.jsonc");
211
+ }
212
+
213
+ function resolveAgentRuntimeDetails(
214
+ config: AgentMockingbirdConfig,
215
+ agentId: string,
216
+ ): AgentRuntimeDetails | null {
217
+ const trimmedId = agentId.trim();
218
+ if (!trimmedId) return null;
219
+ const configPath = resolveOpencodeConfigFilePath(config);
220
+ if (!existsSync(configPath)) return null;
221
+ try {
222
+ const parsed = parseJsonc(readFileSync(configPath, "utf8")) as Record<
223
+ string,
224
+ unknown
225
+ >;
226
+ const agentMap = parsed?.agent;
227
+ if (!agentMap || typeof agentMap !== "object" || Array.isArray(agentMap))
228
+ return null;
229
+ const rawEntry = (agentMap as Record<string, unknown>)[trimmedId];
230
+ if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry))
231
+ return null;
232
+ const entry = rawEntry as Record<string, unknown>;
233
+ return {
234
+ mode:
235
+ typeof entry.mode === "string"
236
+ ? entry.mode.trim().toLowerCase()
237
+ : undefined,
238
+ prompt:
239
+ typeof entry.prompt === "string" ? entry.prompt.trim() : undefined,
240
+ };
241
+ } catch {
242
+ return null;
243
+ }
244
+ }
245
+
246
+ function resolveBootstrapFileNames(
247
+ mode: "full" | "minimal",
248
+ ): Array<BootstrapFileName> {
249
+ if (mode === "full") return [...DEFAULT_BOOTSTRAP_FILE_NAMES];
250
+ return DEFAULT_BOOTSTRAP_FILE_NAMES.filter((name) =>
251
+ SUBAGENT_BOOTSTRAP_ALLOWLIST.has(name),
252
+ );
253
+ }
254
+
255
+ export function buildWorkspaceBootstrapPromptContext(input?: {
256
+ agentId?: string;
257
+ config?: AgentMockingbirdConfig;
258
+ }): WorkspaceBootstrapPromptContext {
259
+ const config = input?.config ?? getConfigSnapshot().config;
260
+ const workspaceDir = resolveWorkspaceDir(config);
261
+ const bootstrap = resolveBootstrapConfig(config);
262
+ const details = input?.agentId
263
+ ? resolveAgentRuntimeDetails(config, input.agentId)
264
+ : null;
265
+ const mode: "full" | "minimal" =
266
+ bootstrap.subagentMinimal && details?.mode === "subagent"
267
+ ? "minimal"
268
+ : "full";
269
+ const agentPrompt =
270
+ bootstrap.includeAgentPrompt && details?.prompt ? details.prompt : null;
271
+
272
+ if (!bootstrap.enabled) {
273
+ return {
274
+ section: null,
275
+ workspaceDir,
276
+ mode,
277
+ hasSoul: false,
278
+ files: [],
279
+ identity: loadWorkspaceIdentityProfile(config),
280
+ agentPrompt,
281
+ agentPromptSource: agentPrompt ? input?.agentId?.trim() || null : null,
282
+ };
283
+ }
284
+
285
+ const fileNames = resolveBootstrapFileNames(mode);
286
+ let remainingTotalChars = Math.max(1, bootstrap.maxCharsTotal);
287
+ const files: LoadedBootstrapFile[] = [];
288
+
289
+ for (const fileName of fileNames) {
290
+ if (remainingTotalChars <= 0) break;
291
+ const filePath = path.join(workspaceDir, fileName);
292
+ if (!existsSync(filePath)) {
293
+ const missing = `[MISSING] Expected at: ${filePath}`;
294
+ const content = clampToBudget(missing, remainingTotalChars);
295
+ if (!content) break;
296
+ files.push({
297
+ name: fileName,
298
+ path: filePath,
299
+ content,
300
+ missing: true,
301
+ truncated: false,
302
+ originalLength: 0,
303
+ });
304
+ remainingTotalChars = Math.max(0, remainingTotalChars - content.length);
305
+ continue;
306
+ }
307
+ if (remainingTotalChars < MIN_BOOTSTRAP_FILE_BUDGET_CHARS) {
308
+ break;
309
+ }
310
+ const raw = readFileSync(filePath, "utf8");
311
+ const maxChars = Math.max(
312
+ 1,
313
+ Math.min(bootstrap.maxCharsPerFile, remainingTotalChars),
314
+ );
315
+ const trimmed = trimBootstrapContent(raw, fileName, maxChars);
316
+ const content = clampToBudget(trimmed.content, remainingTotalChars);
317
+ if (!content) continue;
318
+ files.push({
319
+ name: fileName,
320
+ path: filePath,
321
+ content,
322
+ missing: false,
323
+ truncated: trimmed.truncated || content.length < trimmed.content.length,
324
+ originalLength: trimmed.originalLength,
325
+ });
326
+ remainingTotalChars = Math.max(0, remainingTotalChars - content.length);
327
+ }
328
+
329
+ const hasSoul = files.some(
330
+ (file) => file.name.toLowerCase() === "soul.md" && !file.missing,
331
+ );
332
+ let section: string | null = null;
333
+ if (files.length > 0) {
334
+ const lines: string[] = [];
335
+ lines.push(
336
+ "# Project Context",
337
+ "",
338
+ "The following workspace context files have been loaded:",
339
+ );
340
+ if (hasSoul) {
341
+ lines.push(
342
+ "If SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.",
343
+ );
344
+ }
345
+ lines.push("");
346
+ for (const file of files) {
347
+ lines.push(`## ${file.name}`, "", file.content, "");
348
+ }
349
+ section = lines.filter(Boolean).join("\n");
350
+ }
351
+
352
+ return {
353
+ section,
354
+ workspaceDir,
355
+ mode,
356
+ hasSoul,
357
+ files,
358
+ identity: loadWorkspaceIdentityProfile(config),
359
+ agentPrompt,
360
+ agentPromptSource: agentPrompt ? input?.agentId?.trim() || null : null,
361
+ };
362
+ }
@@ -0,0 +1,133 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+
6
+ import { migrateOpenclawWorkspace } from "./openclawImport";
7
+
8
+ const testRoots: string[] = [];
9
+
10
+ afterEach(() => {
11
+ for (const root of testRoots.splice(0, testRoots.length)) {
12
+ rmSync(root, { recursive: true, force: true });
13
+ }
14
+ });
15
+
16
+ function makePaths() {
17
+ const root = mkdtempSync(path.join(tmpdir(), "agent-mockingbird-openclaw-import-test-"));
18
+ testRoots.push(root);
19
+ const sourceDir = path.join(root, "source");
20
+ const targetDir = path.join(root, "target");
21
+ mkdirSync(sourceDir, { recursive: true });
22
+ mkdirSync(targetDir, { recursive: true });
23
+ return { sourceDir, targetDir };
24
+ }
25
+
26
+ test("migrate copies supported workspace files and maps skills into .agents/skills", async () => {
27
+ const { sourceDir, targetDir } = makePaths();
28
+ mkdirSync(path.join(sourceDir, "skills", "my-skill"), { recursive: true });
29
+ mkdirSync(path.join(sourceDir, "scripts"), { recursive: true });
30
+ writeFileSync(path.join(sourceDir, "AGENTS.md"), "# Imported Agents\n", "utf8");
31
+ writeFileSync(path.join(sourceDir, "skills", "my-skill", "SKILL.md"), "# Skill\n", "utf8");
32
+ writeFileSync(path.join(sourceDir, "scripts", "sync.sh"), "#!/usr/bin/env bash\n", "utf8");
33
+
34
+ const migrated = await migrateOpenclawWorkspace({
35
+ source: { mode: "local", path: sourceDir },
36
+ targetDirectory: targetDir,
37
+ });
38
+
39
+ expect(migrated.summary.copied).toBe(3);
40
+ expect(readFileSync(path.join(targetDir, "AGENTS.md"), "utf8")).toContain("Imported Agents");
41
+ expect(readFileSync(path.join(targetDir, ".agents", "skills", "my-skill", "SKILL.md"), "utf8")).toContain("Skill");
42
+ expect(readFileSync(path.join(targetDir, "scripts", "sync.sh"), "utf8")).toContain("usr/bin/env bash");
43
+ });
44
+
45
+ test("migrate keeps existing AGENTS.md when smart merge is unavailable", async () => {
46
+ const { sourceDir, targetDir } = makePaths();
47
+ writeFileSync(path.join(sourceDir, "AGENTS.md"), "# OpenClaw Instructions\nUse OpenClaw auth\nGeneral rule\n", "utf8");
48
+ writeFileSync(path.join(targetDir, "AGENTS.md"), "# Agent Mockingbird Instructions\nKeep this\n", "utf8");
49
+
50
+ const migrated = await migrateOpenclawWorkspace({
51
+ source: { mode: "local", path: sourceDir },
52
+ targetDirectory: targetDir,
53
+ });
54
+
55
+ expect(migrated.summary.merged).toBe(0);
56
+ expect(migrated.summary.skippedExisting).toBe(1);
57
+ expect(readFileSync(path.join(targetDir, "AGENTS.md"), "utf8")).toBe("# Agent Mockingbird Instructions\nKeep this\n");
58
+ });
59
+
60
+ test("migrate maps CLAUDE.md into AGENTS.md when source AGENTS.md is absent", async () => {
61
+ const { sourceDir, targetDir } = makePaths();
62
+ writeFileSync(path.join(sourceDir, "CLAUDE.md"), "# CLAUDE\nConverted guidance\n", "utf8");
63
+
64
+ const migrated = await migrateOpenclawWorkspace({
65
+ source: { mode: "local", path: sourceDir },
66
+ targetDirectory: targetDir,
67
+ });
68
+
69
+ expect(migrated.summary.copied).toBe(1);
70
+ expect(readFileSync(path.join(targetDir, "AGENTS.md"), "utf8")).toContain("Converted guidance");
71
+ expect(migrated.warnings.some(line => line.includes("Mapped CLAUDE.md to AGENTS.md"))).toBe(true);
72
+ });
73
+
74
+ test("migrate does not map CLAUDE.md when source AGENTS.md exists", async () => {
75
+ const { sourceDir, targetDir } = makePaths();
76
+ writeFileSync(path.join(sourceDir, "AGENTS.md"), "# Agents source\n", "utf8");
77
+ writeFileSync(path.join(sourceDir, "CLAUDE.md"), "# Claude source\n", "utf8");
78
+
79
+ await migrateOpenclawWorkspace({
80
+ source: { mode: "local", path: sourceDir },
81
+ targetDirectory: targetDir,
82
+ });
83
+
84
+ expect(readFileSync(path.join(targetDir, "AGENTS.md"), "utf8")).toContain("Agents source");
85
+ expect(readFileSync(path.join(targetDir, "CLAUDE.md"), "utf8")).toContain("Claude source");
86
+ });
87
+
88
+ test("migrate copies missing memory day files wholesale", async () => {
89
+ const { sourceDir, targetDir } = makePaths();
90
+ mkdirSync(path.join(sourceDir, "memory"), { recursive: true });
91
+ writeFileSync(path.join(sourceDir, "memory", "2026-03-04.md"), "# Memory\nCurrent events and notes\n", "utf8");
92
+
93
+ const migrated = await migrateOpenclawWorkspace({
94
+ source: { mode: "local", path: sourceDir },
95
+ targetDirectory: targetDir,
96
+ });
97
+
98
+ expect(migrated.summary.copied).toBe(1);
99
+ expect(readFileSync(path.join(targetDir, "memory", "2026-03-04.md"), "utf8")).toBe(
100
+ "# Memory\nCurrent events and notes\n",
101
+ );
102
+ });
103
+
104
+ test("migrate keeps existing non-AGENTS conflicts instead of line-merging", async () => {
105
+ const { sourceDir, targetDir } = makePaths();
106
+ mkdirSync(path.join(sourceDir, "memory"), { recursive: true });
107
+ mkdirSync(path.join(targetDir, "memory"), { recursive: true });
108
+ writeFileSync(path.join(sourceDir, "memory", "notes.md"), "# Source\nnew note\n", "utf8");
109
+ writeFileSync(path.join(targetDir, "memory", "notes.md"), "# Target\nexisting note\n", "utf8");
110
+
111
+ const migrated = await migrateOpenclawWorkspace({
112
+ source: { mode: "local", path: sourceDir },
113
+ targetDirectory: targetDir,
114
+ });
115
+
116
+ expect(migrated.summary.merged).toBe(0);
117
+ expect(migrated.summary.skippedExisting).toBe(1);
118
+ expect(readFileSync(path.join(targetDir, "memory", "notes.md"), "utf8")).toBe("# Target\nexisting note\n");
119
+ });
120
+
121
+ test("migrate skips protected .opencode paths", async () => {
122
+ const { sourceDir, targetDir } = makePaths();
123
+ mkdirSync(path.join(sourceDir, ".opencode"), { recursive: true });
124
+ writeFileSync(path.join(sourceDir, ".opencode", "opencode.jsonc"), "{}\n", "utf8");
125
+
126
+ const migrated = await migrateOpenclawWorkspace({
127
+ source: { mode: "local", path: sourceDir },
128
+ targetDirectory: targetDir,
129
+ });
130
+
131
+ expect(migrated.summary.skippedProtected).toBe(1);
132
+ expect(migrated.summary.copied).toBe(0);
133
+ });