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,399 @@
1
+ From b5338d849cb9e2f2f9e37108b4595862cfbfa84d Mon Sep 17 00:00:00 2001
2
+ From: Matt Campbell <matt@battleshopper.com>
3
+ Date: Thu, 19 Mar 2026 15:15:18 -0500
4
+ Subject: [PATCH 06/10] Modernize cron settings
5
+
6
+ ---
7
+ .../src/components/settings-cron-data.test.ts | 96 +++++++++++++++
8
+ .../app/src/components/settings-cron-data.ts | 101 +++++++++++++++
9
+ packages/app/src/components/settings-cron.tsx | 116 +++++-------------
10
+ 3 files changed, 229 insertions(+), 84 deletions(-)
11
+ create mode 100644 packages/app/src/components/settings-cron-data.test.ts
12
+ create mode 100644 packages/app/src/components/settings-cron-data.ts
13
+
14
+ diff --git a/packages/app/src/components/settings-cron-data.test.ts b/packages/app/src/components/settings-cron-data.test.ts
15
+ new file mode 100644
16
+ index 000000000..faf702691
17
+ --- /dev/null
18
+ +++ b/packages/app/src/components/settings-cron-data.test.ts
19
+ @@ -0,0 +1,96 @@
20
+ +import { describe, expect, test } from "bun:test"
21
+ +import { DEFAULT_JOB, loadCronSettings, serializeCronJobForEdit, type CronJobDefinition } from "./settings-cron-data"
22
+ +
23
+ +describe("loadCronSettings", () => {
24
+ + test("loads jobs, health, and contract without requesting handlers", async () => {
25
+ + const calls: Array<{ path: string; init?: RequestInit }> = []
26
+ + const requestJson = async <T>(path: string, init?: RequestInit) => {
27
+ + calls.push({ path, init })
28
+ + if (path === "/api/waffle/cron/jobs") {
29
+ + return { jobs: [] } as T
30
+ + }
31
+ + if (path === "/api/waffle/cron/health") {
32
+ + return { health: { enabled: true, jobs: { total: 0, enabled: 0 } } } as T
33
+ + }
34
+ + if (path === "/api/waffle/cron/manage") {
35
+ + return {
36
+ + contract: {
37
+ + runModes: {
38
+ + agent: { requires: ["agentPromptTemplate"], forbids: ["conditionModulePath"] },
39
+ + },
40
+ + },
41
+ + } as T
42
+ + }
43
+ + throw new Error(`Unexpected path: ${path}`)
44
+ + }
45
+ +
46
+ + const payload = await loadCronSettings(requestJson)
47
+ +
48
+ + expect(payload).toEqual({
49
+ + jobs: [],
50
+ + health: { enabled: true, jobs: { total: 0, enabled: 0 } },
51
+ + contract: {
52
+ + runModes: {
53
+ + agent: { requires: ["agentPromptTemplate"], forbids: ["conditionModulePath"] },
54
+ + },
55
+ + },
56
+ + })
57
+ + expect(calls).toEqual([
58
+ + { path: "/api/waffle/cron/jobs", init: undefined },
59
+ + { path: "/api/waffle/cron/health", init: undefined },
60
+ + {
61
+ + path: "/api/waffle/cron/manage",
62
+ + init: {
63
+ + method: "POST",
64
+ + body: JSON.stringify({ action: "describe_contract" }),
65
+ + },
66
+ + },
67
+ + ])
68
+ + })
69
+ +})
70
+ +
71
+ +describe("Cron settings payload helpers", () => {
72
+ + test("default job example uses the current agent contract", () => {
73
+ + const parsed = JSON.parse(DEFAULT_JOB)
74
+ + expect(parsed).toMatchObject({
75
+ + name: "Hourly review",
76
+ + scheduleKind: "every",
77
+ + everyMs: 3_600_000,
78
+ + runMode: "agent",
79
+ + agentPromptTemplate: expect.any(String),
80
+ + payload: {},
81
+ + })
82
+ + expect(parsed.handlerKey).toBeUndefined()
83
+ + })
84
+ +
85
+ + test("edit payload excludes deprecated handlerKey", () => {
86
+ + const job = {
87
+ + id: "cron-1",
88
+ + name: "Daily review",
89
+ + enabled: true,
90
+ + scheduleKind: "cron",
91
+ + scheduleExpr: "0 9 * * *",
92
+ + everyMs: null,
93
+ + atIso: null,
94
+ + timezone: "America/Chicago",
95
+ + runMode: "background",
96
+ + conditionModulePath: "cron/check.ts",
97
+ + conditionDescription: "Checks for changes",
98
+ + agentPromptTemplate: null,
99
+ + agentModelOverride: null,
100
+ + maxAttempts: 3,
101
+ + retryBackoffMs: 1_000,
102
+ + payload: { channel: "main" },
103
+ + } satisfies CronJobDefinition
104
+ +
105
+ + const parsed = JSON.parse(serializeCronJobForEdit(job)) as Record<string, unknown>
106
+ +
107
+ + expect(parsed).toMatchObject({
108
+ + name: "Daily review",
109
+ + runMode: "background",
110
+ + conditionModulePath: "cron/check.ts",
111
+ + payload: { channel: "main" },
112
+ + })
113
+ + expect(parsed.handlerKey).toBeUndefined()
114
+ + })
115
+ +})
116
+ diff --git a/packages/app/src/components/settings-cron-data.ts b/packages/app/src/components/settings-cron-data.ts
117
+ new file mode 100644
118
+ index 000000000..c747ce06f
119
+ --- /dev/null
120
+ +++ b/packages/app/src/components/settings-cron-data.ts
121
+ @@ -0,0 +1,101 @@
122
+ +import { prettyJson, waffleJson } from "@/utils/waffle"
123
+ +
124
+ +export type CronJobDefinition = {
125
+ + id: string
126
+ + name: string
127
+ + enabled: boolean
128
+ + scheduleKind: "at" | "every" | "cron"
129
+ + scheduleExpr: string | null
130
+ + everyMs: number | null
131
+ + atIso: string | null
132
+ + timezone: string | null
133
+ + runMode: "background" | "conditional_agent" | "agent"
134
+ + conditionModulePath: string | null
135
+ + conditionDescription: string | null
136
+ + agentPromptTemplate: string | null
137
+ + agentModelOverride: string | null
138
+ + maxAttempts: number
139
+ + retryBackoffMs: number
140
+ + payload: Record<string, unknown>
141
+ +}
142
+ +
143
+ +export type CronHealthSnapshot = {
144
+ + enabled: boolean
145
+ + jobs: {
146
+ + total: number
147
+ + enabled: number
148
+ + }
149
+ +}
150
+ +
151
+ +type CronContractMode = {
152
+ + requires?: string[]
153
+ + optional?: string[]
154
+ + forbids?: string[]
155
+ +}
156
+ +
157
+ +export type CronContract = {
158
+ + runModes: Record<string, CronContractMode>
159
+ +}
160
+ +
161
+ +type CronJobsResponse = {
162
+ + jobs: CronJobDefinition[]
163
+ +}
164
+ +
165
+ +type CronHealthResponse = {
166
+ + health: CronHealthSnapshot
167
+ +}
168
+ +
169
+ +type CronContractResponse = {
170
+ + contract: CronContract
171
+ +}
172
+ +
173
+ +type WaffleJson = <T>(path: string, init?: RequestInit) => Promise<T>
174
+ +
175
+ +const DEFAULT_AGENT_JOB = {
176
+ + name: "Hourly review",
177
+ + enabled: true,
178
+ + scheduleKind: "every",
179
+ + everyMs: 3_600_000,
180
+ + runMode: "agent",
181
+ + agentPromptTemplate: "Review recent workspace activity and summarize anything that needs attention.",
182
+ + payload: {},
183
+ +} as const
184
+ +
185
+ +export const DEFAULT_JOB = prettyJson(DEFAULT_AGENT_JOB)
186
+ +
187
+ +export async function loadCronSettings(requestJson: WaffleJson = waffleJson) {
188
+ + const [jobs, health, contract] = await Promise.all([
189
+ + requestJson<CronJobsResponse>("/api/waffle/cron/jobs"),
190
+ + requestJson<CronHealthResponse>("/api/waffle/cron/health"),
191
+ + requestJson<CronContractResponse>("/api/waffle/cron/manage", {
192
+ + method: "POST",
193
+ + body: JSON.stringify({ action: "describe_contract" }),
194
+ + }),
195
+ + ])
196
+ +
197
+ + return {
198
+ + jobs: jobs.jobs,
199
+ + health: health.health,
200
+ + contract: contract.contract,
201
+ + }
202
+ +}
203
+ +
204
+ +export function serializeCronJobForEdit(job: CronJobDefinition) {
205
+ + return prettyJson({
206
+ + name: job.name,
207
+ + enabled: job.enabled,
208
+ + scheduleKind: job.scheduleKind,
209
+ + scheduleExpr: job.scheduleExpr,
210
+ + everyMs: job.everyMs,
211
+ + atIso: job.atIso,
212
+ + timezone: job.timezone,
213
+ + runMode: job.runMode,
214
+ + conditionModulePath: job.conditionModulePath,
215
+ + conditionDescription: job.conditionDescription,
216
+ + agentPromptTemplate: job.agentPromptTemplate,
217
+ + agentModelOverride: job.agentModelOverride,
218
+ + maxAttempts: job.maxAttempts,
219
+ + retryBackoffMs: job.retryBackoffMs,
220
+ + payload: job.payload,
221
+ + })
222
+ +}
223
+ diff --git a/packages/app/src/components/settings-cron.tsx b/packages/app/src/components/settings-cron.tsx
224
+ index 8bda2bf9c..a5b15cb11 100644
225
+ --- a/packages/app/src/components/settings-cron.tsx
226
+ +++ b/packages/app/src/components/settings-cron.tsx
227
+ @@ -1,49 +1,11 @@
228
+ import { Button } from "@opencode-ai/ui/button"
229
+ import { showToast } from "@opencode-ai/ui/toast"
230
+ -import { createSignal, For, onMount, Show, type Component } from "solid-js"
231
+ +import { For, onMount, Show, type Component } from "solid-js"
232
+ import { createStore } from "solid-js/store"
233
+ -import { prettyJson, waffleJson } from "@/utils/waffle"
234
+ +import { waffleJson } from "@/utils/waffle"
235
+ +import { DEFAULT_JOB, loadCronSettings, serializeCronJobForEdit, type CronContract, type CronHealthSnapshot, type CronJobDefinition } from "./settings-cron-data"
236
+ import { WaffleCard, WaffleMetaRow, WaffleNotice, WaffleSettingsPage, WaffleSettingsSection, WaffleTextArea, WaffleToolbar } from "./settings-waffle-shared"
237
+
238
+ -type CronJobDefinition = {
239
+ - id: string
240
+ - name: string
241
+ - enabled: boolean
242
+ - scheduleKind: "at" | "every" | "cron"
243
+ - scheduleExpr: string | null
244
+ - everyMs: number | null
245
+ - atIso: string | null
246
+ - timezone: string | null
247
+ - runMode: "background" | "conditional_agent" | "agent"
248
+ - handlerKey: string | null
249
+ - conditionModulePath: string | null
250
+ - conditionDescription: string | null
251
+ - agentPromptTemplate: string | null
252
+ - agentModelOverride: string | null
253
+ - maxAttempts: number
254
+ - retryBackoffMs: number
255
+ - payload: Record<string, unknown>
256
+ -}
257
+ -
258
+ -type CronHealthSnapshot = {
259
+ - enabled: boolean
260
+ - jobs: {
261
+ - total: number
262
+ - enabled: number
263
+ - }
264
+ -}
265
+ -
266
+ -const DEFAULT_JOB = prettyJson({
267
+ - name: "Daily summary",
268
+ - enabled: true,
269
+ - scheduleKind: "cron",
270
+ - scheduleExpr: "0 9 * * *",
271
+ - timezone: "America/Phoenix",
272
+ - runMode: "background",
273
+ - handlerKey: "daily_summary",
274
+ - payload: {},
275
+ -})
276
+ -
277
+ export const SettingsCron: Component = () => {
278
+ const [state, setState] = createStore({
279
+ loading: true,
280
+ @@ -51,27 +13,21 @@ export const SettingsCron: Component = () => {
281
+ busyId: "",
282
+ error: "",
283
+ jobs: [] as CronJobDefinition[],
284
+ - handlers: [] as string[],
285
+ health: null as CronHealthSnapshot | null,
286
+ + contract: null as CronContract | null,
287
+ createText: DEFAULT_JOB,
288
+ editingId: "",
289
+ editText: "",
290
+ })
291
+ - const [refreshTick, setRefreshTick] = createSignal(0)
292
+
293
+ async function load() {
294
+ - refreshTick()
295
+ setState("loading", true)
296
+ setState("error", "")
297
+ try {
298
+ - const [jobs, handlers, health] = await Promise.all([
299
+ - waffleJson<{ jobs: CronJobDefinition[] }>("/api/waffle/cron/jobs"),
300
+ - waffleJson<{ handlers: string[] }>("/api/waffle/cron/handlers"),
301
+ - waffleJson<{ health: CronHealthSnapshot }>("/api/waffle/cron/health"),
302
+ - ])
303
+ - setState("jobs", jobs.jobs)
304
+ - setState("handlers", handlers.handlers)
305
+ - setState("health", health.health)
306
+ + const payload = await loadCronSettings()
307
+ + setState("jobs", payload.jobs)
308
+ + setState("health", payload.health)
309
+ + setState("contract", payload.contract)
310
+ } catch (error) {
311
+ setState("error", error instanceof Error ? error.message : "Failed to load cron jobs")
312
+ } finally {
313
+ @@ -80,7 +36,6 @@ export const SettingsCron: Component = () => {
314
+ }
315
+
316
+ async function refresh() {
317
+ - setRefreshTick((value) => value + 1)
318
+ await load()
319
+ }
320
+
321
+ @@ -203,13 +158,28 @@ export const SettingsCron: Component = () => {
322
+ )}
323
+ </Show>
324
+
325
+ - <WaffleSettingsSection title="Registered handlers">
326
+ - <div class="px-4 py-3 text-13-regular text-text-strong break-words">
327
+ - <Show when={state.handlers.length > 0} fallback={<span class="text-text-weak">No handlers registered.</span>}>
328
+ - {state.handlers.join(", ")}
329
+ - </Show>
330
+ - </div>
331
+ - </WaffleSettingsSection>
332
+ + <Show when={state.contract}>
333
+ + {(contract) => (
334
+ + <WaffleSettingsSection title="Run mode contract" description="Current backend requirements for each cron run mode.">
335
+ + <For each={Object.entries(contract().runModes)}>
336
+ + {([mode, details]) => (
337
+ + <div class="px-4 py-4 border-b border-border-weak-base last:border-none flex flex-col gap-1">
338
+ + <div class="text-14-medium text-text-strong">{mode}</div>
339
+ + <div class="text-12-regular text-text-weak">
340
+ + Requires: {details.requires?.length ? details.requires.join(", ") : "Nothing special"}
341
+ + </div>
342
+ + <Show when={details.optional?.length}>
343
+ + <div class="text-12-regular text-text-weak">Optional: {details.optional?.join(", ")}</div>
344
+ + </Show>
345
+ + <Show when={details.forbids?.length}>
346
+ + <div class="text-12-regular text-text-weak">Forbids: {details.forbids?.join(", ")}</div>
347
+ + </Show>
348
+ + </div>
349
+ + )}
350
+ + </For>
351
+ + </WaffleSettingsSection>
352
+ + )}
353
+ + </Show>
354
+
355
+ <WaffleSettingsSection title="Jobs">
356
+ <Show
357
+ @@ -235,29 +205,7 @@ export const SettingsCron: Component = () => {
358
+ size="large"
359
+ onClick={() => {
360
+ setState("editingId", state.editingId === job.id ? "" : job.id)
361
+ - setState(
362
+ - "editText",
363
+ - state.editingId === job.id
364
+ - ? ""
365
+ - : prettyJson({
366
+ - name: job.name,
367
+ - enabled: job.enabled,
368
+ - scheduleKind: job.scheduleKind,
369
+ - scheduleExpr: job.scheduleExpr,
370
+ - everyMs: job.everyMs,
371
+ - atIso: job.atIso,
372
+ - timezone: job.timezone,
373
+ - runMode: job.runMode,
374
+ - handlerKey: job.handlerKey,
375
+ - conditionModulePath: job.conditionModulePath,
376
+ - conditionDescription: job.conditionDescription,
377
+ - agentPromptTemplate: job.agentPromptTemplate,
378
+ - agentModelOverride: job.agentModelOverride,
379
+ - maxAttempts: job.maxAttempts,
380
+ - retryBackoffMs: job.retryBackoffMs,
381
+ - payload: job.payload,
382
+ - }),
383
+ - )
384
+ + setState("editText", state.editingId === job.id ? "" : serializeCronJobForEdit(job))
385
+ }}
386
+ >
387
+ {state.editingId === job.id ? "Close editor" : "Edit JSON"}
388
+ @@ -301,7 +249,7 @@ export const SettingsCron: Component = () => {
389
+ </Show>
390
+ </WaffleSettingsSection>
391
+
392
+ - <WaffleSettingsSection title="Create job" description="Post a complete cron job definition as JSON.">
393
+ + <WaffleSettingsSection title="Create job" description="Post a complete cron job definition as JSON using the current run mode contract.">
394
+ <WaffleCard
395
+ footer={
396
+ <div class="flex justify-end">
397
+ --
398
+ 2.53.0
399
+