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.
- package/.agents/skills/btca-cli/SKILL.md +64 -0
- package/.agents/skills/btca-cli/agents/openai.yaml +3 -0
- package/.agents/skills/frontend-design/SKILL.md +42 -0
- package/.agents/skills/frontend-design/agents/openai.yaml +3 -0
- package/.env.example +36 -0
- package/.githooks/pre-commit +33 -0
- package/.github/workflows/ci.yml +309 -0
- package/.opencode/bun.lock +18 -0
- package/.opencode/package.json +5 -0
- package/.opencode/tools/agent_type_manager.ts +100 -0
- package/.opencode/tools/config_manager.ts +87 -0
- package/.opencode/tools/cron_manager.ts +145 -0
- package/.opencode/tools/memory_get.ts +43 -0
- package/.opencode/tools/memory_remember.ts +53 -0
- package/.opencode/tools/memory_search.ts +48 -0
- package/AGENTS.md +126 -0
- package/MEMORY.md +2 -0
- package/README.md +451 -0
- package/THIRD_PARTY_NOTICES.md +11 -0
- package/agent-mockingbird.config.example.json +135 -0
- package/apps/server/package.json +32 -0
- package/apps/server/src/backend/agents/bootstrapContext.ts +362 -0
- package/apps/server/src/backend/agents/openclawImport.test.ts +133 -0
- package/apps/server/src/backend/agents/openclawImport.ts +797 -0
- package/apps/server/src/backend/agents/opencodeConfig.ts +428 -0
- package/apps/server/src/backend/agents/service.ts +10 -0
- package/apps/server/src/backend/config/example-config.test.ts +20 -0
- package/apps/server/src/backend/config/orchestration.ts +243 -0
- package/apps/server/src/backend/config/policy.ts +158 -0
- package/apps/server/src/backend/config/schema.test.ts +15 -0
- package/apps/server/src/backend/config/schema.ts +391 -0
- package/apps/server/src/backend/config/semantic.test.ts +34 -0
- package/apps/server/src/backend/config/semantic.ts +149 -0
- package/apps/server/src/backend/config/service.test.ts +75 -0
- package/apps/server/src/backend/config/service.ts +207 -0
- package/apps/server/src/backend/config/smoke.ts +77 -0
- package/apps/server/src/backend/config/store.test.ts +123 -0
- package/apps/server/src/backend/config/store.ts +581 -0
- package/apps/server/src/backend/config/testFixtures.ts +5 -0
- package/apps/server/src/backend/config/types.ts +56 -0
- package/apps/server/src/backend/contracts/events.ts +320 -0
- package/apps/server/src/backend/contracts/runtime.ts +111 -0
- package/apps/server/src/backend/cron/executor.ts +435 -0
- package/apps/server/src/backend/cron/repository.ts +170 -0
- package/apps/server/src/backend/cron/service.ts +660 -0
- package/apps/server/src/backend/cron/storage.ts +92 -0
- package/apps/server/src/backend/cron/types.ts +138 -0
- package/apps/server/src/backend/cron/utils.ts +351 -0
- package/apps/server/src/backend/db/client.ts +20 -0
- package/apps/server/src/backend/db/migrate.ts +40 -0
- package/apps/server/src/backend/db/repository.ts +1762 -0
- package/apps/server/src/backend/db/schema.ts +113 -0
- package/apps/server/src/backend/db/usageDashboard.test.ts +102 -0
- package/apps/server/src/backend/db/wipe.ts +13 -0
- package/apps/server/src/backend/defaults.ts +32 -0
- package/apps/server/src/backend/env.ts +48 -0
- package/apps/server/src/backend/heartbeat/activeHours.ts +45 -0
- package/apps/server/src/backend/heartbeat/defaultJob.ts +88 -0
- package/apps/server/src/backend/heartbeat/heartbeat.test.ts +110 -0
- package/apps/server/src/backend/heartbeat/runtimeService.ts +190 -0
- package/apps/server/src/backend/heartbeat/service.ts +176 -0
- package/apps/server/src/backend/heartbeat/state.test.ts +63 -0
- package/apps/server/src/backend/heartbeat/state.ts +167 -0
- package/apps/server/src/backend/heartbeat/types.ts +54 -0
- package/apps/server/src/backend/http/boundedQueue.test.ts +49 -0
- package/apps/server/src/backend/http/boundedQueue.ts +92 -0
- package/apps/server/src/backend/http/parsers.ts +40 -0
- package/apps/server/src/backend/http/router.ts +61 -0
- package/apps/server/src/backend/http/routes/agentRoutes.ts +67 -0
- package/apps/server/src/backend/http/routes/backgroundRoutes.ts +203 -0
- package/apps/server/src/backend/http/routes/chatRoutes.ts +107 -0
- package/apps/server/src/backend/http/routes/configRoutes.ts +602 -0
- package/apps/server/src/backend/http/routes/cronRoutes.ts +221 -0
- package/apps/server/src/backend/http/routes/dashboardRoutes.ts +308 -0
- package/apps/server/src/backend/http/routes/eventRoutes.ts +7 -0
- package/apps/server/src/backend/http/routes/heartbeatRoutes.test.ts +41 -0
- package/apps/server/src/backend/http/routes/heartbeatRoutes.ts +28 -0
- package/apps/server/src/backend/http/routes/index.ts +101 -0
- package/apps/server/src/backend/http/routes/mcpRoutes.ts +213 -0
- package/apps/server/src/backend/http/routes/memoryRoutes.ts +154 -0
- package/apps/server/src/backend/http/routes/runRoutes.ts +310 -0
- package/apps/server/src/backend/http/routes/runtimeRoutes.ts +197 -0
- package/apps/server/src/backend/http/routes/skillRoutes.ts +112 -0
- package/apps/server/src/backend/http/routes/uiRoutes.test.ts +161 -0
- package/apps/server/src/backend/http/routes/uiRoutes.ts +177 -0
- package/apps/server/src/backend/http/routes/usageRoutes.test.ts +104 -0
- package/apps/server/src/backend/http/routes/usageRoutes.ts +767 -0
- package/apps/server/src/backend/http/schemas.ts +64 -0
- package/apps/server/src/backend/http/sse.ts +144 -0
- package/apps/server/src/backend/integration/backend-core.test.ts +2316 -0
- package/apps/server/src/backend/logging/logger.ts +64 -0
- package/apps/server/src/backend/mcp/service.ts +326 -0
- package/apps/server/src/backend/memory/cli.ts +170 -0
- package/apps/server/src/backend/memory/conceptExpansion.test.ts +28 -0
- package/apps/server/src/backend/memory/conceptExpansion.ts +80 -0
- package/apps/server/src/backend/memory/qmdPort.test.ts +54 -0
- package/apps/server/src/backend/memory/qmdPort.ts +61 -0
- package/apps/server/src/backend/memory/records.test.ts +66 -0
- package/apps/server/src/backend/memory/records.ts +229 -0
- package/apps/server/src/backend/memory/service.ts +2012 -0
- package/apps/server/src/backend/memory/sqliteVec.ts +58 -0
- package/apps/server/src/backend/memory/types.ts +104 -0
- package/apps/server/src/backend/opencode/agentMockingbirdPlugin.test.ts +396 -0
- package/apps/server/src/backend/opencode/client.ts +98 -0
- package/apps/server/src/backend/opencode/models.ts +41 -0
- package/apps/server/src/backend/opencode/systemPrompt.test.ts +146 -0
- package/apps/server/src/backend/opencode/systemPrompt.ts +284 -0
- package/apps/server/src/backend/paths.ts +57 -0
- package/apps/server/src/backend/prompts/service.ts +100 -0
- package/apps/server/src/backend/queue/queue.test.ts +189 -0
- package/apps/server/src/backend/queue/service.ts +177 -0
- package/apps/server/src/backend/queue/types.ts +39 -0
- package/apps/server/src/backend/run/service.ts +576 -0
- package/apps/server/src/backend/run/storage.ts +47 -0
- package/apps/server/src/backend/run/types.ts +44 -0
- package/apps/server/src/backend/runtime/errors.ts +61 -0
- package/apps/server/src/backend/runtime/index.ts +72 -0
- package/apps/server/src/backend/runtime/memoryPromptDedup.test.ts +153 -0
- package/apps/server/src/backend/runtime/memoryPromptDedup.ts +76 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/backgroundMethods.ts +765 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/coreMethods.ts +705 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/eventMethods.ts +503 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/memoryMethods.ts +462 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/promptMethods.ts +1167 -0
- package/apps/server/src/backend/runtime/opencodeRuntime/shared.ts +254 -0
- package/apps/server/src/backend/runtime/opencodeRuntime.test.ts +2899 -0
- package/apps/server/src/backend/runtime/opencodeRuntime.ts +135 -0
- package/apps/server/src/backend/runtime/sessionScope.ts +45 -0
- package/apps/server/src/backend/skills/service.ts +442 -0
- package/apps/server/src/backend/workspace/resolve.ts +27 -0
- package/apps/server/src/cli/agent-mockingbird.mjs +2522 -0
- package/apps/server/src/cli/agent-mockingbird.test.ts +68 -0
- package/apps/server/src/cli/runtime-assets.mjs +269 -0
- package/apps/server/src/cli/runtime-assets.test.ts +52 -0
- package/apps/server/src/cli/runtime-layout.mjs +75 -0
- package/apps/server/src/cli/standaloneBuild.test.ts +19 -0
- package/apps/server/src/cli/standaloneBuild.ts +19 -0
- package/apps/server/src/cli/standaloneCronBinary.test.ts +187 -0
- package/apps/server/src/index.ts +178 -0
- package/apps/server/tsconfig.json +12 -0
- package/backlog.md +5 -0
- package/bin/agent-mockingbird +2522 -0
- package/bin/runtime-layout.mjs +75 -0
- package/build-bin.ts +34 -0
- package/build-cli.mjs +37 -0
- package/build.ts +40 -0
- package/bun-env.d.ts +11 -0
- package/bun.lock +888 -0
- package/bunfig.toml +2 -0
- package/components.json +21 -0
- package/config.json +130 -0
- package/deploy/RELEASE_INSTALL.md +112 -0
- package/deploy/docker-compose.yml +42 -0
- package/deploy/systemd/README.md +46 -0
- package/deploy/systemd/agent-mockingbird.service +28 -0
- package/deploy/systemd/opencode.service +25 -0
- package/docs/legacy-config-ui-reference.md +51 -0
- package/docs/memory-e2e-trace-2026-03-04.md +63 -0
- package/docs/memory-ops.md +96 -0
- package/docs/memory-runtime-contract.md +42 -0
- package/docs/memory-tuning-remote-2026-03-04.md +59 -0
- package/docs/opencode-rebase-workflow-plan.md +614 -0
- package/docs/opencode-startup-sync-plan.md +94 -0
- package/docs/vendor-opencode.md +41 -0
- package/drizzle/0000_famous_turbo.sql +49 -0
- package/drizzle/0001_cron_memory_aux.sql +160 -0
- package/drizzle/0002_runtime_session_bindings.sql +28 -0
- package/drizzle/0003_background_runs.sql +27 -0
- package/drizzle/0004_memory_open_write.sql +63 -0
- package/drizzle/0005_signal_channel.sql +47 -0
- package/drizzle/0006_usage_event_dimensions.sql +7 -0
- package/drizzle/meta/0000_snapshot.json +341 -0
- package/drizzle/meta/_journal.json +55 -0
- package/drizzle.config.ts +14 -0
- package/eslint.config.mjs +77 -0
- package/knip.json +18 -0
- package/memory/2026-03-04.md +4 -0
- package/opencode.lock.json +16 -0
- package/package.json +67 -0
- package/packages/agent-mockingbird-installer/README.md +31 -0
- package/packages/agent-mockingbird-installer/bin/agent-mockingbird-installer.mjs +44 -0
- package/packages/agent-mockingbird-installer/opencode.lock.json +16 -0
- package/packages/agent-mockingbird-installer/package.json +23 -0
- package/packages/contracts/package.json +19 -0
- package/packages/contracts/src/agentTypes.ts +122 -0
- package/packages/contracts/src/cron.ts +146 -0
- package/packages/contracts/src/dashboard.ts +378 -0
- package/packages/contracts/src/index.ts +3 -0
- package/packages/contracts/tsconfig.json +4 -0
- package/patches/opencode/0001-Wafflebot-OpenCode-baseline.patch +2341 -0
- package/patches/opencode/0002-Fix-OpenCode-web-entry-and-settings-icons.patch +104 -0
- package/patches/opencode/0003-fix-app-remove-duplicate-sidebar-mount.patch +32 -0
- package/patches/opencode/0004-Add-heartbeat-settings-and-usage-nav.patch +506 -0
- package/patches/opencode/0005-Use-chart-icon-for-usage-nav.patch +38 -0
- package/patches/opencode/0006-Modernize-cron-settings.patch +399 -0
- package/patches/opencode/0007-Rename-waffle-namespaces-to-mockingbird.patch +1110 -0
- package/patches/opencode/0008-Remove-cron-contract-section.patch +178 -0
- package/patches/opencode/0009-Rework-cron-tab-as-operations-console.patch +414 -0
- package/patches/opencode/0010-Refine-heartbeat-settings-controls.patch +208 -0
- package/runtime-assets/opencode-config/opencode.jsonc +25 -0
- package/runtime-assets/opencode-config/package.json +5 -0
- package/runtime-assets/opencode-config/plugins/agent-mockingbird.ts +715 -0
- package/runtime-assets/workspace/.agents/skills/config-auditor/SKILL.md +25 -0
- package/runtime-assets/workspace/.agents/skills/config-editor/SKILL.md +24 -0
- package/runtime-assets/workspace/.agents/skills/cron-manager/SKILL.md +57 -0
- package/runtime-assets/workspace/.agents/skills/memory-ops/SKILL.md +120 -0
- package/runtime-assets/workspace/.agents/skills/runtime-diagnose/SKILL.md +25 -0
- package/runtime-assets/workspace/AGENTS.md +56 -0
- package/runtime-assets/workspace/MEMORY.md +4 -0
- package/scripts/build-release-bundle.sh +66 -0
- package/scripts/check-ship.ts +383 -0
- package/scripts/dev-opencode.sh +17 -0
- package/scripts/dev-stack-opencode.sh +15 -0
- package/scripts/dev-stack.sh +61 -0
- package/scripts/install-systemd.sh +87 -0
- package/scripts/memory-e2e.sh +76 -0
- package/scripts/memory-trace-e2e.sh +141 -0
- package/scripts/migrate-opencode-env.ts +108 -0
- package/scripts/onboard/bootstrap.sh +32 -0
- package/scripts/opencode-swap.ts +78 -0
- package/scripts/opencode-sync.ts +715 -0
- package/scripts/runtime-assets-sync.mjs +83 -0
- package/scripts/setup-git-hooks.ts +39 -0
- package/tsconfig.json +45 -0
- package/tui.json +98 -0
- package/turbo.json +36 -0
- package/vendor/OPENCODE_VENDOR.md +13 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
From e6f3084be1883e1d02911b034ff4b1903de01ced Mon Sep 17 00:00:00 2001
|
|
2
|
+
From: Matt Campbell <matt@battleshopper.com>
|
|
3
|
+
Date: Mon, 16 Mar 2026 14:17:21 -0500
|
|
4
|
+
Subject: [PATCH 02/10] Fix OpenCode web entry and settings icons
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
.../app/src/components/dialog-settings.tsx | 10 +++---
|
|
8
|
+
packages/app/src/entry.tsx | 33 +++++++++++++++----
|
|
9
|
+
2 files changed, 32 insertions(+), 11 deletions(-)
|
|
10
|
+
|
|
11
|
+
diff --git a/packages/app/src/components/dialog-settings.tsx b/packages/app/src/components/dialog-settings.tsx
|
|
12
|
+
index 005920ccb..460555ed1 100644
|
|
13
|
+
--- a/packages/app/src/components/dialog-settings.tsx
|
|
14
|
+
+++ b/packages/app/src/components/dialog-settings.tsx
|
|
15
|
+
@@ -57,23 +57,23 @@ export const DialogSettings: Component = () => {
|
|
16
|
+
<Tabs.SectionTitle>Agent Mockingbird</Tabs.SectionTitle>
|
|
17
|
+
<div class="flex flex-col gap-1.5 w-full">
|
|
18
|
+
<Tabs.Trigger value="agents">
|
|
19
|
+
- <Icon name="sparkles" />
|
|
20
|
+
+ <Icon name="brain" />
|
|
21
|
+
{language.t("settings.agents.title")}
|
|
22
|
+
</Tabs.Trigger>
|
|
23
|
+
<Tabs.Trigger value="mcp">
|
|
24
|
+
- <Icon name="plug-2" />
|
|
25
|
+
+ <Icon name="providers" />
|
|
26
|
+
{language.t("settings.mcp.title")}
|
|
27
|
+
</Tabs.Trigger>
|
|
28
|
+
<Tabs.Trigger value="skills">
|
|
29
|
+
- <Icon name="book-open" />
|
|
30
|
+
+ <Icon name="help" />
|
|
31
|
+
{language.t("settings.skills.title")}
|
|
32
|
+
</Tabs.Trigger>
|
|
33
|
+
<Tabs.Trigger value="runtime">
|
|
34
|
+
- <Icon name="terminal-square" />
|
|
35
|
+
+ <Icon name="terminal" />
|
|
36
|
+
{language.t("settings.runtime.title")}
|
|
37
|
+
</Tabs.Trigger>
|
|
38
|
+
<Tabs.Trigger value="cron">
|
|
39
|
+
- <Icon name="clock-3" />
|
|
40
|
+
+ <Icon name="warning" />
|
|
41
|
+
{language.t("settings.cron.title")}
|
|
42
|
+
</Tabs.Trigger>
|
|
43
|
+
</div>
|
|
44
|
+
diff --git a/packages/app/src/entry.tsx b/packages/app/src/entry.tsx
|
|
45
|
+
index 2198a7bc1..52b6e7669 100644
|
|
46
|
+
--- a/packages/app/src/entry.tsx
|
|
47
|
+
+++ b/packages/app/src/entry.tsx
|
|
48
|
+
@@ -9,6 +9,8 @@ import { handleNotificationClick } from "@/utils/notification-click"
|
|
49
|
+
import pkg from "../package.json"
|
|
50
|
+
import { ServerConnection } from "./context/server"
|
|
51
|
+
|
|
52
|
+
+const DEFAULT_SERVER_URL_KEY = "opencode.settings.dat:defaultServerUrl"
|
|
53
|
+
+
|
|
54
|
+
const getLocale = () => {
|
|
55
|
+
if (typeof navigator !== "object") return "en" as const
|
|
56
|
+
const languages = navigator.languages?.length ? navigator.languages : [navigator.language]
|
|
57
|
+
@@ -25,6 +27,31 @@ const getRootNotFoundError = () => {
|
|
58
|
+
return locale === "zh" ? (zh[key] ?? en[key]) : en[key]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
+const getStorage = (key: string) => {
|
|
62
|
+
+ if (typeof localStorage === "undefined") return null
|
|
63
|
+
+ try {
|
|
64
|
+
+ return localStorage.getItem(key)
|
|
65
|
+
+ } catch {
|
|
66
|
+
+ return null
|
|
67
|
+
+ }
|
|
68
|
+
+}
|
|
69
|
+
+
|
|
70
|
+
+const setStorage = (key: string, value: string | null) => {
|
|
71
|
+
+ if (typeof localStorage === "undefined") return
|
|
72
|
+
+ try {
|
|
73
|
+
+ if (value !== null) {
|
|
74
|
+
+ localStorage.setItem(key, value)
|
|
75
|
+
+ return
|
|
76
|
+
+ }
|
|
77
|
+
+ localStorage.removeItem(key)
|
|
78
|
+
+ } catch {
|
|
79
|
+
+ return
|
|
80
|
+
+ }
|
|
81
|
+
+}
|
|
82
|
+
+
|
|
83
|
+
+const readDefaultServerUrl = () => getStorage(DEFAULT_SERVER_URL_KEY)
|
|
84
|
+
+const writeDefaultServerUrl = (url: string | null) => setStorage(DEFAULT_SERVER_URL_KEY, url)
|
|
85
|
+
+
|
|
86
|
+
const notify: Platform["notify"] = async (title, description, href) => {
|
|
87
|
+
if (!("Notification" in window)) return
|
|
88
|
+
|
|
89
|
+
@@ -77,12 +104,6 @@ const getCurrentUrl = () => {
|
|
90
|
+
return location.origin
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
-const getDefaultUrl = () => {
|
|
94
|
+
- const lsDefault = readDefaultServerUrl()
|
|
95
|
+
- if (lsDefault) return lsDefault
|
|
96
|
+
- return getCurrentUrl()
|
|
97
|
+
-}
|
|
98
|
+
-
|
|
99
|
+
const platform: Platform = {
|
|
100
|
+
platform: "web",
|
|
101
|
+
version: pkg.version,
|
|
102
|
+
--
|
|
103
|
+
2.53.0
|
|
104
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
From 862ffa45d820c9ba32cdce55dbc10a64e9c744b1 Mon Sep 17 00:00:00 2001
|
|
2
|
+
From: Matt Campbell <matt@battleshopper.com>
|
|
3
|
+
Date: Mon, 16 Mar 2026 21:06:59 -0500
|
|
4
|
+
Subject: [PATCH 03/10] fix(app): remove duplicate sidebar mount
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
packages/app/src/pages/layout.tsx | 2 --
|
|
8
|
+
1 file changed, 2 deletions(-)
|
|
9
|
+
|
|
10
|
+
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
|
|
11
|
+
index 5626c040b..9df2a3681 100644
|
|
12
|
+
--- a/packages/app/src/pages/layout.tsx
|
|
13
|
+
+++ b/packages/app/src/pages/layout.tsx
|
|
14
|
+
@@ -2272,7 +2272,6 @@ export default function Layout(props: ParentProps) {
|
|
15
|
+
arm()
|
|
16
|
+
}}
|
|
17
|
+
>
|
|
18
|
+
- <div class="@container w-full h-full contain-strict">{sidebarContent()}</div>
|
|
19
|
+
<div class="@container w-full h-full contain-strict">{sidebarContent()}</div>
|
|
20
|
+
<Show when={layout.sidebar.opened()}>
|
|
21
|
+
<div onPointerDown={() => setState("sizing", true)}>
|
|
22
|
+
@@ -2321,7 +2320,6 @@ export default function Layout(props: ParentProps) {
|
|
23
|
+
onClick={(e) => e.stopPropagation()}
|
|
24
|
+
>
|
|
25
|
+
{sidebarContent(true)}
|
|
26
|
+
- {sidebarContent(true)}
|
|
27
|
+
</nav>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
--
|
|
31
|
+
2.53.0
|
|
32
|
+
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
From d0ccb6f44aaff458f67afa3bde01cf7fd0ddf3ab Mon Sep 17 00:00:00 2001
|
|
2
|
+
From: Matt Campbell <matt@battleshopper.com>
|
|
3
|
+
Date: Wed, 18 Mar 2026 15:36:33 -0500
|
|
4
|
+
Subject: [PATCH 04/10] Add heartbeat settings and usage nav
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
.../app/src/components/dialog-settings.tsx | 8 +
|
|
8
|
+
.../app/src/components/settings-heartbeat.tsx | 409 ++++++++++++++++++
|
|
9
|
+
packages/app/src/pages/layout.tsx | 2 +
|
|
10
|
+
.../app/src/pages/layout/sidebar-shell.tsx | 11 +
|
|
11
|
+
4 files changed, 430 insertions(+)
|
|
12
|
+
create mode 100644 packages/app/src/components/settings-heartbeat.tsx
|
|
13
|
+
|
|
14
|
+
diff --git a/packages/app/src/components/dialog-settings.tsx b/packages/app/src/components/dialog-settings.tsx
|
|
15
|
+
index 460555ed1..8b51a29fd 100644
|
|
16
|
+
--- a/packages/app/src/components/dialog-settings.tsx
|
|
17
|
+
+++ b/packages/app/src/components/dialog-settings.tsx
|
|
18
|
+
@@ -13,6 +13,7 @@ import { SettingsMcp } from "./settings-mcp"
|
|
19
|
+
import { SettingsSkills } from "./settings-skills"
|
|
20
|
+
import { SettingsRuntime } from "./settings-runtime"
|
|
21
|
+
import { SettingsCron } from "./settings-cron"
|
|
22
|
+
+import { SettingsHeartbeat } from "./settings-heartbeat"
|
|
23
|
+
|
|
24
|
+
export const DialogSettings: Component = () => {
|
|
25
|
+
const language = useLanguage()
|
|
26
|
+
@@ -72,6 +73,10 @@ export const DialogSettings: Component = () => {
|
|
27
|
+
<Icon name="terminal" />
|
|
28
|
+
{language.t("settings.runtime.title")}
|
|
29
|
+
</Tabs.Trigger>
|
|
30
|
+
+ <Tabs.Trigger value="heartbeat">
|
|
31
|
+
+ <Icon name="status" />
|
|
32
|
+
+ Heartbeat
|
|
33
|
+
+ </Tabs.Trigger>
|
|
34
|
+
<Tabs.Trigger value="cron">
|
|
35
|
+
<Icon name="warning" />
|
|
36
|
+
{language.t("settings.cron.title")}
|
|
37
|
+
@@ -110,6 +115,9 @@ export const DialogSettings: Component = () => {
|
|
38
|
+
<Tabs.Content value="runtime" class="no-scrollbar">
|
|
39
|
+
<SettingsRuntime />
|
|
40
|
+
</Tabs.Content>
|
|
41
|
+
+ <Tabs.Content value="heartbeat" class="no-scrollbar">
|
|
42
|
+
+ <SettingsHeartbeat />
|
|
43
|
+
+ </Tabs.Content>
|
|
44
|
+
<Tabs.Content value="cron" class="no-scrollbar">
|
|
45
|
+
<SettingsCron />
|
|
46
|
+
</Tabs.Content>
|
|
47
|
+
diff --git a/packages/app/src/components/settings-heartbeat.tsx b/packages/app/src/components/settings-heartbeat.tsx
|
|
48
|
+
new file mode 100644
|
|
49
|
+
index 000000000..95a1770cc
|
|
50
|
+
--- /dev/null
|
|
51
|
+
+++ b/packages/app/src/components/settings-heartbeat.tsx
|
|
52
|
+
@@ -0,0 +1,409 @@
|
|
53
|
+
+import { Button } from "@opencode-ai/ui/button"
|
|
54
|
+
+import { Switch } from "@opencode-ai/ui/switch"
|
|
55
|
+
+import { showToast } from "@opencode-ai/ui/toast"
|
|
56
|
+
+import { onMount, Show, type Component } from "solid-js"
|
|
57
|
+
+import { createStore } from "solid-js/store"
|
|
58
|
+
+import { waffleJson } from "@/utils/waffle"
|
|
59
|
+
+import {
|
|
60
|
+
+ WaffleCard,
|
|
61
|
+
+ WaffleInput,
|
|
62
|
+
+ WaffleMetaRow,
|
|
63
|
+
+ WaffleNotice,
|
|
64
|
+
+ WaffleSettingsPage,
|
|
65
|
+
+ WaffleSettingsSection,
|
|
66
|
+
+ WaffleTextArea,
|
|
67
|
+
+ WaffleToolbar,
|
|
68
|
+
+} from "./settings-waffle-shared"
|
|
69
|
+
+
|
|
70
|
+
+type HeartbeatActiveHours = {
|
|
71
|
+
+ start: string
|
|
72
|
+
+ end: string
|
|
73
|
+
+ timezone: string
|
|
74
|
+
+} | null
|
|
75
|
+
+
|
|
76
|
+
+type HeartbeatRuntimeConfig = {
|
|
77
|
+
+ enabled: boolean
|
|
78
|
+
+ interval: string
|
|
79
|
+
+ agentId: string
|
|
80
|
+
+ model: string
|
|
81
|
+
+ prompt: string
|
|
82
|
+
+ ackMaxChars: number
|
|
83
|
+
+ activeHours?: HeartbeatActiveHours
|
|
84
|
+
+}
|
|
85
|
+
+
|
|
86
|
+
+type HeartbeatStatus = {
|
|
87
|
+
+ config: HeartbeatRuntimeConfig
|
|
88
|
+
+ state: {
|
|
89
|
+
+ sessionId: string | null
|
|
90
|
+
+ running: boolean
|
|
91
|
+
+ lastRunAt: string | null
|
|
92
|
+
+ lastResult: "idle" | "acknowledged" | "attention" | "skipped" | "error"
|
|
93
|
+
+ lastResponse: string | null
|
|
94
|
+
+ lastError: string | null
|
|
95
|
+
+ updatedAt: string
|
|
96
|
+
+ }
|
|
97
|
+
+ nextDueAt: string | null
|
|
98
|
+
+}
|
|
99
|
+
+
|
|
100
|
+
+type HeartbeatStatusPayload = {
|
|
101
|
+
+ heartbeat: HeartbeatStatus
|
|
102
|
+
+}
|
|
103
|
+
+
|
|
104
|
+
+type RuntimePayload = {
|
|
105
|
+
+ hash: string
|
|
106
|
+
+ path: string
|
|
107
|
+
+}
|
|
108
|
+
+
|
|
109
|
+
+type HeartbeatFormState = {
|
|
110
|
+
+ enabled: boolean
|
|
111
|
+
+ interval: string
|
|
112
|
+
+ model: string
|
|
113
|
+
+ agentId: string
|
|
114
|
+
+ ackMaxChars: string
|
|
115
|
+
+ prompt: string
|
|
116
|
+
+ activeHoursEnabled: boolean
|
|
117
|
+
+ activeStart: string
|
|
118
|
+
+ activeEnd: string
|
|
119
|
+
+ activeTimezone: string
|
|
120
|
+
+}
|
|
121
|
+
+
|
|
122
|
+
+function toForm(config: HeartbeatRuntimeConfig): HeartbeatFormState {
|
|
123
|
+
+ return {
|
|
124
|
+
+ enabled: config.enabled,
|
|
125
|
+
+ interval: config.interval,
|
|
126
|
+
+ model: config.model,
|
|
127
|
+
+ agentId: config.agentId,
|
|
128
|
+
+ ackMaxChars: String(config.ackMaxChars),
|
|
129
|
+
+ prompt: config.prompt,
|
|
130
|
+
+ activeHoursEnabled: !!config.activeHours,
|
|
131
|
+
+ activeStart: config.activeHours?.start ?? "09:00",
|
|
132
|
+
+ activeEnd: config.activeHours?.end ?? "17:00",
|
|
133
|
+
+ activeTimezone: config.activeHours?.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC",
|
|
134
|
+
+ }
|
|
135
|
+
+}
|
|
136
|
+
+
|
|
137
|
+
+function formatTimestamp(value: string | null) {
|
|
138
|
+
+ if (!value) return "Never"
|
|
139
|
+
+ const date = new Date(value)
|
|
140
|
+
+ if (Number.isNaN(date.getTime())) return value
|
|
141
|
+
+ return date.toLocaleString()
|
|
142
|
+
+}
|
|
143
|
+
+
|
|
144
|
+
+function formatResult(value: HeartbeatStatus["state"]["lastResult"]) {
|
|
145
|
+
+ switch (value) {
|
|
146
|
+
+ case "acknowledged":
|
|
147
|
+
+ return "Acknowledged"
|
|
148
|
+
+ case "attention":
|
|
149
|
+
+ return "Needs attention"
|
|
150
|
+
+ case "skipped":
|
|
151
|
+
+ return "Skipped"
|
|
152
|
+
+ case "error":
|
|
153
|
+
+ return "Error"
|
|
154
|
+
+ default:
|
|
155
|
+
+ return "Idle"
|
|
156
|
+
+ }
|
|
157
|
+
+}
|
|
158
|
+
+
|
|
159
|
+
+function buildHeartbeatPatch(form: HeartbeatFormState): HeartbeatRuntimeConfig {
|
|
160
|
+
+ const interval = form.interval.trim()
|
|
161
|
+
+ const model = form.model.trim()
|
|
162
|
+
+ const agentId = form.agentId.trim()
|
|
163
|
+
+ const prompt = form.prompt.trim()
|
|
164
|
+
+ const ackMaxChars = Number(form.ackMaxChars)
|
|
165
|
+
+
|
|
166
|
+
+ if (!interval) throw new Error("Interval is required")
|
|
167
|
+
+ if (!model) throw new Error("Model is required")
|
|
168
|
+
+ if (!prompt) throw new Error("Prompt is required")
|
|
169
|
+
+ if (!Number.isFinite(ackMaxChars) || ackMaxChars < 1) throw new Error("Ack max chars must be a positive number")
|
|
170
|
+
+
|
|
171
|
+
+ const activeHours =
|
|
172
|
+
+ form.activeHoursEnabled
|
|
173
|
+
+ ? {
|
|
174
|
+
+ start: form.activeStart.trim(),
|
|
175
|
+
+ end: form.activeEnd.trim(),
|
|
176
|
+
+ timezone: form.activeTimezone.trim(),
|
|
177
|
+
+ }
|
|
178
|
+
+ : null
|
|
179
|
+
+
|
|
180
|
+
+ if (activeHours && (!activeHours.start || !activeHours.end || !activeHours.timezone)) {
|
|
181
|
+
+ throw new Error("Active hours require a start, end, and timezone")
|
|
182
|
+
+ }
|
|
183
|
+
+
|
|
184
|
+
+ return {
|
|
185
|
+
+ enabled: form.enabled,
|
|
186
|
+
+ interval,
|
|
187
|
+
+ model,
|
|
188
|
+
+ agentId,
|
|
189
|
+
+ prompt,
|
|
190
|
+
+ ackMaxChars,
|
|
191
|
+
+ activeHours,
|
|
192
|
+
+ }
|
|
193
|
+
+}
|
|
194
|
+
+
|
|
195
|
+
+export const SettingsHeartbeat: Component = () => {
|
|
196
|
+
+ const [state, setState] = createStore({
|
|
197
|
+
+ loading: true,
|
|
198
|
+
+ saving: false,
|
|
199
|
+
+ running: false,
|
|
200
|
+
+ error: "",
|
|
201
|
+
+ runtimeHash: "",
|
|
202
|
+
+ runtimePath: "",
|
|
203
|
+
+ status: null as HeartbeatStatus | null,
|
|
204
|
+
+ form: {
|
|
205
|
+
+ enabled: true,
|
|
206
|
+
+ interval: "15m",
|
|
207
|
+
+ model: "",
|
|
208
|
+
+ agentId: "build",
|
|
209
|
+
+ ackMaxChars: "280",
|
|
210
|
+
+ prompt: "",
|
|
211
|
+
+ activeHoursEnabled: false,
|
|
212
|
+
+ activeStart: "09:00",
|
|
213
|
+
+ activeEnd: "17:00",
|
|
214
|
+
+ activeTimezone: "UTC",
|
|
215
|
+
+ } as HeartbeatFormState,
|
|
216
|
+
+ })
|
|
217
|
+
+
|
|
218
|
+
+ async function load() {
|
|
219
|
+
+ setState("loading", true)
|
|
220
|
+
+ setState("error", "")
|
|
221
|
+
+ try {
|
|
222
|
+
+ const [heartbeat, runtime] = await Promise.all([
|
|
223
|
+
+ waffleJson<HeartbeatStatusPayload>("/api/waffle/heartbeat"),
|
|
224
|
+
+ waffleJson<RuntimePayload>("/api/waffle/runtime/config"),
|
|
225
|
+
+ ])
|
|
226
|
+
+ setState("status", heartbeat.heartbeat)
|
|
227
|
+
+ setState("runtimeHash", runtime.hash)
|
|
228
|
+
+ setState("runtimePath", runtime.path)
|
|
229
|
+
+ setState("form", toForm(heartbeat.heartbeat.config))
|
|
230
|
+
+ } catch (error) {
|
|
231
|
+
+ setState("error", error instanceof Error ? error.message : "Failed to load heartbeat settings")
|
|
232
|
+
+ } finally {
|
|
233
|
+
+ setState("loading", false)
|
|
234
|
+
+ }
|
|
235
|
+
+ }
|
|
236
|
+
+
|
|
237
|
+
+ async function save() {
|
|
238
|
+
+ let heartbeatPatch: HeartbeatRuntimeConfig
|
|
239
|
+
+ try {
|
|
240
|
+
+ heartbeatPatch = buildHeartbeatPatch(state.form)
|
|
241
|
+
+ } catch (error) {
|
|
242
|
+
+ setState("error", error instanceof Error ? error.message : "Invalid heartbeat settings")
|
|
243
|
+
+ return
|
|
244
|
+
+ }
|
|
245
|
+
+
|
|
246
|
+
+ setState("saving", true)
|
|
247
|
+
+ setState("error", "")
|
|
248
|
+
+ try {
|
|
249
|
+
+ const runtime = await waffleJson<RuntimePayload>("/api/waffle/runtime/config", {
|
|
250
|
+
+ method: "PATCH",
|
|
251
|
+
+ body: JSON.stringify({
|
|
252
|
+
+ patch: {
|
|
253
|
+
+ runtime: {
|
|
254
|
+
+ heartbeat: heartbeatPatch,
|
|
255
|
+
+ },
|
|
256
|
+
+ },
|
|
257
|
+
+ expectedHash: state.runtimeHash || undefined,
|
|
258
|
+
+ }),
|
|
259
|
+
+ })
|
|
260
|
+
+ setState("runtimeHash", runtime.hash)
|
|
261
|
+
+ setState("runtimePath", runtime.path)
|
|
262
|
+
+ showToast({
|
|
263
|
+
+ variant: "success",
|
|
264
|
+
+ icon: "circle-check",
|
|
265
|
+
+ title: "Heartbeat settings saved",
|
|
266
|
+
+ })
|
|
267
|
+
+ await load()
|
|
268
|
+
+ } catch (error) {
|
|
269
|
+
+ setState("error", error instanceof Error ? error.message : "Failed to save heartbeat settings")
|
|
270
|
+
+ } finally {
|
|
271
|
+
+ setState("saving", false)
|
|
272
|
+
+ }
|
|
273
|
+
+ }
|
|
274
|
+
+
|
|
275
|
+
+ async function runNow() {
|
|
276
|
+
+ setState("running", true)
|
|
277
|
+
+ setState("error", "")
|
|
278
|
+
+ try {
|
|
279
|
+
+ const payload = await waffleJson<HeartbeatStatusPayload>("/api/waffle/heartbeat/run", {
|
|
280
|
+
+ method: "POST",
|
|
281
|
+
+ })
|
|
282
|
+
+ setState("status", payload.heartbeat)
|
|
283
|
+
+ setState("form", toForm(payload.heartbeat.config))
|
|
284
|
+
+ showToast({
|
|
285
|
+
+ variant: "success",
|
|
286
|
+
+ icon: "circle-check",
|
|
287
|
+
+ title: "Heartbeat run completed",
|
|
288
|
+
+ })
|
|
289
|
+
+ } catch (error) {
|
|
290
|
+
+ setState("error", error instanceof Error ? error.message : "Failed to run heartbeat")
|
|
291
|
+
+ } finally {
|
|
292
|
+
+ setState("running", false)
|
|
293
|
+
+ }
|
|
294
|
+
+ }
|
|
295
|
+
+
|
|
296
|
+
+ onMount(() => {
|
|
297
|
+
+ void load()
|
|
298
|
+
+ })
|
|
299
|
+
+
|
|
300
|
+
+ return (
|
|
301
|
+
+ <WaffleSettingsPage
|
|
302
|
+
+ title="Heartbeat"
|
|
303
|
+
+ description="Monitor the dedicated heartbeat session, control its schedule, and choose its model explicitly."
|
|
304
|
+
+ actions={
|
|
305
|
+
+ <WaffleToolbar>
|
|
306
|
+
+ <Button variant="ghost" size="large" onClick={() => void load()} disabled={state.loading || state.saving || state.running}>
|
|
307
|
+
+ Refresh
|
|
308
|
+
+ </Button>
|
|
309
|
+
+ <Button variant="ghost" size="large" onClick={() => void runNow()} disabled={state.loading || state.saving || state.running}>
|
|
310
|
+
+ Run now
|
|
311
|
+
+ </Button>
|
|
312
|
+
+ <Button size="large" onClick={() => void save()} disabled={state.loading || state.saving || state.running}>
|
|
313
|
+
+ Save
|
|
314
|
+
+ </Button>
|
|
315
|
+
+ </WaffleToolbar>
|
|
316
|
+
+ }
|
|
317
|
+
+ >
|
|
318
|
+
+ <Show when={state.error}>
|
|
319
|
+
+ <WaffleNotice tone="error">{state.error}</WaffleNotice>
|
|
320
|
+
+ </Show>
|
|
321
|
+
+
|
|
322
|
+
+ <Show when={state.status}>
|
|
323
|
+
+ {(status) => (
|
|
324
|
+
+ <>
|
|
325
|
+
+ <WaffleSettingsSection title="Status" description="Current scheduler state for the dedicated Heartbeat session.">
|
|
326
|
+
+ <WaffleMetaRow label="Config file" value={state.runtimePath || "Unavailable"} />
|
|
327
|
+
+ <WaffleMetaRow label="Enabled" value={status().config.enabled ? "Yes" : "No"} />
|
|
328
|
+
+ <WaffleMetaRow label="Session id" value={status().state.sessionId ?? "Not created yet"} />
|
|
329
|
+
+ <WaffleMetaRow label="Currently running" value={status().state.running ? "Yes" : "No"} />
|
|
330
|
+
+ <WaffleMetaRow label="Last run" value={formatTimestamp(status().state.lastRunAt)} />
|
|
331
|
+
+ <WaffleMetaRow label="Next due" value={formatTimestamp(status().nextDueAt)} />
|
|
332
|
+
+ <WaffleMetaRow label="Last result" value={formatResult(status().state.lastResult)} />
|
|
333
|
+
+ <WaffleMetaRow label="Last updated" value={formatTimestamp(status().state.updatedAt)} />
|
|
334
|
+
+ </WaffleSettingsSection>
|
|
335
|
+
+
|
|
336
|
+
+ <Show when={status().state.lastError}>
|
|
337
|
+
+ <WaffleNotice tone="error">{status().state.lastError ?? ""}</WaffleNotice>
|
|
338
|
+
+ </Show>
|
|
339
|
+
+
|
|
340
|
+
+ <Show when={status().state.lastResponse}>
|
|
341
|
+
+ <WaffleSettingsSection title="Last response" description="Most recent heartbeat response captured from the dedicated session.">
|
|
342
|
+
+ <WaffleCard>
|
|
343
|
+
+ <div class="text-13-regular text-text-strong whitespace-pre-wrap break-words">
|
|
344
|
+
+ {status().state.lastResponse}
|
|
345
|
+
+ </div>
|
|
346
|
+
+ </WaffleCard>
|
|
347
|
+
+ </WaffleSettingsSection>
|
|
348
|
+
+ </Show>
|
|
349
|
+
+ </>
|
|
350
|
+
+ )}
|
|
351
|
+
+ </Show>
|
|
352
|
+
+
|
|
353
|
+
+ <WaffleSettingsSection title="Controls" description="Choose whether heartbeat runs, how often it runs, and which model it uses.">
|
|
354
|
+
+ <WaffleCard>
|
|
355
|
+
+ <div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border-weak-base bg-surface-base px-4 py-3">
|
|
356
|
+
+ <div class="flex flex-col gap-1">
|
|
357
|
+
+ <div class="text-13-medium text-text-strong">Heartbeat enabled</div>
|
|
358
|
+
+ <div class="text-12-regular text-text-weak">Disable this to stop the scheduler without removing the Heartbeat session.</div>
|
|
359
|
+
+ </div>
|
|
360
|
+
+ <Switch checked={state.form.enabled} onChange={(enabled) => setState("form", "enabled", enabled)} hideLabel>
|
|
361
|
+
+ Heartbeat enabled
|
|
362
|
+
+ </Switch>
|
|
363
|
+
+ </div>
|
|
364
|
+
+
|
|
365
|
+
+ <div class="grid gap-4 sm:grid-cols-2">
|
|
366
|
+
+ <div class="flex flex-col gap-2">
|
|
367
|
+
+ <span class="text-12-medium text-text-weak">Model</span>
|
|
368
|
+
+ <WaffleInput
|
|
369
|
+
+ value={state.form.model}
|
|
370
|
+
+ placeholder="openai/gpt-5.4"
|
|
371
|
+
+ onInput={(event) => setState("form", "model", event.currentTarget.value)}
|
|
372
|
+
+ />
|
|
373
|
+
+ </div>
|
|
374
|
+
+ <div class="flex flex-col gap-2">
|
|
375
|
+
+ <span class="text-12-medium text-text-weak">Interval</span>
|
|
376
|
+
+ <WaffleInput
|
|
377
|
+
+ value={state.form.interval}
|
|
378
|
+
+ placeholder="15m"
|
|
379
|
+
+ onInput={(event) => setState("form", "interval", event.currentTarget.value)}
|
|
380
|
+
+ />
|
|
381
|
+
+ </div>
|
|
382
|
+
+ <div class="flex flex-col gap-2">
|
|
383
|
+
+ <span class="text-12-medium text-text-weak">Agent id</span>
|
|
384
|
+
+ <WaffleInput
|
|
385
|
+
+ value={state.form.agentId}
|
|
386
|
+
+ placeholder="build"
|
|
387
|
+
+ onInput={(event) => setState("form", "agentId", event.currentTarget.value)}
|
|
388
|
+
+ />
|
|
389
|
+
+ </div>
|
|
390
|
+
+ <div class="flex flex-col gap-2">
|
|
391
|
+
+ <span class="text-12-medium text-text-weak">Ack max chars</span>
|
|
392
|
+
+ <WaffleInput
|
|
393
|
+
+ type="number"
|
|
394
|
+
+ min="1"
|
|
395
|
+
+ value={state.form.ackMaxChars}
|
|
396
|
+
+ onInput={(event) => setState("form", "ackMaxChars", event.currentTarget.value)}
|
|
397
|
+
+ />
|
|
398
|
+
+ </div>
|
|
399
|
+
+ </div>
|
|
400
|
+
+ </WaffleCard>
|
|
401
|
+
+ </WaffleSettingsSection>
|
|
402
|
+
+
|
|
403
|
+
+ <WaffleSettingsSection title="Active hours" description="Optionally restrict heartbeat runs to a daily time window.">
|
|
404
|
+
+ <WaffleCard>
|
|
405
|
+
+ <div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border-weak-base bg-surface-base px-4 py-3">
|
|
406
|
+
+ <div class="flex flex-col gap-1">
|
|
407
|
+
+ <div class="text-13-medium text-text-strong">Restrict to active hours</div>
|
|
408
|
+
+ <div class="text-12-regular text-text-weak">When enabled, heartbeat only runs inside the configured local time window.</div>
|
|
409
|
+
+ </div>
|
|
410
|
+
+ <Switch
|
|
411
|
+
+ checked={state.form.activeHoursEnabled}
|
|
412
|
+
+ onChange={(enabled) => setState("form", "activeHoursEnabled", enabled)}
|
|
413
|
+
+ hideLabel
|
|
414
|
+
+ >
|
|
415
|
+
+ Restrict to active hours
|
|
416
|
+
+ </Switch>
|
|
417
|
+
+ </div>
|
|
418
|
+
+
|
|
419
|
+
+ <Show when={state.form.activeHoursEnabled}>
|
|
420
|
+
+ <div class="grid gap-4 sm:grid-cols-3">
|
|
421
|
+
+ <div class="flex flex-col gap-2">
|
|
422
|
+
+ <span class="text-12-medium text-text-weak">Start</span>
|
|
423
|
+
+ <WaffleInput
|
|
424
|
+
+ value={state.form.activeStart}
|
|
425
|
+
+ placeholder="09:00"
|
|
426
|
+
+ onInput={(event) => setState("form", "activeStart", event.currentTarget.value)}
|
|
427
|
+
+ />
|
|
428
|
+
+ </div>
|
|
429
|
+
+ <div class="flex flex-col gap-2">
|
|
430
|
+
+ <span class="text-12-medium text-text-weak">End</span>
|
|
431
|
+
+ <WaffleInput
|
|
432
|
+
+ value={state.form.activeEnd}
|
|
433
|
+
+ placeholder="17:00"
|
|
434
|
+
+ onInput={(event) => setState("form", "activeEnd", event.currentTarget.value)}
|
|
435
|
+
+ />
|
|
436
|
+
+ </div>
|
|
437
|
+
+ <div class="flex flex-col gap-2">
|
|
438
|
+
+ <span class="text-12-medium text-text-weak">Timezone</span>
|
|
439
|
+
+ <WaffleInput
|
|
440
|
+
+ value={state.form.activeTimezone}
|
|
441
|
+
+ placeholder="America/Chicago"
|
|
442
|
+
+ onInput={(event) => setState("form", "activeTimezone", event.currentTarget.value)}
|
|
443
|
+
+ />
|
|
444
|
+
+ </div>
|
|
445
|
+
+ </div>
|
|
446
|
+
+ </Show>
|
|
447
|
+
+ </WaffleCard>
|
|
448
|
+
+ </WaffleSettingsSection>
|
|
449
|
+
+
|
|
450
|
+
+ <WaffleSettingsSection title="Prompt" description="This prompt runs in the visible Heartbeat session whenever the scheduler fires.">
|
|
451
|
+
+ <WaffleCard>
|
|
452
|
+
+ <WaffleTextArea
|
|
453
|
+
+ rows={12}
|
|
454
|
+
+ value={state.form.prompt}
|
|
455
|
+
+ onInput={(event) => setState("form", "prompt", event.currentTarget.value)}
|
|
456
|
+
+ />
|
|
457
|
+
+ </WaffleCard>
|
|
458
|
+
+ </WaffleSettingsSection>
|
|
459
|
+
+ </WaffleSettingsPage>
|
|
460
|
+
+ )
|
|
461
|
+
+}
|
|
462
|
+
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
|
|
463
|
+
index 9df2a3681..86cfb0b9b 100644
|
|
464
|
+
--- a/packages/app/src/pages/layout.tsx
|
|
465
|
+
+++ b/packages/app/src/pages/layout.tsx
|
|
466
|
+
@@ -2227,6 +2227,8 @@ export default function Layout(props: ParentProps) {
|
|
467
|
+
openProjectKeybind={() => command.keybind("project.open")}
|
|
468
|
+
onOpenProject={chooseProject}
|
|
469
|
+
renderProjectOverlay={projectOverlay}
|
|
470
|
+
+ usageLabel={() => "Usage"}
|
|
471
|
+
+ onOpenUsage={() => window.location.assign("/usage")}
|
|
472
|
+
settingsLabel={() => language.t("sidebar.settings")}
|
|
473
|
+
settingsKeybind={() => command.keybind("settings.open")}
|
|
474
|
+
onOpenSettings={openSettings}
|
|
475
|
+
diff --git a/packages/app/src/pages/layout/sidebar-shell.tsx b/packages/app/src/pages/layout/sidebar-shell.tsx
|
|
476
|
+
index 924828c2f..7ed209697 100644
|
|
477
|
+
--- a/packages/app/src/pages/layout/sidebar-shell.tsx
|
|
478
|
+
+++ b/packages/app/src/pages/layout/sidebar-shell.tsx
|
|
479
|
+
@@ -25,6 +25,8 @@ export const SidebarContent = (props: {
|
|
480
|
+
openProjectKeybind?: Accessor<string | undefined>
|
|
481
|
+
onOpenProject?: () => void
|
|
482
|
+
renderProjectOverlay: () => JSX.Element
|
|
483
|
+
+ usageLabel: Accessor<string>
|
|
484
|
+
+ onOpenUsage: () => void
|
|
485
|
+
settingsLabel: Accessor<string>
|
|
486
|
+
settingsKeybind: Accessor<string | undefined>
|
|
487
|
+
onOpenSettings: () => void
|
|
488
|
+
@@ -71,6 +73,15 @@ export const SidebarContent = (props: {
|
|
489
|
+
</DragDropProvider>
|
|
490
|
+
</div>
|
|
491
|
+
<div class="shrink-0 w-full pt-3 pb-6 flex flex-col items-center gap-2">
|
|
492
|
+
+ <Tooltip placement={placement()} value={props.usageLabel()}>
|
|
493
|
+
+ <IconButton
|
|
494
|
+
+ icon="status"
|
|
495
|
+
+ variant="ghost"
|
|
496
|
+
+ size="large"
|
|
497
|
+
+ onClick={props.onOpenUsage}
|
|
498
|
+
+ aria-label={props.usageLabel()}
|
|
499
|
+
+ />
|
|
500
|
+
+ </Tooltip>
|
|
501
|
+
<TooltipKeybind placement={placement()} title={props.settingsLabel()} keybind={props.settingsKeybind() ?? ""}>
|
|
502
|
+
<IconButton
|
|
503
|
+
icon="settings-gear"
|
|
504
|
+
--
|
|
505
|
+
2.53.0
|
|
506
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
From c96dd61ea8cfefe92ab911a881be4385713b3a31 Mon Sep 17 00:00:00 2001
|
|
2
|
+
From: Matt Campbell <matt@battleshopper.com>
|
|
3
|
+
Date: Wed, 18 Mar 2026 16:12:22 -0500
|
|
4
|
+
Subject: [PATCH 05/10] Use chart icon for usage nav
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
packages/app/src/pages/layout/sidebar-shell.tsx | 2 +-
|
|
8
|
+
packages/ui/src/components/icon.tsx | 1 +
|
|
9
|
+
2 files changed, 2 insertions(+), 1 deletion(-)
|
|
10
|
+
|
|
11
|
+
diff --git a/packages/app/src/pages/layout/sidebar-shell.tsx b/packages/app/src/pages/layout/sidebar-shell.tsx
|
|
12
|
+
index 7ed209697..47df7f298 100644
|
|
13
|
+
--- a/packages/app/src/pages/layout/sidebar-shell.tsx
|
|
14
|
+
+++ b/packages/app/src/pages/layout/sidebar-shell.tsx
|
|
15
|
+
@@ -75,7 +75,7 @@ export const SidebarContent = (props: {
|
|
16
|
+
<div class="shrink-0 w-full pt-3 pb-6 flex flex-col items-center gap-2">
|
|
17
|
+
<Tooltip placement={placement()} value={props.usageLabel()}>
|
|
18
|
+
<IconButton
|
|
19
|
+
- icon="status"
|
|
20
|
+
+ icon="chart-bars"
|
|
21
|
+
variant="ghost"
|
|
22
|
+
size="large"
|
|
23
|
+
onClick={props.onOpenUsage}
|
|
24
|
+
diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx
|
|
25
|
+
index e2eaf107a..f40783117 100644
|
|
26
|
+
--- a/packages/ui/src/components/icon.tsx
|
|
27
|
+
+++ b/packages/ui/src/components/icon.tsx
|
|
28
|
+
@@ -102,6 +102,7 @@ const icons = {
|
|
29
|
+
link: `<path d="M2.08334 12.0833L1.72979 11.7298L1.37624 12.0833L1.72979 12.4369L2.08334 12.0833ZM7.91668 17.9167L7.56312 18.2702L7.91668 18.6238L8.27023 18.2702L7.91668 17.9167ZM17.9167 7.91666L18.2702 8.27022L18.6238 7.91666L18.2702 7.56311L17.9167 7.91666ZM12.0833 2.08333L12.4369 1.72977L12.0833 1.37622L11.7298 1.72977L12.0833 2.08333ZM8.39646 5.06311L8.0429 5.41666L8.75001 6.12377L9.10356 5.77021L8.75001 5.41666L8.39646 5.06311ZM5.77023 9.10355L6.12378 8.74999L5.41668 8.04289L5.06312 8.39644L5.41668 8.74999L5.77023 9.10355ZM14.2298 10.8964L13.8762 11.25L14.5833 11.9571L14.9369 11.6035L14.5833 11.25L14.2298 10.8964ZM11.6036 14.9369L11.9571 14.5833L11.25 13.8762L10.8965 14.2298L11.25 14.5833L11.6036 14.9369ZM7.14646 12.1464L6.7929 12.5L7.50001 13.2071L7.85356 12.8535L7.50001 12.5L7.14646 12.1464ZM12.8536 7.85355L13.2071 7.49999L12.5 6.79289L12.1465 7.14644L12.5 7.49999L12.8536 7.85355ZM2.08334 12.0833L1.72979 12.4369L7.56312 18.2702L7.91668 17.9167L8.27023 17.5631L2.4369 11.7298L2.08334 12.0833ZM17.9167 7.91666L18.2702 7.56311L12.4369 1.72977L12.0833 2.08333L11.7298 2.43688L17.5631 8.27022L17.9167 7.91666ZM12.0833 2.08333L11.7298 1.72977L8.39646 5.06311L8.75001 5.41666L9.10356 5.77021L12.4369 2.43688L12.0833 2.08333ZM5.41668 8.74999L5.06312 8.39644L1.72979 11.7298L2.08334 12.0833L2.4369 12.4369L5.77023 9.10355L5.41668 8.74999ZM14.5833 11.25L14.9369 11.6035L18.2702 8.27022L17.9167 7.91666L17.5631 7.56311L14.2298 10.8964L14.5833 11.25ZM7.91668 17.9167L8.27023 18.2702L11.6036 14.9369L11.25 14.5833L10.8965 14.2298L7.56312 17.5631L7.91668 17.9167ZM7.50001 12.5L7.85356 12.8535L12.8536 7.85355L12.5 7.49999L12.1465 7.14644L7.14646 12.1464L7.50001 12.5Z" fill="currentColor"/>`,
|
|
30
|
+
providers: `<path d="M10.0001 4.37562V2.875M13 4.37793V2.87793M7.00014 4.37793V2.875M10 17.1279V15.6279M13 17.1279V15.6279M7 17.1279V15.6279M15.625 13.0029H17.125M15.625 7.00293H17.125M15.625 10.0029H17.125M2.875 10.0029H4.375M2.875 13.0029H4.375M2.875 7.00293H4.375M4.375 4.37793H15.625V15.6279H4.375V4.37793ZM12.6241 10.0022C12.6241 11.4519 11.4488 12.6272 9.99908 12.6272C8.54934 12.6272 7.37408 11.4519 7.37408 10.0022C7.37408 8.55245 8.54934 7.3772 9.99908 7.3772C11.4488 7.3772 12.6241 8.55245 12.6241 10.0022Z" stroke="currentColor" stroke-linecap="square"/>`,
|
|
31
|
+
models: `<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5 10C12.2917 10 10 12.2917 10 17.5C10 12.2917 7.70833 10 2.5 10C7.70833 10 10 7.70833 10 2.5C10 7.70833 12.2917 10 17.5 10Z" stroke="currentColor"/>`,
|
|
32
|
+
+ "chart-bars": `<path d="M3.75 16.25H16.25" stroke="currentColor" stroke-linecap="square"/><path d="M6.25 16.25V10.8333" stroke="currentColor" stroke-linecap="square"/><path d="M10 16.25V7.5" stroke="currentColor" stroke-linecap="square"/><path d="M13.75 16.25V4.16667" stroke="currentColor" stroke-linecap="square"/>`,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface IconProps extends ComponentProps<"svg"> {
|
|
36
|
+
--
|
|
37
|
+
2.53.0
|
|
38
|
+
|