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,503 @@
1
+ import {
2
+ BACKGROUND_SYNC_INTERVAL_MS,
3
+ OPENCODE_RUNTIME_ID,
4
+ STREAMED_METADATA_CACHE_LIMIT,
5
+ logger,
6
+ type Message,
7
+ type OpencodeEvent,
8
+ type OpencodeMessagePartDeltaEvent,
9
+ type OpencodeMessagePartUpdatedEvent,
10
+ type OpencodeMessageUpdatedEvent,
11
+ type OpencodePermissionAskedEvent,
12
+ type OpencodePermissionRepliedEvent,
13
+ type OpencodeQuestionAskedEvent,
14
+ type OpencodeQuestionRejectedEvent,
15
+ type OpencodeQuestionRepliedEvent,
16
+ type OpencodeRuntimeEvent,
17
+ type Part,
18
+ } from "./shared";
19
+ import {
20
+ createSessionCompactedEvent,
21
+ createSessionMessageDeltaEvent,
22
+ createSessionMessagePartUpdatedEvent,
23
+ createSessionPermissionRequestedEvent,
24
+ createSessionPermissionResolvedEvent,
25
+ createSessionQuestionRequestedEvent,
26
+ createSessionQuestionResolvedEvent,
27
+ createSessionRunErrorEvent,
28
+ createSessionRunStatusUpdatedEvent,
29
+ createSessionStateUpdatedEvent,
30
+ } from "../../contracts/events";
31
+ import { getLocalSessionIdByRuntimeBinding, getSessionById, setSessionTitle } from "../../db/repository";
32
+ import type { OpencodeRuntime } from "../opencodeRuntime";
33
+
34
+ export interface OpencodeRuntimeEventMethods {
35
+ startEventSync(): void;
36
+ startBackgroundSync(): void;
37
+ runBackgroundSyncLoop(): Promise<void>;
38
+ runEventSyncLoop(): Promise<void>;
39
+ handleOpencodeEvent(event: unknown): void;
40
+ handleMessagePartUpdatedEvent(event: OpencodeMessagePartUpdatedEvent): void;
41
+ handleMessagePartDeltaEvent(event: OpencodeMessagePartDeltaEvent): void;
42
+ handleMessageUpdatedEvent(event: OpencodeMessageUpdatedEvent): void;
43
+ handleSessionCreatedEvent(event: Extract<OpencodeEvent, { type: "session.created" }>): void;
44
+ handleSessionUpdatedEvent(event: Extract<OpencodeEvent, { type: "session.updated" }>): void;
45
+ handleSessionStatusEvent(event: Extract<OpencodeEvent, { type: "session.status" }>): void;
46
+ handleSessionIdleEvent(event: Extract<OpencodeEvent, { type: "session.idle" }>): void;
47
+ handleSessionCompactedEvent(event: Extract<OpencodeEvent, { type: "session.compacted" }>): void;
48
+ handleSessionErrorEvent(event: Extract<OpencodeEvent, { type: "session.error" }>): void;
49
+ handlePermissionAskedEvent(event: OpencodePermissionAskedEvent): void;
50
+ handlePermissionRepliedEvent(event: OpencodePermissionRepliedEvent): void;
51
+ handleQuestionAskedEvent(event: OpencodeQuestionAskedEvent): void;
52
+ handleQuestionResolvedEvent(
53
+ event: OpencodeQuestionRepliedEvent | OpencodeQuestionRejectedEvent,
54
+ outcome: "replied" | "rejected",
55
+ ): void;
56
+ isOpencodeEvent(event: unknown): event is OpencodeRuntimeEvent;
57
+ scopedMessageId(sessionId: string, messageId: string): string;
58
+ scopedPartId(sessionId: string, messageId: string, partId: string): string;
59
+ isAssistantOnlyPartType(partType: Part["type"]): boolean;
60
+ setBoundedMapEntry<Key, Value>(map: Map<Key, Value>, key: Key, value: Value): void;
61
+ rememberMessageRole(sessionId: string, messageId: string, role: Message["role"]): void;
62
+ rememberPartMetadata(part: Part): void;
63
+ }
64
+
65
+ export const opencodeRuntimeEventMethods: OpencodeRuntimeEventMethods = {
66
+ startEventSync(this: OpencodeRuntime) {
67
+ if (this["eventSyncStarted"] || this["disposed"]) return;
68
+ this["eventSyncStarted"] = true;
69
+ void this.runEventSyncLoop().catch((error) => logger.errorWithCause("Event sync loop crashed", error));
70
+ },
71
+
72
+ startBackgroundSync(this: OpencodeRuntime) {
73
+ if (this["backgroundSyncStarted"] || this["disposed"]) return;
74
+ this["backgroundSyncStarted"] = true;
75
+ void this.runBackgroundSyncLoop().catch((error) => {
76
+ logger.errorWithCause("Background sync loop crashed", error);
77
+ });
78
+ },
79
+
80
+ async runBackgroundSyncLoop(this: OpencodeRuntime) {
81
+ while (!this["disposed"]) {
82
+ await this.syncBackgroundRuns();
83
+ await this.waitFor(BACKGROUND_SYNC_INTERVAL_MS);
84
+ }
85
+ },
86
+
87
+ async runEventSyncLoop(this: OpencodeRuntime) {
88
+ while (!this["disposed"]) {
89
+ try {
90
+ const subscription = await this.getClient().event.subscribe({
91
+ responseStyle: "data",
92
+ throwOnError: true,
93
+ signal: this["disposeController"].signal,
94
+ });
95
+ for await (const event of subscription.stream) {
96
+ if (this["disposed"]) return;
97
+ this.handleOpencodeEvent(event);
98
+ }
99
+ } catch (error) {
100
+ if (this["disposed"]) return;
101
+ logger.warnWithCause("Event stream sync failed", error);
102
+ }
103
+ await this.waitFor(1_000);
104
+ }
105
+ },
106
+
107
+ handleOpencodeEvent(this: OpencodeRuntime, event) {
108
+ if (!this.isOpencodeEvent(event)) return;
109
+ switch (event.type) {
110
+ case "session.created":
111
+ this.handleSessionCreatedEvent(event);
112
+ return;
113
+ case "session.updated":
114
+ this.handleSessionUpdatedEvent(event);
115
+ return;
116
+ case "message.part.updated":
117
+ this.handleMessagePartUpdatedEvent(event);
118
+ return;
119
+ case "message.part.delta":
120
+ this.handleMessagePartDeltaEvent(event);
121
+ return;
122
+ case "message.updated":
123
+ this.handleMessageUpdatedEvent(event);
124
+ return;
125
+ case "session.status":
126
+ this.handleSessionStatusEvent(event);
127
+ return;
128
+ case "session.idle":
129
+ this.handleSessionIdleEvent(event);
130
+ return;
131
+ case "session.compacted":
132
+ this.handleSessionCompactedEvent(event);
133
+ return;
134
+ case "session.error":
135
+ this.handleSessionErrorEvent(event);
136
+ return;
137
+ case "permission.asked":
138
+ this.handlePermissionAskedEvent(event);
139
+ return;
140
+ case "permission.replied":
141
+ this.handlePermissionRepliedEvent(event);
142
+ return;
143
+ case "question.asked":
144
+ this.handleQuestionAskedEvent(event);
145
+ return;
146
+ case "question.replied":
147
+ this.handleQuestionResolvedEvent(event, "replied");
148
+ return;
149
+ case "question.rejected":
150
+ this.handleQuestionResolvedEvent(event, "rejected");
151
+ return;
152
+ default:
153
+ return;
154
+ }
155
+ },
156
+
157
+ handleMessagePartUpdatedEvent(this: OpencodeRuntime, event) {
158
+ const sessionId = event.properties.part.sessionID.trim();
159
+ if (!sessionId) return;
160
+ this.rememberPartMetadata(event.properties.part);
161
+ const messageRole = this["messageRoleByScopedMessageId"].get(
162
+ this.scopedMessageId(sessionId, event.properties.part.messageID),
163
+ );
164
+ const canTreatAsAssistant =
165
+ messageRole === "assistant" ||
166
+ (messageRole !== "user" && event.properties.part.type === "reasoning");
167
+ const localSessionId = getLocalSessionIdByRuntimeBinding(OPENCODE_RUNTIME_ID, sessionId);
168
+ if (!localSessionId) return;
169
+ const mappedPart = this.mapChatMessagePart(event.properties.part);
170
+ if (mappedPart) {
171
+ const phase = (() => {
172
+ if (event.properties.part.type !== "tool") return "update" as const;
173
+ const status = event.properties.part.state.status;
174
+ if (status === "pending") return "start" as const;
175
+ if (status === "running") return "update" as const;
176
+ return "final" as const;
177
+ })();
178
+ this.emit(
179
+ createSessionMessagePartUpdatedEvent(
180
+ {
181
+ sessionId: localSessionId,
182
+ messageId: event.properties.part.messageID,
183
+ part: mappedPart,
184
+ phase,
185
+ observedAt: new Date().toISOString(),
186
+ },
187
+ "runtime",
188
+ ),
189
+ );
190
+ }
191
+ const maybeDelta = (event.properties as { delta?: unknown }).delta;
192
+ const deltaFromPartUpdate = typeof maybeDelta === "string" ? maybeDelta : "";
193
+ if (!canTreatAsAssistant) return;
194
+ if (event.properties.part.type !== "text" && event.properties.part.type !== "reasoning") {
195
+ return;
196
+ }
197
+ if (deltaFromPartUpdate.length > 0) {
198
+ this.emit(
199
+ createSessionMessageDeltaEvent(
200
+ {
201
+ sessionId: localSessionId,
202
+ messageId: event.properties.part.messageID,
203
+ text: deltaFromPartUpdate,
204
+ mode: "append",
205
+ observedAt: new Date().toISOString(),
206
+ },
207
+ "runtime",
208
+ ),
209
+ );
210
+ return;
211
+ }
212
+ if (event.properties.part.text.length > 0) {
213
+ this.emit(
214
+ createSessionMessageDeltaEvent(
215
+ {
216
+ sessionId: localSessionId,
217
+ messageId: event.properties.part.messageID,
218
+ text: event.properties.part.text,
219
+ mode: "replace",
220
+ observedAt: new Date().toISOString(),
221
+ },
222
+ "runtime",
223
+ ),
224
+ );
225
+ }
226
+ },
227
+
228
+ handleMessagePartDeltaEvent(this: OpencodeRuntime, event) {
229
+ const sessionId = event.properties.sessionID.trim();
230
+ if (!sessionId) return;
231
+ const field = event.properties.field.trim();
232
+ if (field !== "text") return;
233
+ const delta = event.properties.delta;
234
+ if (typeof delta !== "string" || delta.length === 0) return;
235
+ const messageId = event.properties.messageID.trim();
236
+ const partId = event.properties.partID.trim();
237
+ if (!messageId || !partId) return;
238
+ const localSessionId = getLocalSessionIdByRuntimeBinding(OPENCODE_RUNTIME_ID, sessionId);
239
+ if (!localSessionId) return;
240
+ const partType = this["partTypeByScopedPartId"].get(this.scopedPartId(sessionId, messageId, partId));
241
+ if (partType && partType !== "text" && partType !== "reasoning") return;
242
+ const messageRole = this["messageRoleByScopedMessageId"].get(this.scopedMessageId(sessionId, messageId));
243
+ const canTreatAsAssistant = messageRole === "assistant" || (messageRole !== "user" && partType === "reasoning");
244
+ if (!canTreatAsAssistant) return;
245
+ this.emit(
246
+ createSessionMessageDeltaEvent(
247
+ { sessionId: localSessionId, messageId, text: delta, mode: "append", observedAt: new Date().toISOString() },
248
+ "runtime",
249
+ ),
250
+ );
251
+ },
252
+
253
+ handleMessageUpdatedEvent(this: OpencodeRuntime, event) {
254
+ this.rememberMessageRole(event.properties.info.sessionID, event.properties.info.id, event.properties.info.role);
255
+ if (event.properties.info.role !== "assistant") return;
256
+ const opencodeSessionId = event.properties.info.sessionID.trim();
257
+ if (!opencodeSessionId) return;
258
+ const localSessionId = getLocalSessionIdByRuntimeBinding(OPENCODE_RUNTIME_ID, opencodeSessionId);
259
+ if (!localSessionId || this["busySessions"].has(localSessionId)) return;
260
+ void this.syncMessageById({
261
+ localSessionId,
262
+ externalSessionId: opencodeSessionId,
263
+ messageId: event.properties.info.id,
264
+ });
265
+ },
266
+
267
+ handleSessionCreatedEvent(this: OpencodeRuntime, event) {
268
+ this.ensureBackgroundRunForSessionInfo(event.properties.info, "created");
269
+ },
270
+
271
+ handleSessionUpdatedEvent(this: OpencodeRuntime, event) {
272
+ this.ensureBackgroundRunForSessionInfo(event.properties.info, "created");
273
+ const opencodeSessionId = event.properties.info.id.trim();
274
+ const remoteTitle = event.properties.info.title.trim();
275
+ if (!opencodeSessionId || !remoteTitle) return;
276
+ const localSessionId = getLocalSessionIdByRuntimeBinding(OPENCODE_RUNTIME_ID, opencodeSessionId);
277
+ if (!localSessionId || localSessionId === "main") return;
278
+ const localSession = getSessionById(localSessionId);
279
+ if (!localSession || localSession.title === remoteTitle) return;
280
+ const updated = setSessionTitle(localSessionId, remoteTitle);
281
+ if (updated) this.emit(createSessionStateUpdatedEvent(updated, "runtime"));
282
+ },
283
+
284
+ handleSessionStatusEvent(this: OpencodeRuntime, event) {
285
+ const opencodeSessionId = event.properties.sessionID;
286
+ const status = event.properties.status;
287
+ this.applyBackgroundStatusBySessionId(opencodeSessionId, status);
288
+ const localSessionId = getLocalSessionIdByRuntimeBinding(OPENCODE_RUNTIME_ID, opencodeSessionId);
289
+ if (!localSessionId) return;
290
+ this.emit(
291
+ createSessionRunStatusUpdatedEvent(
292
+ {
293
+ sessionId: localSessionId,
294
+ status: status.type,
295
+ attempt: status.type === "retry" ? status.attempt : undefined,
296
+ message: status.type === "retry" ? this.normalizeProviderMessage(status.message) : undefined,
297
+ nextAt:
298
+ status.type === "retry" && Number.isFinite(status.next)
299
+ ? new Date(status.next).toISOString()
300
+ : undefined,
301
+ },
302
+ "runtime",
303
+ ),
304
+ );
305
+ },
306
+
307
+ handleSessionIdleEvent(this: OpencodeRuntime, event) {
308
+ const opencodeSessionId = event.properties.sessionID;
309
+ this.applyBackgroundStatusBySessionId(opencodeSessionId, { type: "idle" });
310
+ const localSessionId = getLocalSessionIdByRuntimeBinding(OPENCODE_RUNTIME_ID, opencodeSessionId);
311
+ if (!localSessionId) return;
312
+ this.emit(createSessionRunStatusUpdatedEvent({ sessionId: localSessionId, status: "idle" }, "runtime"));
313
+ void this.syncLocalSessionFromOpencode({
314
+ localSessionId,
315
+ externalSessionId: opencodeSessionId,
316
+ force: true,
317
+ });
318
+ void this.maybeDrainSessionQueue(localSessionId);
319
+ },
320
+
321
+ handleSessionCompactedEvent(this: OpencodeRuntime, event) {
322
+ const opencodeSessionId = event.properties.sessionID;
323
+ this.markMemoryInjectionStateForReinject(opencodeSessionId);
324
+ const localSessionId = getLocalSessionIdByRuntimeBinding(OPENCODE_RUNTIME_ID, opencodeSessionId);
325
+ if (!localSessionId) return;
326
+ this.emit(createSessionCompactedEvent({ sessionId: localSessionId }, "runtime"));
327
+ },
328
+
329
+ handleSessionErrorEvent(this: OpencodeRuntime, event) {
330
+ const error = event.properties.error;
331
+ if (!error) return;
332
+ const normalized = this.normalizeRuntimeError(error);
333
+ if (event.properties.sessionID) {
334
+ this.markBackgroundRunFailed(event.properties.sessionID, normalized.message);
335
+ }
336
+ const localSessionId = event.properties.sessionID
337
+ ? getLocalSessionIdByRuntimeBinding(OPENCODE_RUNTIME_ID, event.properties.sessionID)
338
+ : null;
339
+ if (localSessionId && this.isTimeoutLikeError(error) && this.inFlightBackgroundChildRunCount(localSessionId) > 0) {
340
+ this.emit(createSessionRunStatusUpdatedEvent({ sessionId: localSessionId, status: "busy" }, "runtime"));
341
+ return;
342
+ }
343
+ this.emit(
344
+ createSessionRunErrorEvent(
345
+ { sessionId: localSessionId, name: normalized.name, message: normalized.message },
346
+ "runtime",
347
+ ),
348
+ );
349
+ },
350
+
351
+ handlePermissionAskedEvent(this: OpencodeRuntime, event) {
352
+ const externalSessionId = event.properties.sessionID.trim();
353
+ if (!externalSessionId) return;
354
+ const localSessionId = getLocalSessionIdByRuntimeBinding(OPENCODE_RUNTIME_ID, externalSessionId);
355
+ if (!localSessionId) return;
356
+ this.emit(
357
+ createSessionPermissionRequestedEvent(
358
+ {
359
+ id: event.properties.id,
360
+ sessionId: localSessionId,
361
+ permission: event.properties.permission,
362
+ patterns: Array.isArray(event.properties.patterns) ? event.properties.patterns : [],
363
+ metadata:
364
+ event.properties.metadata && typeof event.properties.metadata === "object"
365
+ ? event.properties.metadata
366
+ : {},
367
+ always: Array.isArray(event.properties.always) ? event.properties.always : [],
368
+ },
369
+ "runtime",
370
+ ),
371
+ );
372
+ },
373
+
374
+ handlePermissionRepliedEvent(this: OpencodeRuntime, event) {
375
+ const externalSessionId = event.properties.sessionID.trim();
376
+ if (!externalSessionId) return;
377
+ const localSessionId = getLocalSessionIdByRuntimeBinding(OPENCODE_RUNTIME_ID, externalSessionId);
378
+ if (!localSessionId) return;
379
+ const requestId = event.properties.requestID ?? event.properties.permissionID ?? "";
380
+ if (!requestId.trim()) return;
381
+ const rawReply = (event.properties.reply ?? event.properties.response ?? "").trim();
382
+ const reply =
383
+ rawReply === "once" || rawReply === "always" || rawReply === "reject"
384
+ ? rawReply
385
+ : "once";
386
+ this.emit(
387
+ createSessionPermissionResolvedEvent(
388
+ { sessionId: localSessionId, requestId, reply },
389
+ "runtime",
390
+ ),
391
+ );
392
+ },
393
+
394
+ handleQuestionAskedEvent(this: OpencodeRuntime, event) {
395
+ const externalSessionId = event.properties.sessionID.trim();
396
+ if (!externalSessionId) return;
397
+ const localSessionId = getLocalSessionIdByRuntimeBinding(OPENCODE_RUNTIME_ID, externalSessionId);
398
+ if (!localSessionId) return;
399
+ const questions = Array.isArray(event.properties.questions) ? event.properties.questions : [];
400
+ this.emit(
401
+ createSessionQuestionRequestedEvent(
402
+ {
403
+ id: event.properties.id,
404
+ sessionId: localSessionId,
405
+ questions: questions.map((question) => ({
406
+ question: question.question,
407
+ header: question.header,
408
+ options: Array.isArray(question.options)
409
+ ? question.options.map((option) => ({
410
+ label: option.label,
411
+ description: option.description,
412
+ }))
413
+ : [],
414
+ multiple: question.multiple === true ? true : undefined,
415
+ custom: question.custom === false ? false : true,
416
+ })),
417
+ },
418
+ "runtime",
419
+ ),
420
+ );
421
+ },
422
+
423
+ handleQuestionResolvedEvent(this: OpencodeRuntime, event, outcome) {
424
+ const externalSessionId = event.properties.sessionID.trim();
425
+ if (!externalSessionId) return;
426
+ const localSessionId = getLocalSessionIdByRuntimeBinding(OPENCODE_RUNTIME_ID, externalSessionId);
427
+ if (!localSessionId) return;
428
+ this.emit(
429
+ createSessionQuestionResolvedEvent(
430
+ { sessionId: localSessionId, requestId: event.properties.requestID, outcome },
431
+ "runtime",
432
+ ),
433
+ );
434
+ },
435
+
436
+ isOpencodeEvent(this: OpencodeRuntime, event: unknown): event is OpencodeRuntimeEvent {
437
+ if (!event || typeof event !== "object") return false;
438
+ return typeof (event as { type?: unknown }).type === "string";
439
+ },
440
+
441
+ scopedMessageId(this: OpencodeRuntime, sessionId: string, messageId: string) {
442
+ return `${sessionId}:${messageId}`;
443
+ },
444
+
445
+ scopedPartId(this: OpencodeRuntime, sessionId: string, messageId: string, partId: string) {
446
+ return `${sessionId}:${messageId}:${partId}`;
447
+ },
448
+
449
+ isAssistantOnlyPartType(this: OpencodeRuntime, partType: Part["type"]) {
450
+ return (
451
+ partType === "reasoning" ||
452
+ partType === "tool" ||
453
+ partType === "step-start" ||
454
+ partType === "step-finish" ||
455
+ partType === "snapshot" ||
456
+ partType === "patch" ||
457
+ partType === "agent" ||
458
+ partType === "retry" ||
459
+ partType === "compaction"
460
+ );
461
+ },
462
+
463
+ setBoundedMapEntry<Key, Value>(
464
+ this: OpencodeRuntime,
465
+ map: Map<Key, Value>,
466
+ key: Key,
467
+ value: Value,
468
+ ) {
469
+ if (map.has(key)) map.delete(key);
470
+ map.set(key, value);
471
+ if (map.size <= STREAMED_METADATA_CACHE_LIMIT) return;
472
+ const oldest = map.keys().next().value;
473
+ if (oldest !== undefined) map.delete(oldest);
474
+ },
475
+
476
+ rememberMessageRole(this: OpencodeRuntime, sessionId, messageId, role) {
477
+ const normalizedSessionId = sessionId.trim();
478
+ const normalizedMessageId = messageId.trim();
479
+ if (!normalizedSessionId || !normalizedMessageId) return;
480
+ this.setBoundedMapEntry(
481
+ this["messageRoleByScopedMessageId"],
482
+ this.scopedMessageId(normalizedSessionId, normalizedMessageId),
483
+ role,
484
+ );
485
+ },
486
+
487
+ rememberPartMetadata(this: OpencodeRuntime, part) {
488
+ const maybePart = part as { sessionID?: unknown; messageID?: unknown; id?: unknown; type?: unknown };
489
+ const sessionId = typeof maybePart.sessionID === "string" ? maybePart.sessionID.trim() : "";
490
+ const messageId = typeof maybePart.messageID === "string" ? maybePart.messageID.trim() : "";
491
+ const partId = typeof maybePart.id === "string" ? maybePart.id.trim() : "";
492
+ const partType = typeof maybePart.type === "string" ? (maybePart.type as Part["type"]) : null;
493
+ if (!sessionId || !messageId || !partId || !partType) return;
494
+ this.setBoundedMapEntry(
495
+ this["partTypeByScopedPartId"],
496
+ this.scopedPartId(sessionId, messageId, partId),
497
+ partType,
498
+ );
499
+ if (this.isAssistantOnlyPartType(partType)) {
500
+ this.rememberMessageRole(sessionId, messageId, "assistant");
501
+ }
502
+ },
503
+ };