@swarmclawai/swarmclaw 1.9.35 → 1.9.37
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/README.md +34 -0
- package/package.json +3 -3
- package/src/app/api/openclaw/history/route.ts +11 -6
- package/src/app/api/preview-server/route.ts +20 -12
- package/src/app/api/search/route.test.ts +63 -0
- package/src/app/api/search/route.ts +3 -2
- package/src/app/api/settings/route.ts +5 -1
- package/src/app/api/settings/settings-route.test.ts +38 -0
- package/src/app/api/usage/live/route.ts +2 -2
- package/src/app/globals.css +158 -0
- package/src/app/layout.tsx +12 -9
- package/src/app/protocols/builder/[templateId]/page.tsx +5 -5
- package/src/components/layout/dashboard-shell.tsx +9 -0
- package/src/components/protocols/builder/protocol-builder-canvas.tsx +106 -15
- package/src/components/providers/theme-provider.tsx +16 -0
- package/src/features/protocols/builder/hooks/use-template-sync.ts +5 -0
- package/src/features/protocols/builder/protocol-builder-store.ts +4 -4
- package/src/features/protocols/builder/utils/builder-template-access.test.ts +30 -0
- package/src/features/protocols/builder/utils/builder-template-access.ts +5 -0
- package/src/lib/server/messages/message-repository.test.ts +122 -0
- package/src/lib/server/messages/message-repository.ts +67 -11
- package/src/lib/server/runtime/devserver-launch.ts +7 -4
- package/src/lib/theme-mode.ts +5 -0
- package/src/types/app-settings.ts +2 -0
- package/src/views/settings/section-theme.tsx +41 -1
package/README.md
CHANGED
|
@@ -151,6 +151,23 @@ openclaw skills install swarmclaw
|
|
|
151
151
|
|
|
152
152
|
[Browse on ClawHub](https://clawhub.ai/waydelyle/swarmclaw)
|
|
153
153
|
|
|
154
|
+
## v1.9.37 Highlights
|
|
155
|
+
|
|
156
|
+
Theme and memory-pressure release for lighter UI preferences and leaner chat history storage.
|
|
157
|
+
|
|
158
|
+
- **Light, dark, and system theme modes.** Settings → Appearance now persists a Light/Dark/System selector while keeping the existing hue presets and custom color picker.
|
|
159
|
+
- **Lean session history storage.** Legacy transcript blobs migrate into the `session_messages` table and are compacted from session records after persistence is verified, reducing page-load memory pressure on lower-RAM devices.
|
|
160
|
+
- **Repo-backed message readers.** Global search, live usage summaries, and OpenClaw history merge now read table-backed messages after transcript compaction.
|
|
161
|
+
- **Regression coverage.** Added tests for theme-mode normalization, legacy transcript compaction, and repo-backed message search.
|
|
162
|
+
|
|
163
|
+
## v1.9.36 Highlights
|
|
164
|
+
|
|
165
|
+
Protocol builder visibility release for built-in Structured Sessions.
|
|
166
|
+
|
|
167
|
+
- **Built-in flow inspector.** Built-in protocol templates now open in a full-size visual builder canvas with a read-only template step panel.
|
|
168
|
+
- **Canvas viewport repair.** Builder routes now claim the full dashboard workspace and refit React Flow after async template loads.
|
|
169
|
+
- **Regression coverage.** Browser smoke now verifies that the built-in facilitated discussion graph renders with visible flow nodes.
|
|
170
|
+
|
|
154
171
|
## v1.9.35 Highlights
|
|
155
172
|
|
|
156
173
|
Installed package build fix for fresh npm-global installs and upgrades.
|
|
@@ -462,6 +479,23 @@ Operational docs: https://swarmclaw.ai/docs/observability
|
|
|
462
479
|
|
|
463
480
|
## Releases
|
|
464
481
|
|
|
482
|
+
### v1.9.37 Highlights
|
|
483
|
+
|
|
484
|
+
Theme and memory-pressure release for lighter UI preferences and leaner chat history storage.
|
|
485
|
+
|
|
486
|
+
- **Light, dark, and system theme modes.** Settings → Appearance now persists a Light/Dark/System selector while keeping the existing hue presets and custom color picker.
|
|
487
|
+
- **Lean session history storage.** Legacy transcript blobs migrate into the `session_messages` table and are compacted from session records after persistence is verified, reducing page-load memory pressure on lower-RAM devices.
|
|
488
|
+
- **Repo-backed message readers.** Global search, live usage summaries, and OpenClaw history merge now read table-backed messages after transcript compaction.
|
|
489
|
+
- **Regression coverage.** Added tests for theme-mode normalization, legacy transcript compaction, and repo-backed message search.
|
|
490
|
+
|
|
491
|
+
### v1.9.36 Highlights
|
|
492
|
+
|
|
493
|
+
Protocol builder visibility release for built-in Structured Sessions.
|
|
494
|
+
|
|
495
|
+
- **Built-in flow inspector.** Built-in protocol templates now open in a full-size visual builder canvas with a read-only template step panel.
|
|
496
|
+
- **Canvas viewport repair.** Builder routes now claim the full dashboard workspace and refit React Flow after async template loads.
|
|
497
|
+
- **Regression coverage.** Browser smoke now verifies that the built-in facilitated discussion graph renders with visible flow nodes.
|
|
498
|
+
|
|
465
499
|
### v1.9.35 Highlights
|
|
466
500
|
|
|
467
501
|
Installed package build fix for fresh npm-global installs and upgrades.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.37",
|
|
4
4
|
"description": "Build and run autonomous AI agents with OpenClaw, Hermes, multiple model providers, orchestration, delegation, memory, skills, schedules, and chat connectors.",
|
|
5
5
|
"main": "electron-dist/main.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
"test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/electron-after-pack.test.mjs scripts/electron-signing-config.test.mjs scripts/ensure-sandbox-browser-image.test.mjs scripts/postinstall.test.mjs scripts/run-next-build.test.mjs scripts/run-next-typegen.test.mjs",
|
|
89
89
|
"test:setup": "tsx --test src/app/api/setup/check-provider/route.test.ts src/lib/server/provider-model-discovery.test.ts src/components/auth/setup-wizard/utils.test.ts src/components/auth/setup-wizard/types.test.ts src/hooks/setup-done-detection.test.ts src/lib/setup-defaults.test.ts src/lib/server/storage-auth.test.ts src/lib/server/storage-auth-docker.test.ts",
|
|
90
90
|
"test:openclaw": "tsx --test src/lib/openclaw/openclaw-agent-id.test.ts src/lib/openclaw/openclaw-endpoint.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/connectors/swarmdock.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/gateways/gateway-topology.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw/agent-resolver.test.ts src/lib/server/openclaw/deploy.test.ts src/lib/server/openclaw/skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/session-tools/swarmdock.test.ts src/lib/server/tasks/task-quality-gate.test.ts src/lib/server/tasks/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/providers/openai.test.ts src/lib/providers/openclaw-exports.test.ts src/app/api/gateways/topology-route.test.ts src/app/api/openclaw/dashboard-url/route.test.ts",
|
|
91
|
-
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/agent-planning-mode.test.ts src/lib/agent-config-history.test.ts src/lib/autonomy/supervisor-settings.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/agents/delegation-advisory.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/autonomy/supervisor-reflection.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/provider-diagnostics.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/memory/dream-service.test.ts src/lib/server/memory/memory-consolidation.test.ts src/lib/server/extension-managed-resources.test.ts src/lib/server/eval/baseline.test.ts src/lib/server/eval/environment-plan.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/compaction-generation-preference.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chat-execution/prompt-sections.planning-mode.test.ts src/lib/server/chat-execution/reasoning-tag-scrubber.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/chats/session-context-pack.test.ts src/lib/server/connectors/email.test.ts src/lib/server/connectors/slack.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runtime/queue-retry-policy.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/runs/run-handoff.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/schedules/schedule-history.test.ts src/lib/server/schedules/schedule-timing.test.ts src/lib/server/schedules/schedule-preview.test.ts src/lib/quality/release-readiness.test.ts src/lib/quality/architecture-health.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-execution-policy.test.ts src/lib/server/tasks/task-handoff.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.test.ts src/lib/server/session-tools/web-crawl.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/tasks/task-workspace-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-pack-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/config-versions/config-versions-route.test.ts src/app/api/runs/run-handoff-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/extensions/managed-resources/route.test.ts src/app/api/gateways/control-route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/route.test.ts src/app/api/portability/import/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/schedules/preview/route.test.ts src/app/api/schedules/schedule-history-route.test.ts src/app/api/tts/route.test.ts",
|
|
92
|
-
"test:builder": "tsx --test src/features/protocols/builder/utils/nodes-to-template.test.ts src/features/protocols/builder/utils/template-to-nodes.test.ts src/features/protocols/builder/validators/dag-validator.test.ts",
|
|
91
|
+
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/agent-planning-mode.test.ts src/lib/agent-config-history.test.ts src/lib/autonomy/supervisor-settings.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/agents/delegation-advisory.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/autonomy/supervisor-reflection.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/provider-diagnostics.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/memory/dream-service.test.ts src/lib/server/memory/memory-consolidation.test.ts src/lib/server/messages/message-repository.test.ts src/lib/server/extension-managed-resources.test.ts src/lib/server/eval/baseline.test.ts src/lib/server/eval/environment-plan.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/compaction-generation-preference.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chat-execution/prompt-sections.planning-mode.test.ts src/lib/server/chat-execution/reasoning-tag-scrubber.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/chats/session-context-pack.test.ts src/lib/server/connectors/email.test.ts src/lib/server/connectors/slack.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runtime/queue-retry-policy.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/runs/run-handoff.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/schedules/schedule-history.test.ts src/lib/server/schedules/schedule-timing.test.ts src/lib/server/schedules/schedule-preview.test.ts src/lib/quality/release-readiness.test.ts src/lib/quality/architecture-health.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-execution-policy.test.ts src/lib/server/tasks/task-handoff.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.test.ts src/lib/server/session-tools/web-crawl.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/tasks/task-workspace-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-pack-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/config-versions/config-versions-route.test.ts src/app/api/runs/run-handoff-route.test.ts src/app/api/search/route.test.ts src/app/api/settings/settings-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/extensions/managed-resources/route.test.ts src/app/api/gateways/control-route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/route.test.ts src/app/api/portability/import/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/schedules/preview/route.test.ts src/app/api/schedules/schedule-history-route.test.ts src/app/api/tts/route.test.ts",
|
|
92
|
+
"test:builder": "tsx --test src/features/protocols/builder/utils/builder-template-access.test.ts src/features/protocols/builder/utils/nodes-to-template.test.ts src/features/protocols/builder/utils/template-to-nodes.test.ts src/features/protocols/builder/validators/dag-validator.test.ts",
|
|
93
93
|
"test:e2e": "node --import tsx scripts/browser-e2e-smoke.ts",
|
|
94
94
|
"test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
|
|
95
95
|
"electron:compile": "tsc -p electron/tsconfig.json",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { ensureGatewayConnected } from '@/lib/server/openclaw/gateway'
|
|
3
3
|
import { mergeHistoryMessages, isValidSessionKey } from '@/lib/server/openclaw/history-merge'
|
|
4
|
-
import { getSession,
|
|
4
|
+
import { getSession, patchSession } from '@/lib/server/sessions/session-repository'
|
|
5
|
+
import { getMessages, replaceAllMessages } from '@/lib/server/messages/message-repository'
|
|
5
6
|
import { notify } from '@/lib/server/ws-hub'
|
|
6
7
|
import type { GatewaySessionPreview } from '@/types'
|
|
7
8
|
import { errorMessage } from '@/lib/shared-utils'
|
|
@@ -95,11 +96,15 @@ export async function POST(req: Request) {
|
|
|
95
96
|
return NextResponse.json({ error: 'Local session not found' }, { status: 404 })
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
const currentMessages = getMessages(localSessionId)
|
|
100
|
+
const merged = mergeHistoryMessages(currentMessages, preview)
|
|
101
|
+
const newCount = merged.length - currentMessages.length
|
|
102
|
+
replaceAllMessages(localSessionId, merged)
|
|
103
|
+
patchSession(localSessionId, (current) => {
|
|
104
|
+
if (!current) return null
|
|
105
|
+
current.lastActiveAt = Date.now()
|
|
106
|
+
return current
|
|
107
|
+
})
|
|
103
108
|
notify('sessions')
|
|
104
109
|
|
|
105
110
|
return NextResponse.json({ ok: true, merged: newCount })
|
|
@@ -49,11 +49,13 @@ const servers: Map<string, PreviewServer> =
|
|
|
49
49
|
// ---------------------------------------------------------------------------
|
|
50
50
|
|
|
51
51
|
function resolveServeDir(filePath: string): string {
|
|
52
|
-
const resolved = path.resolve(filePath)
|
|
52
|
+
const resolved = path.resolve(/*turbopackIgnore: true*/ filePath)
|
|
53
53
|
try {
|
|
54
|
-
return fs.statSync(resolved).isDirectory()
|
|
54
|
+
return fs.statSync(/*turbopackIgnore: true*/ resolved).isDirectory()
|
|
55
|
+
? resolved
|
|
56
|
+
: path.dirname(/*turbopackIgnore: true*/ resolved)
|
|
55
57
|
} catch {
|
|
56
|
-
return path.dirname(resolved)
|
|
58
|
+
return path.dirname(/*turbopackIgnore: true*/ resolved)
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
|
|
@@ -91,13 +93,13 @@ function buildFrameworkArgs(framework: string | undefined, port: number): string
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
function detectProject(dir: string): ProjectInfo {
|
|
94
|
-
const pkgPath = path.join(dir, 'package.json')
|
|
95
|
-
if (!fs.existsSync(pkgPath)) {
|
|
96
|
+
const pkgPath = path.join(/*turbopackIgnore: true*/ dir, 'package.json')
|
|
97
|
+
if (!fs.existsSync(/*turbopackIgnore: true*/ pkgPath)) {
|
|
96
98
|
return { type: 'static' }
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
try {
|
|
100
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
|
|
102
|
+
const pkg = JSON.parse(fs.readFileSync(/*turbopackIgnore: true*/ pkgPath, 'utf-8'))
|
|
101
103
|
const scripts = pkg.scripts || {}
|
|
102
104
|
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }
|
|
103
105
|
|
|
@@ -156,16 +158,22 @@ function createStaticServer(dir: string): http.Server {
|
|
|
156
158
|
]
|
|
157
159
|
|
|
158
160
|
for (const candidate of candidates) {
|
|
159
|
-
if (
|
|
161
|
+
if (
|
|
162
|
+
fs.existsSync(/*turbopackIgnore: true*/ candidate)
|
|
163
|
+
&& fs.statSync(/*turbopackIgnore: true*/ candidate).isFile()
|
|
164
|
+
) {
|
|
160
165
|
const ext = path.extname(candidate).toLowerCase()
|
|
161
166
|
res.writeHead(200, { 'Content-Type': MIME_MAP[ext] || 'application/octet-stream' })
|
|
162
|
-
fs.createReadStream(candidate).pipe(res)
|
|
167
|
+
fs.createReadStream(/*turbopackIgnore: true*/ candidate).pipe(res)
|
|
163
168
|
return
|
|
164
169
|
}
|
|
165
170
|
}
|
|
166
171
|
|
|
167
|
-
if (
|
|
168
|
-
|
|
172
|
+
if (
|
|
173
|
+
fs.existsSync(/*turbopackIgnore: true*/ normalizedFile)
|
|
174
|
+
&& fs.statSync(/*turbopackIgnore: true*/ normalizedFile).isDirectory()
|
|
175
|
+
) {
|
|
176
|
+
const files = fs.readdirSync(/*turbopackIgnore: true*/ normalizedFile)
|
|
169
177
|
const links = files.map((f) => `<li><a href="${reqPath.replace(/\/$/, '')}/${f}">${f}</a></li>`).join('\n')
|
|
170
178
|
res.writeHead(200, { 'Content-Type': 'text/html' })
|
|
171
179
|
res.end(`<!DOCTYPE html><html><head><title>Index of ${reqPath}</title><style>body{font-family:monospace;padding:20px;background:#1a1a2e;color:#e0e0e0}a{color:#60a5fa}</style></head><body><h2>Index of ${reqPath}</h2><ul>${links}</ul></body></html>`)
|
|
@@ -183,7 +191,7 @@ function createStaticServer(dir: string): http.Server {
|
|
|
183
191
|
|
|
184
192
|
async function startNpmServer(dir: string, command: string[], port: number, framework?: string): Promise<PreviewServer> {
|
|
185
193
|
// Install deps if node_modules missing
|
|
186
|
-
if (!fs.existsSync(path.join(dir, 'node_modules'))) {
|
|
194
|
+
if (!fs.existsSync(/*turbopackIgnore: true*/ path.join(/*turbopackIgnore: true*/ dir, 'node_modules'))) {
|
|
187
195
|
log.info(TAG, `Installing dependencies in ${dir}`)
|
|
188
196
|
await new Promise<void>((resolve, reject) => {
|
|
189
197
|
const install = spawn('npm', ['install'], { cwd: dir, stdio: 'pipe' })
|
|
@@ -290,7 +298,7 @@ export async function POST(req: Request) {
|
|
|
290
298
|
return NextResponse.json(buildResponse(servers.get(key)!))
|
|
291
299
|
}
|
|
292
300
|
|
|
293
|
-
if (!fs.existsSync(dir)) {
|
|
301
|
+
if (!fs.existsSync(/*turbopackIgnore: true*/ dir)) {
|
|
294
302
|
return NextResponse.json({ error: 'Directory not found' }, { status: 404 })
|
|
295
303
|
}
|
|
296
304
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
|
|
5
|
+
|
|
6
|
+
test('global search finds repo-backed session messages after blob compaction', () => {
|
|
7
|
+
const output = runWithTempDataDir<{
|
|
8
|
+
messageTitles: string[]
|
|
9
|
+
messageIndexes: number[]
|
|
10
|
+
}>(`
|
|
11
|
+
const storageMod = await import('@/lib/server/storage')
|
|
12
|
+
const repoMod = await import('@/lib/server/messages/message-repository')
|
|
13
|
+
const routeMod = await import('./src/app/api/search/route')
|
|
14
|
+
const storage = storageMod.default || storageMod
|
|
15
|
+
const repo = repoMod.default || repoMod
|
|
16
|
+
const route = routeMod.default || routeMod
|
|
17
|
+
|
|
18
|
+
storage.saveAgents({
|
|
19
|
+
'agent-search': {
|
|
20
|
+
id: 'agent-search',
|
|
21
|
+
name: 'Search Agent',
|
|
22
|
+
description: 'Search fixture',
|
|
23
|
+
provider: 'openai',
|
|
24
|
+
model: 'gpt-5',
|
|
25
|
+
createdAt: Date.now(),
|
|
26
|
+
updatedAt: Date.now(),
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
storage.saveSessions({
|
|
30
|
+
'sess-search': {
|
|
31
|
+
id: 'sess-search',
|
|
32
|
+
name: 'Search Session',
|
|
33
|
+
cwd: process.env.WORKSPACE_DIR,
|
|
34
|
+
user: 'tester',
|
|
35
|
+
provider: 'openai',
|
|
36
|
+
model: 'gpt-5',
|
|
37
|
+
agentId: 'agent-search',
|
|
38
|
+
claudeSessionId: null,
|
|
39
|
+
codexThreadId: null,
|
|
40
|
+
opencodeSessionId: null,
|
|
41
|
+
delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
|
|
42
|
+
messages: [],
|
|
43
|
+
createdAt: Date.now(),
|
|
44
|
+
lastActiveAt: Date.now(),
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
repo.appendMessage('sess-search', { role: 'user', text: 'ordinary setup note', time: 10 })
|
|
49
|
+
repo.appendMessage('sess-search', { role: 'assistant', text: 'needle-backed answer from the message table', time: 20 })
|
|
50
|
+
|
|
51
|
+
const response = await route.GET(new Request('http://local/api/search?q=needle-backed'))
|
|
52
|
+
const payload = await response.json()
|
|
53
|
+
const messageResults = payload.results.filter((result) => result.type === 'message')
|
|
54
|
+
|
|
55
|
+
console.log(JSON.stringify({
|
|
56
|
+
messageTitles: messageResults.map((result) => result.title),
|
|
57
|
+
messageIndexes: messageResults.map((result) => result.messageIndex),
|
|
58
|
+
}))
|
|
59
|
+
`, { prefix: 'swarmclaw-search-repo-messages-' })
|
|
60
|
+
|
|
61
|
+
assert.deepEqual(output.messageTitles, ['needle-backed answer from the message table'])
|
|
62
|
+
assert.deepEqual(output.messageIndexes, [1])
|
|
63
|
+
})
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
loadWebhooks,
|
|
8
8
|
loadSkills,
|
|
9
9
|
} from '@/lib/server/storage'
|
|
10
|
+
import { getMessages } from '@/lib/server/messages/message-repository'
|
|
10
11
|
|
|
11
12
|
interface SearchResult {
|
|
12
13
|
type: 'task' | 'agent' | 'session' | 'schedule' | 'webhook' | 'skill' | 'message'
|
|
@@ -59,8 +60,8 @@ function searchMessages(
|
|
|
59
60
|
const MAX_MSG_RESULTS = 10
|
|
60
61
|
for (const [sessionId, session] of Object.entries(sessions)) {
|
|
61
62
|
if (results.length >= MAX_MSG_RESULTS) break
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
const messages = getMessages(sessionId)
|
|
64
|
+
if (!messages.length) continue
|
|
64
65
|
const agentId = session.agentId as string | undefined
|
|
65
66
|
const agentName = agentId && agents[agentId] ? (agents[agentId].name as string) : undefined
|
|
66
67
|
const sessionName = (session.name as string) || 'Untitled'
|
|
@@ -7,6 +7,7 @@ import { normalizeRuntimeSettingFields } from '@/lib/runtime/runtime-loop'
|
|
|
7
7
|
import { normalizeSupervisorSettings } from '@/lib/autonomy/supervisor-settings'
|
|
8
8
|
import { ensureDaemonProcessRunning } from '@/lib/server/daemon/controller'
|
|
9
9
|
import { logActivity } from '@/lib/server/activity/activity-log'
|
|
10
|
+
import { normalizeThemeMode } from '@/lib/theme-mode'
|
|
10
11
|
export const dynamic = 'force-dynamic'
|
|
11
12
|
|
|
12
13
|
|
|
@@ -55,7 +56,9 @@ function parseGuardMode(value: unknown): 'off' | 'warn' | 'block' {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export async function GET() {
|
|
58
|
-
|
|
59
|
+
const settings = loadPublicSettings()
|
|
60
|
+
settings.themeMode = normalizeThemeMode(settings.themeMode)
|
|
61
|
+
return NextResponse.json(settings)
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
export async function PUT(req: Request) {
|
|
@@ -146,6 +149,7 @@ export async function PUT(req: Request) {
|
|
|
146
149
|
settings.daemonAutostartEnabled = parseBoolSetting(settings.daemonAutostartEnabled, true)
|
|
147
150
|
settings.autonomyResumeApprovalsEnabled = parseBoolSetting(settings.autonomyResumeApprovalsEnabled, false)
|
|
148
151
|
settings.untrustedContentGuardMode = parseGuardMode(settings.untrustedContentGuardMode)
|
|
152
|
+
settings.themeMode = normalizeThemeMode(settings.themeMode)
|
|
149
153
|
settings.sessionResetMode = settings.sessionResetMode === 'daily' ? 'daily' : settings.sessionResetMode === 'idle' ? 'idle' : null
|
|
150
154
|
settings.whatsappApprovedContacts = normalizeWhatsAppApprovedContacts(settings.whatsappApprovedContacts)
|
|
151
155
|
settings.sessionIdleTimeoutSec = parseIntSetting(
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
|
|
5
|
+
|
|
6
|
+
test('settings route persists valid theme mode and normalizes invalid values to dark', () => {
|
|
7
|
+
const output = runWithTempDataDir<{
|
|
8
|
+
lightMode: string | null
|
|
9
|
+
invalidMode: string | null
|
|
10
|
+
}>(`
|
|
11
|
+
const routeMod = await import('./src/app/api/settings/route')
|
|
12
|
+
const route = routeMod.default || routeMod
|
|
13
|
+
|
|
14
|
+
await route.PUT(new Request('http://local/api/settings', {
|
|
15
|
+
method: 'PUT',
|
|
16
|
+
headers: { 'content-type': 'application/json' },
|
|
17
|
+
body: JSON.stringify({ themeMode: 'light' }),
|
|
18
|
+
}))
|
|
19
|
+
const lightResponse = await route.GET()
|
|
20
|
+
const lightSettings = await lightResponse.json()
|
|
21
|
+
|
|
22
|
+
await route.PUT(new Request('http://local/api/settings', {
|
|
23
|
+
method: 'PUT',
|
|
24
|
+
headers: { 'content-type': 'application/json' },
|
|
25
|
+
body: JSON.stringify({ themeMode: 'sepia' }),
|
|
26
|
+
}))
|
|
27
|
+
const invalidResponse = await route.GET()
|
|
28
|
+
const invalidSettings = await invalidResponse.json()
|
|
29
|
+
|
|
30
|
+
console.log(JSON.stringify({
|
|
31
|
+
lightMode: lightSettings.themeMode || null,
|
|
32
|
+
invalidMode: invalidSettings.themeMode || null,
|
|
33
|
+
}))
|
|
34
|
+
`, { prefix: 'swarmclaw-settings-theme-mode-' })
|
|
35
|
+
|
|
36
|
+
assert.equal(output.lightMode, 'light')
|
|
37
|
+
assert.equal(output.invalidMode, 'dark')
|
|
38
|
+
})
|
|
@@ -9,7 +9,7 @@ type SessionSnapshot = {
|
|
|
9
9
|
agentId?: string
|
|
10
10
|
createdAt?: number
|
|
11
11
|
lastActiveAt?: number
|
|
12
|
-
|
|
12
|
+
messageCount?: number
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
interface LiveUsage {
|
|
@@ -45,7 +45,7 @@ function summarize(sessionId: string, records: UsageRecord[], session: SessionSn
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
const turns =
|
|
48
|
+
const turns = typeof session?.messageCount === 'number' ? session.messageCount : records.length
|
|
49
49
|
const wallStart = session?.createdAt ?? firstAt ?? 0
|
|
50
50
|
const wallEnd = session?.lastActiveAt ?? lastAt ?? Date.now()
|
|
51
51
|
const wallclockMs = wallStart > 0 ? Math.max(0, wallEnd - wallStart) : 0
|
package/src/app/globals.css
CHANGED
|
@@ -144,6 +144,164 @@
|
|
|
144
144
|
--command-header: rgba(0,0,0,0.3);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
.light {
|
|
148
|
+
--background: color-mix(in srgb, var(--neutral-tint) 10%, #fff);
|
|
149
|
+
--foreground: #1f2937;
|
|
150
|
+
--card: color-mix(in srgb, var(--neutral-tint) 8%, #fff);
|
|
151
|
+
--card-foreground: #1f2937;
|
|
152
|
+
--popover: color-mix(in srgb, var(--neutral-tint) 8%, #fff);
|
|
153
|
+
--popover-foreground: #1f2937;
|
|
154
|
+
--primary: #4f46e5;
|
|
155
|
+
--primary-foreground: #ffffff;
|
|
156
|
+
--secondary: color-mix(in srgb, var(--neutral-tint) 12%, #fff);
|
|
157
|
+
--secondary-foreground: #253047;
|
|
158
|
+
--muted: color-mix(in srgb, var(--neutral-tint) 10%, #fff);
|
|
159
|
+
--muted-foreground: #5f6b85;
|
|
160
|
+
--accent: #4f46e5;
|
|
161
|
+
--accent-foreground: #ffffff;
|
|
162
|
+
--destructive: #dc2626;
|
|
163
|
+
--border: rgba(31,41,55,0.12);
|
|
164
|
+
--input: rgba(31,41,55,0.10);
|
|
165
|
+
--ring: rgba(79,70,229,0.38);
|
|
166
|
+
--sidebar: color-mix(in srgb, var(--neutral-tint) 9%, #fff);
|
|
167
|
+
--sidebar-foreground: #1f2937;
|
|
168
|
+
--sidebar-primary: #4f46e5;
|
|
169
|
+
--sidebar-primary-foreground: #ffffff;
|
|
170
|
+
--sidebar-accent: rgba(79,70,229,0.09);
|
|
171
|
+
--sidebar-accent-foreground: #253047;
|
|
172
|
+
--sidebar-border: rgba(31,41,55,0.11);
|
|
173
|
+
--sidebar-ring: rgba(79,70,229,0.35);
|
|
174
|
+
|
|
175
|
+
--color-bg: color-mix(in srgb, var(--neutral-tint) 10%, #fff);
|
|
176
|
+
--color-raised: color-mix(in srgb, var(--neutral-tint) 8%, #fff);
|
|
177
|
+
--color-surface: color-mix(in srgb, var(--neutral-tint) 14%, #fff);
|
|
178
|
+
--color-surface-2: color-mix(in srgb, var(--neutral-tint) 20%, #fff);
|
|
179
|
+
--color-surface-3: color-mix(in srgb, var(--neutral-tint) 24%, #fff);
|
|
180
|
+
--color-border-hi: rgba(31,41,55,0.12);
|
|
181
|
+
--color-border-focus: rgba(79,70,229,0.45);
|
|
182
|
+
--color-text: #1f2937;
|
|
183
|
+
--color-text-2: #4b5568;
|
|
184
|
+
--color-text-3: #6b7280;
|
|
185
|
+
--color-accent-soft: rgba(79,70,229,0.09);
|
|
186
|
+
--color-accent-glow: rgba(79,70,229,0.16);
|
|
187
|
+
--color-accent-bright: #4f46e5;
|
|
188
|
+
--color-user-text: #fff;
|
|
189
|
+
--color-success: #059669;
|
|
190
|
+
--color-success-soft: rgba(5,150,105,0.10);
|
|
191
|
+
--color-danger: #dc2626;
|
|
192
|
+
--color-danger-soft: rgba(220,38,38,0.10);
|
|
193
|
+
--color-shereen: #db2777;
|
|
194
|
+
--color-user-bubble: #4f46e5;
|
|
195
|
+
--color-user-bubble-2: #6366f1;
|
|
196
|
+
--color-ai-bubble: #f4f6fb;
|
|
197
|
+
--color-glass: rgba(255,255,255,0.82);
|
|
198
|
+
--color-glass-border: rgba(31,41,55,0.10);
|
|
199
|
+
|
|
200
|
+
--status-idle-bg: rgba(31,41,55,0.05);
|
|
201
|
+
--status-idle-border: rgba(31,41,55,0.10);
|
|
202
|
+
--status-idle-fg: #5f6b85;
|
|
203
|
+
--status-running-bg: rgba(5,150,105,0.10);
|
|
204
|
+
--status-running-border: rgba(5,150,105,0.18);
|
|
205
|
+
--status-running-fg: #047857;
|
|
206
|
+
--status-error-bg: rgba(220,38,38,0.10);
|
|
207
|
+
--status-error-border: rgba(220,38,38,0.18);
|
|
208
|
+
--status-error-fg: #dc2626;
|
|
209
|
+
--status-connecting-bg: rgba(217,119,6,0.10);
|
|
210
|
+
--status-connecting-border: rgba(217,119,6,0.18);
|
|
211
|
+
--status-connecting-fg: #b45309;
|
|
212
|
+
--status-connected-bg: rgba(2,132,199,0.10);
|
|
213
|
+
--status-connected-border: rgba(2,132,199,0.18);
|
|
214
|
+
--status-connected-fg: #0369a1;
|
|
215
|
+
--status-approval-bg: rgba(234,88,12,0.10);
|
|
216
|
+
--status-approval-border: rgba(234,88,12,0.18);
|
|
217
|
+
--status-approval-fg: #c2410c;
|
|
218
|
+
|
|
219
|
+
--command-bg: #ffffff;
|
|
220
|
+
--command-border: rgba(31,41,55,0.12);
|
|
221
|
+
--command-header: rgba(31,41,55,0.04);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.light .bg-white\/\[0\.01\] { background-color: rgba(31,41,55,0.01) !important; }
|
|
225
|
+
.light .bg-white\/\[0\.02\] { background-color: rgba(31,41,55,0.025) !important; }
|
|
226
|
+
.light .bg-white\/\[0\.025\] { background-color: rgba(31,41,55,0.03) !important; }
|
|
227
|
+
.light .bg-white\/\[0\.03\] { background-color: rgba(31,41,55,0.035) !important; }
|
|
228
|
+
.light .bg-white\/\[0\.035\] { background-color: rgba(31,41,55,0.04) !important; }
|
|
229
|
+
.light .bg-white\/\[0\.04\] { background-color: rgba(31,41,55,0.045) !important; }
|
|
230
|
+
.light .bg-white\/\[0\.05\] { background-color: rgba(31,41,55,0.055) !important; }
|
|
231
|
+
.light .bg-white\/\[0\.06\] { background-color: rgba(31,41,55,0.065) !important; }
|
|
232
|
+
.light .bg-white\/\[0\.07\] { background-color: rgba(31,41,55,0.075) !important; }
|
|
233
|
+
.light .bg-white\/\[0\.08\] { background-color: rgba(31,41,55,0.085) !important; }
|
|
234
|
+
.light .bg-white\/\[0\.10\],
|
|
235
|
+
.light .bg-white\/\[0\.1\] { background-color: rgba(31,41,55,0.10) !important; }
|
|
236
|
+
.light .bg-white\/\[0\.12\] { background-color: rgba(31,41,55,0.12) !important; }
|
|
237
|
+
.light .bg-white\/\[0\.15\] { background-color: rgba(31,41,55,0.15) !important; }
|
|
238
|
+
.light .bg-white\/\[0\.16\] { background-color: rgba(31,41,55,0.16) !important; }
|
|
239
|
+
.light .bg-white\/\[0\.18\] { background-color: rgba(31,41,55,0.18) !important; }
|
|
240
|
+
.light .bg-white\/\[0\.2\],
|
|
241
|
+
.light .bg-white\/\[0\.20\] { background-color: rgba(31,41,55,0.20) !important; }
|
|
242
|
+
.light .bg-white\/\[0\.22\] { background-color: rgba(31,41,55,0.22) !important; }
|
|
243
|
+
.light .bg-white\/\[0\.45\] { background-color: rgba(31,41,55,0.45) !important; }
|
|
244
|
+
|
|
245
|
+
.light .border-white\/\[0\.03\] { border-color: rgba(31,41,55,0.07) !important; }
|
|
246
|
+
.light .border-white\/\[0\.04\] { border-color: rgba(31,41,55,0.08) !important; }
|
|
247
|
+
.light .border-white\/\[0\.05\] { border-color: rgba(31,41,55,0.09) !important; }
|
|
248
|
+
.light .border-white\/\[0\.06\] { border-color: rgba(31,41,55,0.10) !important; }
|
|
249
|
+
.light .border-white\/\[0\.07\] { border-color: rgba(31,41,55,0.11) !important; }
|
|
250
|
+
.light .border-white\/\[0\.08\] { border-color: rgba(31,41,55,0.12) !important; }
|
|
251
|
+
.light .border-white\/\[0\.10\],
|
|
252
|
+
.light .border-white\/\[0\.1\] { border-color: rgba(31,41,55,0.14) !important; }
|
|
253
|
+
.light .border-white\/\[0\.12\] { border-color: rgba(31,41,55,0.16) !important; }
|
|
254
|
+
.light .border-white\/\[0\.14\] { border-color: rgba(31,41,55,0.18) !important; }
|
|
255
|
+
.light .border-white\/\[0\.15\] { border-color: rgba(31,41,55,0.19) !important; }
|
|
256
|
+
.light .border-white\/\[0\.16\] { border-color: rgba(31,41,55,0.20) !important; }
|
|
257
|
+
.light .border-white\/\[0\.2\],
|
|
258
|
+
.light .border-white\/\[0\.20\] { border-color: rgba(31,41,55,0.24) !important; }
|
|
259
|
+
.light .border-white\/\[0\.25\] { border-color: rgba(31,41,55,0.28) !important; }
|
|
260
|
+
.light .border-white\/\[0\.4\],
|
|
261
|
+
.light .border-white\/\[0\.40\] { border-color: rgba(31,41,55,0.42) !important; }
|
|
262
|
+
.light .divide-white\/\[0\.04\] > :not(:last-child) { border-color: rgba(31,41,55,0.08) !important; }
|
|
263
|
+
.light .divide-white\/\[0\.05\] > :not(:last-child) { border-color: rgba(31,41,55,0.09) !important; }
|
|
264
|
+
.light .ring-white\/\[0\.08\] { --tw-ring-color: rgba(31,41,55,0.14) !important; }
|
|
265
|
+
|
|
266
|
+
html.light,
|
|
267
|
+
html.light body {
|
|
268
|
+
color-scheme: light;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
html.light body {
|
|
272
|
+
background: color-mix(in srgb, var(--neutral-tint) 8%, #fff);
|
|
273
|
+
color: #1f2937;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
html.light .bg-bg { background-color: color-mix(in srgb, var(--neutral-tint) 8%, #fff) !important; }
|
|
277
|
+
html.light .bg-raised { background-color: color-mix(in srgb, var(--neutral-tint) 10%, #fff) !important; }
|
|
278
|
+
html.light .bg-surface { background-color: color-mix(in srgb, var(--neutral-tint) 14%, #fff) !important; }
|
|
279
|
+
html.light .bg-surface-2 { background-color: color-mix(in srgb, var(--neutral-tint) 18%, #fff) !important; }
|
|
280
|
+
html.light .bg-glass { background-color: rgba(255,255,255,0.88) !important; }
|
|
281
|
+
html.light .text-text { color: #1f2937 !important; }
|
|
282
|
+
html.light .text-text-2 { color: #4b5568 !important; }
|
|
283
|
+
html.light .text-text-3 { color: #6b7280 !important; }
|
|
284
|
+
html.light .text-text-3\/40 { color: rgba(107,114,128,0.40) !important; }
|
|
285
|
+
html.light .text-text-3\/45 { color: rgba(107,114,128,0.45) !important; }
|
|
286
|
+
html.light .text-text-3\/50 { color: rgba(107,114,128,0.50) !important; }
|
|
287
|
+
html.light .text-text-3\/60 { color: rgba(107,114,128,0.60) !important; }
|
|
288
|
+
html.light .text-text-3\/70 { color: rgba(107,114,128,0.70) !important; }
|
|
289
|
+
html.light .text-text-3\/75 { color: rgba(107,114,128,0.75) !important; }
|
|
290
|
+
html.light .text-accent-bright { color: #4f46e5 !important; }
|
|
291
|
+
html.light .bg-accent-soft { background-color: rgba(79,70,229,0.09) !important; }
|
|
292
|
+
html.light .placeholder\:text-text-3\/40::placeholder { color: rgba(107,114,128,0.40) !important; }
|
|
293
|
+
|
|
294
|
+
html.light .bg-\[\#0a0a14\],
|
|
295
|
+
html.light .bg-\[\#0c0c13\],
|
|
296
|
+
html.light .bg-\[\#12121e\],
|
|
297
|
+
html.light .bg-\[\#13131e\],
|
|
298
|
+
html.light .bg-\[\#16162a\],
|
|
299
|
+
html.light .bg-\[\#171a2b\],
|
|
300
|
+
html.light .bg-\[\#1a1a2e\],
|
|
301
|
+
html.light .bg-\[\#1e1e38\] {
|
|
302
|
+
background-color: color-mix(in srgb, var(--neutral-tint) 14%, #fff) !important;
|
|
303
|
+
}
|
|
304
|
+
|
|
147
305
|
@layer base {
|
|
148
306
|
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
149
307
|
}
|
package/src/app/layout.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { TooltipProvider } from "@/components/ui/tooltip"
|
|
|
3
3
|
import { Toaster } from "@/components/ui/sonner"
|
|
4
4
|
import { DashboardShell } from "@/components/layout/dashboard-shell"
|
|
5
5
|
import { AppQueryProvider } from "@/components/providers/app-query-provider"
|
|
6
|
+
import { ThemeProvider } from "@/components/providers/theme-provider"
|
|
6
7
|
import "./globals.css"
|
|
7
8
|
|
|
8
9
|
export const metadata: Metadata = {
|
|
@@ -27,16 +28,18 @@ export default function RootLayout({
|
|
|
27
28
|
children: React.ReactNode
|
|
28
29
|
}>) {
|
|
29
30
|
return (
|
|
30
|
-
<html lang="en"
|
|
31
|
+
<html lang="en" suppressHydrationWarning>
|
|
31
32
|
<body className="antialiased" cz-shortcut-listen="true">
|
|
32
|
-
<
|
|
33
|
-
<
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
<ThemeProvider>
|
|
34
|
+
<AppQueryProvider>
|
|
35
|
+
<TooltipProvider>
|
|
36
|
+
<DashboardShell>
|
|
37
|
+
{children}
|
|
38
|
+
</DashboardShell>
|
|
39
|
+
<Toaster />
|
|
40
|
+
</TooltipProvider>
|
|
41
|
+
</AppQueryProvider>
|
|
42
|
+
</ThemeProvider>
|
|
40
43
|
</body>
|
|
41
44
|
</html>
|
|
42
45
|
)
|
|
@@ -43,7 +43,7 @@ export default function ProtocolBuilderPage() {
|
|
|
43
43
|
|
|
44
44
|
if (isLoading) {
|
|
45
45
|
return (
|
|
46
|
-
<div className="flex h-
|
|
46
|
+
<div className="flex h-full min-h-0 min-w-0 flex-1 items-center justify-center">
|
|
47
47
|
<div className="text-sm text-muted-foreground">Loading builder...</div>
|
|
48
48
|
</div>
|
|
49
49
|
)
|
|
@@ -52,7 +52,7 @@ export default function ProtocolBuilderPage() {
|
|
|
52
52
|
const template = templates?.find((t) => t.id === templateId)
|
|
53
53
|
if (!template) {
|
|
54
54
|
return (
|
|
55
|
-
<div className="flex h-
|
|
55
|
+
<div className="flex h-full min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-3">
|
|
56
56
|
<div className="text-sm text-muted-foreground">Template not found</div>
|
|
57
57
|
<button
|
|
58
58
|
onClick={() => router.push('/protocols')}
|
|
@@ -65,9 +65,9 @@ export default function ProtocolBuilderPage() {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
return (
|
|
68
|
-
<div className="flex h-full flex-col">
|
|
68
|
+
<div className="flex h-full min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
|
69
69
|
{/* Header */}
|
|
70
|
-
<div className="flex items-center justify-between border-b px-4 py-2">
|
|
70
|
+
<div className="flex shrink-0 items-center justify-between border-b px-4 py-2">
|
|
71
71
|
<div className="flex items-center gap-3">
|
|
72
72
|
<button
|
|
73
73
|
onClick={() => router.push('/protocols')}
|
|
@@ -85,7 +85,7 @@ export default function ProtocolBuilderPage() {
|
|
|
85
85
|
</div>
|
|
86
86
|
|
|
87
87
|
{/* Canvas */}
|
|
88
|
-
<div className="flex-1 p-3">
|
|
88
|
+
<div className="min-h-0 flex-1 p-3">
|
|
89
89
|
<ProtocolBuilderCanvas />
|
|
90
90
|
</div>
|
|
91
91
|
</div>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useRef, useState, useCallback } from 'react'
|
|
4
4
|
import { useRouter, usePathname } from 'next/navigation'
|
|
5
|
+
import { useTheme } from 'next-themes'
|
|
5
6
|
import { initAudioContext } from '@/lib/tts'
|
|
6
7
|
import { clearStoredAccessKey } from '@/lib/app/api-client'
|
|
7
8
|
import { safeStorageGet, safeStorageRemove, safeStorageSet } from '@/lib/app/safe-storage'
|
|
@@ -14,6 +15,7 @@ import { useWs } from '@/hooks/use-ws'
|
|
|
14
15
|
import { api } from '@/lib/app/api-client'
|
|
15
16
|
import { pathToView, useNavigate } from '@/lib/app/navigation'
|
|
16
17
|
import { shouldAutoOpenPanelSidebar } from '@/lib/app/view-constants'
|
|
18
|
+
import { normalizeThemeMode } from '@/lib/theme-mode'
|
|
17
19
|
|
|
18
20
|
import { FullScreenLoader } from '@/components/ui/full-screen-loader'
|
|
19
21
|
import { SidebarRail } from '@/components/layout/sidebar-rail'
|
|
@@ -32,6 +34,7 @@ export function DashboardShell({ children }: { children: React.ReactNode }) {
|
|
|
32
34
|
const router = useRouter()
|
|
33
35
|
const pathname = usePathname()
|
|
34
36
|
const navigateTo = useNavigate()
|
|
37
|
+
const { setTheme } = useTheme()
|
|
35
38
|
|
|
36
39
|
const {
|
|
37
40
|
hydrated,
|
|
@@ -152,6 +155,12 @@ export function DashboardShell({ children }: { children: React.ReactNode }) {
|
|
|
152
155
|
}
|
|
153
156
|
}, [appSettings.themeHue])
|
|
154
157
|
|
|
158
|
+
// Theme mode
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (!appSettings.themeMode) return
|
|
161
|
+
setTheme(normalizeThemeMode(appSettings.themeMode))
|
|
162
|
+
}, [appSettings.themeMode, setTheme])
|
|
163
|
+
|
|
155
164
|
// View validity check
|
|
156
165
|
const isViewEnabled = useCallback((view: AppView) => {
|
|
157
166
|
if (view === 'webhooks') return extensions['http']?.enabled !== false
|