@swarmclawai/swarmclaw 1.9.34 → 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 CHANGED
@@ -151,6 +151,31 @@ 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
+
171
+ ## v1.9.35 Highlights
172
+
173
+ Installed package build fix for fresh npm-global installs and upgrades.
174
+
175
+ - **Fallback build dependency fix.** The npm package now declares `mime-types` and `@types/mime-types` directly so `swarmclaw server --build` can type-check the OpenClaw media proxy on clean installs.
176
+ - **Installed-build regression guard.** CLI/package tests now verify that unbundled type declarations needed by local fallback builds ship as runtime package dependencies.
177
+ - **macOS desktop status.** The damaged-app issue remains open until Developer ID signing and Apple notarization are configured and verified on downloaded macOS artifacts.
178
+
154
179
  ## v1.9.34 Highlights
155
180
 
156
181
  Credential recovery and external extension access release for npm-global upgrades and scoped agent tool configuration.
@@ -454,6 +479,31 @@ Operational docs: https://swarmclaw.ai/docs/observability
454
479
 
455
480
  ## Releases
456
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
+
499
+ ### v1.9.35 Highlights
500
+
501
+ Installed package build fix for fresh npm-global installs and upgrades.
502
+
503
+ - **Fallback build dependency fix.** The npm package now declares `mime-types` and `@types/mime-types` directly so `swarmclaw server --build` can type-check the OpenClaw media proxy on clean installs.
504
+ - **Installed-build regression guard.** CLI/package tests now verify that unbundled type declarations needed by local fallback builds ship as runtime package dependencies.
505
+ - **macOS desktop status.** The damaged-app issue remains open until Developer ID signing and Apple notarization are configured and verified on downloaded macOS artifacts.
506
+
457
507
  ### v1.9.34 Highlights
458
508
 
459
509
  Credential recovery and external extension access release for npm-global upgrades and scoped agent tool configuration.
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const electron_1 = require("electron");
40
+ const node_fs_1 = __importDefault(require("node:fs"));
41
+ const node_path_1 = __importDefault(require("node:path"));
42
+ const paths_1 = require("./paths");
43
+ const server_lifecycle_1 = require("./server-lifecycle");
44
+ const menu_1 = require("./menu");
45
+ const DEV_URL_DEFAULT = 'http://127.0.0.1:3456';
46
+ const LOG_TAIL_BYTES = 1500;
47
+ let mainWindow = null;
48
+ let serverHandle = null;
49
+ let serverLogFile = null;
50
+ let isQuitting = false;
51
+ const gotLock = electron_1.app.requestSingleInstanceLock();
52
+ if (!gotLock) {
53
+ electron_1.app.quit();
54
+ }
55
+ else {
56
+ electron_1.app.on('second-instance', () => {
57
+ if (mainWindow) {
58
+ if (mainWindow.isMinimized())
59
+ mainWindow.restore();
60
+ mainWindow.focus();
61
+ }
62
+ });
63
+ electron_1.app.on('ready', () => void onReady());
64
+ electron_1.app.on('window-all-closed', () => {
65
+ if (process.platform !== 'darwin')
66
+ electron_1.app.quit();
67
+ });
68
+ electron_1.app.on('activate', () => {
69
+ if (mainWindow !== null)
70
+ return;
71
+ if (serverHandle) {
72
+ createMainWindow(serverHandle.url);
73
+ }
74
+ else if (!electron_1.app.isPackaged) {
75
+ createMainWindow(process.env.SWARMCLAW_DEV_URL || DEV_URL_DEFAULT);
76
+ }
77
+ });
78
+ electron_1.app.on('before-quit', () => {
79
+ isQuitting = true;
80
+ });
81
+ electron_1.app.on('will-quit', async (event) => {
82
+ if (!serverHandle)
83
+ return;
84
+ event.preventDefault();
85
+ try {
86
+ await serverHandle.stop();
87
+ }
88
+ finally {
89
+ serverHandle = null;
90
+ electron_1.app.exit(0);
91
+ }
92
+ });
93
+ }
94
+ async function onReady() {
95
+ const paths = (0, paths_1.resolveRuntimePaths)();
96
+ (0, menu_1.buildAppMenu)(paths, () => mainWindow);
97
+ const iconPath = resolveIconPath();
98
+ if (process.platform === 'darwin' && iconPath && electron_1.app.dock) {
99
+ const img = electron_1.nativeImage.createFromPath(iconPath);
100
+ if (!img.isEmpty())
101
+ electron_1.app.dock.setIcon(img);
102
+ }
103
+ if (!electron_1.app.isPackaged) {
104
+ const devUrl = process.env.SWARMCLAW_DEV_URL || DEV_URL_DEFAULT;
105
+ console.log(`[swarmclaw] dev mode, loading ${devUrl}`);
106
+ createMainWindow(devUrl);
107
+ return;
108
+ }
109
+ serverLogFile = node_path_1.default.join(electron_1.app.getPath('userData'), 'logs', 'server.log');
110
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(serverLogFile), { recursive: true });
111
+ try {
112
+ serverHandle = await (0, server_lifecycle_1.startEmbeddedServer)({
113
+ paths,
114
+ logFile: serverLogFile,
115
+ onStdout: (c) => process.stdout.write(`[swarmclaw] ${c}`),
116
+ onStderr: (c) => process.stderr.write(`[swarmclaw] ${c}`),
117
+ onExit: (code, signal) => {
118
+ if (!isQuitting) {
119
+ console.error(`[swarmclaw] server exited unexpectedly (code=${code}, signal=${signal ?? 'none'})`);
120
+ void showServerCrashDialog(code, signal);
121
+ }
122
+ },
123
+ });
124
+ }
125
+ catch (err) {
126
+ await showStartupFailureDialog(err, paths);
127
+ electron_1.app.exit(1);
128
+ return;
129
+ }
130
+ createMainWindow(serverHandle.url);
131
+ void Promise.resolve().then(() => __importStar(require('./updater'))).then((m) => m.initAutoUpdater());
132
+ }
133
+ function resolveIconPath() {
134
+ const candidate = electron_1.app.isPackaged
135
+ ? node_path_1.default.join(process.resourcesPath, 'icon.png')
136
+ : node_path_1.default.join(__dirname, '..', 'resources', 'icon.png');
137
+ return node_fs_1.default.existsSync(candidate) ? candidate : undefined;
138
+ }
139
+ function createMainWindow(startUrl) {
140
+ const iconPath = resolveIconPath();
141
+ mainWindow = new electron_1.BrowserWindow({
142
+ width: 1440,
143
+ height: 900,
144
+ minWidth: 1024,
145
+ minHeight: 640,
146
+ backgroundColor: '#0b0b0f',
147
+ show: true,
148
+ ...(iconPath ? { icon: iconPath } : {}),
149
+ webPreferences: {
150
+ contextIsolation: true,
151
+ nodeIntegration: false,
152
+ sandbox: false,
153
+ },
154
+ });
155
+ const wc = mainWindow.webContents;
156
+ if (!electron_1.app.isPackaged)
157
+ wc.openDevTools({ mode: 'detach' });
158
+ wc.on('did-start-loading', () => console.log('[swarmclaw] did-start-loading'));
159
+ wc.on('did-finish-load', () => console.log('[swarmclaw] did-finish-load'));
160
+ wc.on('did-fail-load', (_e, code, desc, url) => console.error(`[swarmclaw] did-fail-load code=${code} desc=${desc} url=${url}`));
161
+ wc.on('render-process-gone', (_e, details) => console.error(`[swarmclaw] render-process-gone reason=${details.reason}`));
162
+ wc.on('unresponsive', () => console.error('[swarmclaw] webContents unresponsive'));
163
+ mainWindow.on('closed', () => {
164
+ mainWindow = null;
165
+ });
166
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
167
+ if (url.startsWith(startUrl))
168
+ return { action: 'allow' };
169
+ void electron_1.shell.openExternal(url);
170
+ return { action: 'deny' };
171
+ });
172
+ void mainWindow.loadURL(startUrl).catch((err) => {
173
+ console.error('[swarmclaw] loadURL rejected:', err);
174
+ });
175
+ }
176
+ async function showServerCrashDialog(code, signal) {
177
+ const buttons = serverLogFile ? ['Open Logs Folder', 'Quit'] : ['Quit'];
178
+ const quitButtonId = buttons.length - 1;
179
+ const detail = buildLogDetail(`code=${code ?? 'null'} signal=${signal ?? 'none'}`);
180
+ const res = await electron_1.dialog.showMessageBox({
181
+ type: 'error',
182
+ buttons,
183
+ defaultId: quitButtonId,
184
+ cancelId: quitButtonId,
185
+ title: 'SwarmClaw stopped',
186
+ message: 'The SwarmClaw server exited unexpectedly.',
187
+ detail,
188
+ });
189
+ if (serverLogFile && res.response === 0)
190
+ electron_1.shell.showItemInFolder(serverLogFile);
191
+ electron_1.app.exit(1);
192
+ }
193
+ async function showStartupFailureDialog(err, paths) {
194
+ const message = err instanceof Error ? err.message : String(err);
195
+ const base = `${message}\n\nStandalone entry: ${paths.standaloneEntry}\nData dir: ${paths.dataDir}`;
196
+ const detail = buildLogDetail(base);
197
+ const buttons = serverLogFile ? ['Open Logs Folder', 'Quit'] : ['Quit'];
198
+ const quitButtonId = buttons.length - 1;
199
+ const res = await electron_1.dialog.showMessageBox({
200
+ type: 'error',
201
+ buttons,
202
+ defaultId: quitButtonId,
203
+ cancelId: quitButtonId,
204
+ title: 'SwarmClaw failed to start',
205
+ message: 'The embedded server did not start.',
206
+ detail,
207
+ });
208
+ if (serverLogFile && res.response === 0)
209
+ electron_1.shell.showItemInFolder(serverLogFile);
210
+ }
211
+ function buildLogDetail(base) {
212
+ if (!serverLogFile)
213
+ return base;
214
+ const tail = (0, server_lifecycle_1.tailLogFile)(serverLogFile, LOG_TAIL_BYTES).trim();
215
+ if (!tail)
216
+ return `${base}\n\nLog file: ${serverLogFile}\n(no output captured yet)`;
217
+ return `${base}\n\nLog tail (${serverLogFile}):\n${tail}`;
218
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "1.9.34",
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",
@@ -121,6 +121,7 @@
121
121
  "@types/better-sqlite3": "^7.6.13",
122
122
  "@types/dagre": "^0.7.54",
123
123
  "@types/mailparser": "^3.4.6",
124
+ "@types/mime-types": "^2.1.4",
124
125
  "@types/node": "^20",
125
126
  "@types/nodemailer": "^7.0.11",
126
127
  "@types/qrcode": "^1.5.6",
@@ -151,6 +152,7 @@
151
152
  "langchain": "^1.2.30",
152
153
  "lucide-react": "^0.574.0",
153
154
  "mailparser": "^3.9.3",
155
+ "mime-types": "^3.0.2",
154
156
  "next": "16.2.4",
155
157
  "next-themes": "^0.4.6",
156
158
  "nodemailer": "^8.0.1",
@@ -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, saveSession } from '@/lib/server/sessions/session-repository'
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 merged = mergeHistoryMessages(session.messages, preview)
99
- const newCount = merged.length - session.messages.length
100
- session.messages = merged
101
- session.lastActiveAt = Date.now()
102
- saveSession(localSessionId, session)
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() ? resolved : path.dirname(resolved)
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 (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
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 (fs.existsSync(normalizedFile) && fs.statSync(normalizedFile).isDirectory()) {
168
- const files = fs.readdirSync(normalizedFile)
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
- if (!Array.isArray(session.messages) || !session.messages.length) continue
63
- const messages = session.messages as Array<Record<string, unknown>>
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
- return NextResponse.json(loadPublicSettings())
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
- messages?: unknown[]
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 = Array.isArray(session?.messages) ? session!.messages!.length : records.length
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