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,462 @@
1
+ import { searchMemory } from "../../memory/service";
2
+ import {
3
+ analyzeMemoryInjectionResults,
4
+ buildMemoryContextFingerprint,
5
+ isMemoryRecallIntentQuery,
6
+ isWriteIntentMemoryQuery,
7
+ memoryInjectionResultKey,
8
+ } from "../memoryPromptDedup";
9
+ import type { OpencodeRuntime } from "../opencodeRuntime";
10
+ import {
11
+ currentMemoryConfig,
12
+ logger,
13
+ MEMORY_INJECTION_STATE_MAX_ENTRIES,
14
+ MEMORY_INJECTION_STATE_TTL_MS,
15
+ MODEL_MEMORY_TOOLS,
16
+ type MemoryInjectionState,
17
+ type MemorySearchResult,
18
+ type MessageMemoryTrace,
19
+ type MemoryToolCallTrace,
20
+ type Part,
21
+ type RuntimeInputPart,
22
+ } from "./shared";
23
+
24
+ export interface OpencodeRuntimeMemoryMethods {
25
+ buildPromptInputWithMemory(
26
+ opencodeSessionId: string,
27
+ userContent: string,
28
+ ): Promise<{
29
+ content: string;
30
+ freshSessionContent: string;
31
+ injectedContextResults: number;
32
+ retrievedContextResults: number;
33
+ suppressedAsAlreadyInContext: number;
34
+ suppressedAsIrrelevant: number;
35
+ memoryContextFingerprint: string | null;
36
+ }>;
37
+ setMemoryInjectionState(sessionId: string, state: MemoryInjectionState): void;
38
+ clearMemoryInjectionState(sessionId: string): void;
39
+ getMemoryInjectionState(sessionId: string): MemoryInjectionState | null;
40
+ pruneMemoryInjectionState(now?: number): void;
41
+ markMemoryInjectionStateForReinject(sessionId: string): void;
42
+ searchMemory(
43
+ query: string,
44
+ options?: { maxResults?: number; minScore?: number },
45
+ ): Promise<MemorySearchResult[]>;
46
+ normalizePromptInputParts(
47
+ content: string,
48
+ parts?: RuntimeInputPart[],
49
+ ): RuntimeInputPart[];
50
+ extractPrimaryTextInput(parts: RuntimeInputPart[]): string;
51
+ applyMemoryPromptToParts(
52
+ parts: RuntimeInputPart[],
53
+ memoryWrappedText: string,
54
+ ): RuntimeInputPart[];
55
+ summarizeUserInputForStorage(
56
+ content: string,
57
+ parts: RuntimeInputPart[],
58
+ ): string;
59
+ buildMessageMemoryTrace(
60
+ parts: Array<Part>,
61
+ memoryStats: {
62
+ injectedContextResults: number;
63
+ retrievedContextResults: number;
64
+ suppressedAsAlreadyInContext: number;
65
+ suppressedAsIrrelevant: number;
66
+ },
67
+ ): MessageMemoryTrace | null;
68
+ summarizeMemoryToolOutput(tool: string, output: string): string;
69
+ }
70
+
71
+ export const opencodeRuntimeMemoryMethods: OpencodeRuntimeMemoryMethods = {
72
+ async buildPromptInputWithMemory(this: OpencodeRuntime, opencodeSessionId, userContent) {
73
+ if (currentMemoryConfig().toolMode === "tool_only") {
74
+ return {
75
+ content: userContent,
76
+ freshSessionContent: userContent,
77
+ injectedContextResults: 0,
78
+ retrievedContextResults: 0,
79
+ suppressedAsAlreadyInContext: 0,
80
+ suppressedAsIrrelevant: 0,
81
+ memoryContextFingerprint: null,
82
+ };
83
+ }
84
+
85
+ const query = userContent.trim();
86
+ if (!query) {
87
+ this.clearMemoryInjectionState(opencodeSessionId);
88
+ return {
89
+ content: userContent,
90
+ freshSessionContent: userContent,
91
+ injectedContextResults: 0,
92
+ retrievedContextResults: 0,
93
+ suppressedAsAlreadyInContext: 0,
94
+ suppressedAsIrrelevant: 0,
95
+ memoryContextFingerprint: null,
96
+ };
97
+ }
98
+ if (currentMemoryConfig().toolMode === "hybrid" && isWriteIntentMemoryQuery(query)) {
99
+ return {
100
+ content: userContent,
101
+ freshSessionContent: userContent,
102
+ injectedContextResults: 0,
103
+ retrievedContextResults: 0,
104
+ suppressedAsAlreadyInContext: 0,
105
+ suppressedAsIrrelevant: 0,
106
+ memoryContextFingerprint: null,
107
+ };
108
+ }
109
+
110
+ try {
111
+ const searchResults = await this.searchMemory(query);
112
+ const analyzed = analyzeMemoryInjectionResults(query, searchResults as MemorySearchResult[]);
113
+ const relevantResults = analyzed.results;
114
+ if (!relevantResults.length) {
115
+ return {
116
+ content: userContent,
117
+ freshSessionContent: userContent,
118
+ injectedContextResults: 0,
119
+ retrievedContextResults: searchResults.length,
120
+ suppressedAsAlreadyInContext: 0,
121
+ suppressedAsIrrelevant: analyzed.filteredIrrelevantCount,
122
+ memoryContextFingerprint: null,
123
+ };
124
+ }
125
+
126
+ const memoryConfig = currentMemoryConfig();
127
+ const dedupeEnabled = memoryConfig.injectionDedupeEnabled;
128
+ const dedupeRecallFallbackOnly = memoryConfig.injectionDedupeFallbackRecallOnly;
129
+ const isRecallIntent = isMemoryRecallIntentQuery(query);
130
+ const state = this.getMemoryInjectionState(opencodeSessionId) ?? {
131
+ fingerprint: "",
132
+ forceReinject: false,
133
+ generation: 0,
134
+ turn: 0,
135
+ injectedKeysByGeneration: [],
136
+ };
137
+ const alreadyInjected = new Set(state.injectedKeysByGeneration);
138
+ let suppressedAsAlreadyInContext = 0;
139
+ let candidateResults = [...relevantResults];
140
+ let recallFallbackApplied = false;
141
+ if (dedupeEnabled) {
142
+ candidateResults = relevantResults.filter(
143
+ (result) => !alreadyInjected.has(memoryInjectionResultKey(result)),
144
+ );
145
+ suppressedAsAlreadyInContext = relevantResults.length - candidateResults.length;
146
+ }
147
+ if (!candidateResults.length && dedupeEnabled) {
148
+ const allowFallback = dedupeRecallFallbackOnly ? isRecallIntent : true;
149
+ if (allowFallback && relevantResults.length > 0) {
150
+ candidateResults = [relevantResults[0] as MemorySearchResult];
151
+ suppressedAsAlreadyInContext = Math.max(0, relevantResults.length - 1);
152
+ recallFallbackApplied = true;
153
+ }
154
+ }
155
+
156
+ const makeWrappedText = (results: MemorySearchResult[]) => {
157
+ const contextLines = results.map(
158
+ (result, index) =>
159
+ `${index + 1}. (${result.score.toFixed(3)}) ${result.citation}\n${result.snippet}`,
160
+ );
161
+ const contextBlock = contextLines.join("\n\n");
162
+ return [
163
+ "Use the memory context below only if relevant and non-contradictory to current user intent.",
164
+ "",
165
+ "[Memory Context]",
166
+ contextBlock,
167
+ "[/Memory Context]",
168
+ "",
169
+ "[User Message]",
170
+ userContent,
171
+ "[/User Message]",
172
+ ].join("\n");
173
+ };
174
+
175
+ const freshSessionWrappedText = makeWrappedText(relevantResults);
176
+ if (!candidateResults.length) {
177
+ this.setMemoryInjectionState(opencodeSessionId, {
178
+ ...state,
179
+ forceReinject: false,
180
+ turn: state.turn + 1,
181
+ });
182
+ return {
183
+ content: userContent,
184
+ freshSessionContent: freshSessionWrappedText,
185
+ injectedContextResults: 0,
186
+ retrievedContextResults: searchResults.length,
187
+ suppressedAsAlreadyInContext,
188
+ suppressedAsIrrelevant: analyzed.filteredIrrelevantCount,
189
+ memoryContextFingerprint: null,
190
+ };
191
+ }
192
+
193
+ const wrappedText = makeWrappedText(candidateResults);
194
+ const fingerprint = buildMemoryContextFingerprint(candidateResults);
195
+ const shouldInject =
196
+ recallFallbackApplied ||
197
+ state.forceReinject ||
198
+ state.fingerprint !== fingerprint;
199
+ if (shouldInject) {
200
+ const maxTracked = Math.max(32, memoryConfig.injectionDedupeMaxTracked);
201
+ const injectedKeys = [
202
+ ...state.injectedKeysByGeneration,
203
+ ...candidateResults.map(memoryInjectionResultKey),
204
+ ];
205
+ const dedupedKeys = [...new Set(injectedKeys)];
206
+ this.setMemoryInjectionState(opencodeSessionId, {
207
+ fingerprint,
208
+ forceReinject: false,
209
+ generation: state.generation,
210
+ turn: state.turn + 1,
211
+ injectedKeysByGeneration: dedupedKeys.slice(-maxTracked),
212
+ });
213
+ } else {
214
+ this.setMemoryInjectionState(opencodeSessionId, {
215
+ ...state,
216
+ forceReinject: false,
217
+ turn: state.turn + 1,
218
+ });
219
+ }
220
+ return {
221
+ content: shouldInject ? wrappedText : userContent,
222
+ freshSessionContent: freshSessionWrappedText,
223
+ injectedContextResults: shouldInject ? candidateResults.length : 0,
224
+ retrievedContextResults: searchResults.length,
225
+ suppressedAsAlreadyInContext,
226
+ suppressedAsIrrelevant: analyzed.filteredIrrelevantCount,
227
+ memoryContextFingerprint: fingerprint,
228
+ };
229
+ } catch (error) {
230
+ logger.warnWithCause("Memory injection failed", error, {
231
+ sessionId: opencodeSessionId,
232
+ });
233
+ return {
234
+ content: userContent,
235
+ freshSessionContent: userContent,
236
+ injectedContextResults: 0,
237
+ retrievedContextResults: 0,
238
+ suppressedAsAlreadyInContext: 0,
239
+ suppressedAsIrrelevant: 0,
240
+ memoryContextFingerprint: null,
241
+ };
242
+ }
243
+ },
244
+
245
+ setMemoryInjectionState(this: OpencodeRuntime, sessionId, state) {
246
+ const normalized = sessionId.trim();
247
+ if (!normalized) return;
248
+ const now = Date.now();
249
+ this["memoryInjectionStateBySessionId"].delete(normalized);
250
+ this["memoryInjectionStateBySessionId"].set(normalized, {
251
+ state,
252
+ lastTouchedAt: now,
253
+ });
254
+ this.pruneMemoryInjectionState(now);
255
+ },
256
+
257
+ clearMemoryInjectionState(this: OpencodeRuntime, sessionId) {
258
+ const normalized = sessionId.trim();
259
+ if (!normalized) return;
260
+ this["memoryInjectionStateBySessionId"].delete(normalized);
261
+ },
262
+
263
+ getMemoryInjectionState(this: OpencodeRuntime, sessionId) {
264
+ const normalized = sessionId.trim();
265
+ if (!normalized) return null;
266
+ const now = Date.now();
267
+ this.pruneMemoryInjectionState(now);
268
+ const entry = this["memoryInjectionStateBySessionId"].get(normalized);
269
+ if (!entry) return null;
270
+ if (now - entry.lastTouchedAt > MEMORY_INJECTION_STATE_TTL_MS) {
271
+ this["memoryInjectionStateBySessionId"].delete(normalized);
272
+ return null;
273
+ }
274
+ this["memoryInjectionStateBySessionId"].delete(normalized);
275
+ this["memoryInjectionStateBySessionId"].set(normalized, {
276
+ state: entry.state,
277
+ lastTouchedAt: now,
278
+ });
279
+ return entry.state;
280
+ },
281
+
282
+ pruneMemoryInjectionState(this: OpencodeRuntime, now = Date.now()) {
283
+ for (const [sessionId, entry] of this["memoryInjectionStateBySessionId"].entries()) {
284
+ if (now - entry.lastTouchedAt > MEMORY_INJECTION_STATE_TTL_MS) {
285
+ this["memoryInjectionStateBySessionId"].delete(sessionId);
286
+ }
287
+ }
288
+ while (this["memoryInjectionStateBySessionId"].size > MEMORY_INJECTION_STATE_MAX_ENTRIES) {
289
+ const oldest = this["memoryInjectionStateBySessionId"].keys().next().value as
290
+ | string
291
+ | undefined;
292
+ if (!oldest) break;
293
+ this["memoryInjectionStateBySessionId"].delete(oldest);
294
+ }
295
+ },
296
+
297
+ markMemoryInjectionStateForReinject(this: OpencodeRuntime, sessionId) {
298
+ const normalized = sessionId.trim();
299
+ if (!normalized) return;
300
+ const existing = this.getMemoryInjectionState(normalized);
301
+ if (existing) {
302
+ this.setMemoryInjectionState(normalized, {
303
+ ...existing,
304
+ forceReinject: true,
305
+ generation: existing.generation + 1,
306
+ injectedKeysByGeneration: [],
307
+ });
308
+ return;
309
+ }
310
+ this.setMemoryInjectionState(normalized, {
311
+ fingerprint: "",
312
+ forceReinject: true,
313
+ generation: 1,
314
+ turn: 0,
315
+ injectedKeysByGeneration: [],
316
+ });
317
+ },
318
+
319
+ async searchMemory(this: OpencodeRuntime, query, options) {
320
+ if (this["options"].searchMemoryFn) {
321
+ return this["options"].searchMemoryFn(query, options);
322
+ }
323
+ return searchMemory(query, options);
324
+ },
325
+
326
+ normalizePromptInputParts(this: OpencodeRuntime, content: string, parts?: RuntimeInputPart[]) {
327
+ const normalized: RuntimeInputPart[] = [];
328
+ if (Array.isArray(parts)) {
329
+ for (const part of parts) {
330
+ if (part.type === "text") {
331
+ if (!part.text?.trim()) continue;
332
+ normalized.push({ type: "text", text: part.text });
333
+ continue;
334
+ }
335
+ if (part.type === "file") {
336
+ const mime = part.mime?.trim();
337
+ const url = part.url?.trim();
338
+ if (!mime || !url) continue;
339
+ normalized.push({
340
+ type: "file",
341
+ mime,
342
+ filename: part.filename?.trim() || undefined,
343
+ url,
344
+ });
345
+ }
346
+ }
347
+ }
348
+ if (normalized.length > 0) return normalized;
349
+ if (!content.trim()) return [];
350
+ return [{ type: "text", text: content }];
351
+ },
352
+
353
+ extractPrimaryTextInput(this: OpencodeRuntime, parts: RuntimeInputPart[]) {
354
+ const firstText = parts.find((part) => part.type === "text");
355
+ return firstText?.text ?? "";
356
+ },
357
+
358
+ applyMemoryPromptToParts(
359
+ this: OpencodeRuntime,
360
+ parts: RuntimeInputPart[],
361
+ memoryWrappedText: string,
362
+ ) {
363
+ if (!memoryWrappedText.trim()) return parts;
364
+ const next = [...parts];
365
+ const index = next.findIndex((part) => part.type === "text");
366
+ if (index === -1) {
367
+ next.unshift({ type: "text", text: memoryWrappedText });
368
+ return next;
369
+ }
370
+ const existing = next[index];
371
+ if (!existing || existing.type !== "text") return next;
372
+ next[index] = { ...existing, text: memoryWrappedText };
373
+ return next;
374
+ },
375
+
376
+ summarizeUserInputForStorage(
377
+ this: OpencodeRuntime,
378
+ content: string,
379
+ parts: RuntimeInputPart[],
380
+ ) {
381
+ const text = content.trim();
382
+ const attachments = parts.filter((part) => part.type === "file");
383
+ if (attachments.length === 0) return text;
384
+ const imageCount = attachments.filter((part) =>
385
+ part.mime.toLowerCase().startsWith("image/"),
386
+ ).length;
387
+ const fileCount = attachments.length - imageCount;
388
+ const summaryBits: string[] = [];
389
+ if (imageCount > 0) {
390
+ summaryBits.push(`${imageCount} image${imageCount === 1 ? "" : "s"}`);
391
+ }
392
+ if (fileCount > 0) {
393
+ summaryBits.push(`${fileCount} file${fileCount === 1 ? "" : "s"}`);
394
+ }
395
+ const attachmentSummary = `[Attachments: ${summaryBits.join(", ")}]`;
396
+ return text ? `${text}\n\n${attachmentSummary}` : attachmentSummary;
397
+ },
398
+
399
+ buildMessageMemoryTrace(
400
+ this: OpencodeRuntime,
401
+ parts: Array<Part>,
402
+ memoryStats: {
403
+ injectedContextResults: number;
404
+ retrievedContextResults: number;
405
+ suppressedAsAlreadyInContext: number;
406
+ suppressedAsIrrelevant: number;
407
+ },
408
+ ) {
409
+ const toolCalls: MemoryToolCallTrace[] = [];
410
+ for (const part of parts) {
411
+ if (part.type !== "tool" || !MODEL_MEMORY_TOOLS.has(part.tool)) continue;
412
+ const call: MemoryToolCallTrace = {
413
+ tool: part.tool,
414
+ status: part.state.status,
415
+ };
416
+ if (part.state.status === "error") {
417
+ call.error = part.state.error;
418
+ call.summary = "tool call failed";
419
+ } else if (part.state.status === "completed") {
420
+ call.summary = this.summarizeMemoryToolOutput(part.tool, part.state.output);
421
+ }
422
+ toolCalls.push(call);
423
+ }
424
+
425
+ if (memoryStats.injectedContextResults <= 0 && toolCalls.length === 0) {
426
+ return null;
427
+ }
428
+
429
+ return {
430
+ mode: currentMemoryConfig().toolMode,
431
+ injectedContextResults: memoryStats.injectedContextResults,
432
+ retrievedContextResults: memoryStats.retrievedContextResults,
433
+ suppressedAsAlreadyInContext: memoryStats.suppressedAsAlreadyInContext,
434
+ suppressedAsIrrelevant: memoryStats.suppressedAsIrrelevant,
435
+ toolCalls,
436
+ createdAt: new Date().toISOString(),
437
+ };
438
+ },
439
+
440
+ summarizeMemoryToolOutput(this: OpencodeRuntime, tool: string, output: string) {
441
+ if (tool !== "memory_remember") {
442
+ return "completed";
443
+ }
444
+ try {
445
+ const parsed = JSON.parse(output) as unknown;
446
+ if (!parsed || typeof parsed !== "object") return "completed";
447
+ const container = parsed as Record<string, unknown>;
448
+ const result = (container.result as Record<string, unknown> | undefined) ?? container;
449
+ const accepted = result.accepted;
450
+ const reason = result.reason;
451
+ if (typeof accepted === "boolean" && typeof reason === "string") {
452
+ return accepted ? `accepted: ${reason}` : `rejected: ${reason}`;
453
+ }
454
+ if (typeof accepted === "boolean") {
455
+ return accepted ? "accepted" : "rejected";
456
+ }
457
+ } catch {
458
+ // ignore parse errors and fall back to generic summary
459
+ }
460
+ return "completed";
461
+ },
462
+ };