@swarmclawai/swarmclaw 0.7.7 → 0.8.0
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 +12 -14
- package/next.config.ts +13 -2
- package/package.json +4 -2
- package/src/app/api/agents/[id]/thread/route.ts +9 -0
- package/src/app/api/agents/route.ts +4 -0
- package/src/app/api/agents/thread-route.test.ts +133 -0
- package/src/app/api/approvals/route.test.ts +148 -0
- package/src/app/api/canvas/[sessionId]/route.ts +3 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
- package/src/app/api/chats/[id]/devserver/route.ts +48 -7
- package/src/app/api/chats/[id]/messages/route.ts +42 -18
- package/src/app/api/chats/[id]/route.ts +1 -1
- package/src/app/api/chats/[id]/stop/route.ts +5 -4
- package/src/app/api/chats/route.ts +23 -2
- package/src/app/api/clawhub/install/route.ts +28 -8
- package/src/app/api/connectors/[id]/route.ts +46 -3
- package/src/app/api/connectors/route.ts +12 -8
- package/src/app/api/external-agents/route.test.ts +165 -0
- package/src/app/api/gateways/[id]/health/route.ts +27 -12
- package/src/app/api/gateways/[id]/route.ts +2 -0
- package/src/app/api/gateways/health-route.test.ts +135 -0
- package/src/app/api/gateways/route.ts +2 -0
- package/src/app/api/mcp-servers/route.test.ts +130 -0
- package/src/app/api/openclaw/deploy/route.ts +38 -5
- package/src/app/api/plugins/install/route.ts +46 -6
- package/src/app/api/plugins/marketplace/route.ts +48 -15
- package/src/app/api/preview-server/route.ts +26 -11
- package/src/app/api/projects/[id]/route.ts +6 -2
- package/src/app/api/projects/route.ts +4 -3
- package/src/app/api/schedules/[id]/run/route.ts +4 -0
- package/src/app/api/schedules/route.test.ts +86 -0
- package/src/app/api/schedules/route.ts +6 -1
- package/src/app/api/secrets/[id]/route.ts +1 -0
- package/src/app/api/secrets/route.ts +2 -1
- package/src/app/api/settings/route.ts +2 -0
- package/src/app/api/setup/check-provider/route.test.ts +19 -0
- package/src/app/api/setup/check-provider/route.ts +40 -10
- package/src/app/api/skills/[id]/route.ts +12 -0
- package/src/app/api/skills/import/route.ts +14 -12
- package/src/app/api/skills/route.ts +13 -1
- package/src/app/api/tasks/[id]/route.ts +10 -1
- package/src/app/api/tasks/import/github/route.test.ts +65 -0
- package/src/app/api/tasks/import/github/route.ts +337 -0
- package/src/app/api/wallets/[id]/approve/route.ts +17 -3
- package/src/app/api/wallets/[id]/route.ts +79 -33
- package/src/app/api/wallets/[id]/send/route.ts +19 -33
- package/src/app/api/wallets/route.ts +78 -61
- package/src/app/api/webhooks/[id]/route.ts +33 -6
- package/src/app/api/webhooks/route.test.ts +272 -0
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-card.tsx +9 -2
- package/src/components/agents/agent-chat-list.tsx +18 -2
- package/src/components/agents/agent-list.tsx +1 -0
- package/src/components/agents/agent-sheet.tsx +257 -38
- package/src/components/agents/inspector-panel.tsx +41 -0
- package/src/components/canvas/canvas-panel.tsx +236 -65
- package/src/components/chat/chat-area.tsx +36 -19
- package/src/components/chat/chat-card.tsx +36 -13
- package/src/components/chat/chat-header.tsx +48 -16
- package/src/components/chat/chat-list.tsx +28 -4
- package/src/components/chat/checkpoint-timeline.tsx +50 -34
- package/src/components/chat/delegation-banner.test.ts +14 -1
- package/src/components/chat/delegation-banner.tsx +1 -1
- package/src/components/chat/message-bubble.tsx +208 -145
- package/src/components/chat/message-list.tsx +48 -19
- package/src/components/chatrooms/chatroom-message.tsx +2 -2
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
- package/src/components/connectors/connector-health.tsx +1 -1
- package/src/components/connectors/connector-list.tsx +7 -2
- package/src/components/connectors/connector-sheet.tsx +337 -148
- package/src/components/gateways/gateway-sheet.tsx +2 -2
- package/src/components/layout/app-layout.tsx +40 -23
- package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
- package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
- package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
- package/src/components/plugins/plugin-list.tsx +45 -9
- package/src/components/plugins/plugin-sheet.tsx +55 -7
- package/src/components/projects/project-detail.tsx +217 -0
- package/src/components/projects/project-sheet.tsx +176 -4
- package/src/components/providers/provider-list.tsx +2 -1
- package/src/components/providers/provider-sheet.tsx +21 -2
- package/src/components/schedules/schedule-card.tsx +25 -1
- package/src/components/schedules/schedule-sheet.tsx +44 -2
- package/src/components/secrets/secret-sheet.tsx +21 -2
- package/src/components/shared/agent-switch-dialog.tsx +12 -1
- package/src/components/shared/bottom-sheet.tsx +13 -3
- package/src/components/shared/command-palette.tsx +8 -1
- package/src/components/shared/confirm-dialog.tsx +19 -4
- package/src/components/shared/connector-platform-icon.test.ts +28 -0
- package/src/components/shared/connector-platform-icon.tsx +39 -6
- package/src/components/shared/settings/plugin-manager.tsx +29 -6
- package/src/components/shared/settings/section-capability-policy.tsx +45 -3
- package/src/components/shared/settings/section-voice.tsx +11 -3
- package/src/components/skills/skill-list.tsx +25 -0
- package/src/components/skills/skill-sheet.tsx +84 -12
- package/src/components/tasks/approvals-panel.tsx +289 -34
- package/src/components/tasks/task-board.tsx +410 -25
- package/src/components/tasks/task-card.tsx +66 -8
- package/src/components/tasks/task-sheet.tsx +16 -4
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
- package/src/components/wallets/wallet-panel.tsx +435 -90
- package/src/components/wallets/wallet-section.tsx +198 -48
- package/src/components/webhooks/webhook-sheet.tsx +22 -2
- package/src/lib/approval-display.ts +20 -0
- package/src/lib/canvas-content.ts +198 -0
- package/src/lib/chat-artifact-summary.ts +165 -0
- package/src/lib/chat-display.test.ts +91 -0
- package/src/lib/chat-display.ts +58 -0
- package/src/lib/chat-streaming-state.test.ts +47 -1
- package/src/lib/chat-streaming-state.ts +42 -0
- package/src/lib/ollama-model.ts +10 -0
- package/src/lib/openclaw-endpoint.test.ts +8 -0
- package/src/lib/openclaw-endpoint.ts +6 -1
- package/src/lib/plugin-install-cors.ts +46 -0
- package/src/lib/plugin-sources.test.ts +43 -0
- package/src/lib/plugin-sources.ts +77 -0
- package/src/lib/providers/ollama.ts +16 -6
- package/src/lib/providers/openclaw.test.ts +54 -0
- package/src/lib/providers/openclaw.ts +127 -11
- package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
- package/src/lib/schedule-dedupe.test.ts +66 -1
- package/src/lib/schedule-dedupe.ts +169 -12
- package/src/lib/schedule-origin.test.ts +20 -0
- package/src/lib/schedule-origin.ts +15 -0
- package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
- package/src/lib/server/agent-availability.ts +16 -0
- package/src/lib/server/agent-runtime-config.ts +12 -4
- package/src/lib/server/agent-thread-session.test.ts +51 -0
- package/src/lib/server/agent-thread-session.ts +7 -0
- package/src/lib/server/approval-match.ts +205 -0
- package/src/lib/server/approvals-auto-approve.test.ts +538 -1
- package/src/lib/server/approvals.ts +214 -1
- package/src/lib/server/assistant-control.test.ts +29 -0
- package/src/lib/server/assistant-control.ts +23 -0
- package/src/lib/server/build-llm.test.ts +79 -0
- package/src/lib/server/build-llm.ts +14 -4
- package/src/lib/server/canvas-content.test.ts +32 -0
- package/src/lib/server/canvas-content.ts +6 -0
- package/src/lib/server/capability-router.test.ts +33 -0
- package/src/lib/server/capability-router.ts +80 -19
- package/src/lib/server/chat-execution-advanced.test.ts +651 -0
- package/src/lib/server/chat-execution-disabled.test.ts +94 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
- package/src/lib/server/chat-execution.ts +378 -73
- package/src/lib/server/clawhub-client.test.ts +14 -8
- package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
- package/src/lib/server/connectors/manager.test.ts +1147 -0
- package/src/lib/server/connectors/manager.ts +461 -137
- package/src/lib/server/connectors/pairing.ts +26 -5
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp.test.ts +134 -0
- package/src/lib/server/connectors/whatsapp.ts +271 -47
- package/src/lib/server/context-manager.ts +6 -1
- package/src/lib/server/daemon-state.ts +84 -47
- package/src/lib/server/data-dir.test.ts +37 -0
- package/src/lib/server/data-dir.ts +20 -1
- package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
- package/src/lib/server/devserver-launch.test.ts +60 -0
- package/src/lib/server/devserver-launch.ts +85 -0
- package/src/lib/server/elevenlabs.test.ts +247 -1
- package/src/lib/server/elevenlabs.ts +147 -43
- package/src/lib/server/ethereum.ts +590 -0
- package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
- package/src/lib/server/eval/agent-regression.test.ts +18 -1
- package/src/lib/server/eval/agent-regression.ts +383 -11
- package/src/lib/server/evm-swap.ts +475 -0
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
- package/src/lib/server/heartbeat-service.ts +20 -11
- package/src/lib/server/heartbeat-wake.test.ts +112 -0
- package/src/lib/server/heartbeat-wake.ts +338 -57
- package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
- package/src/lib/server/main-agent-loop.test.ts +260 -0
- package/src/lib/server/main-agent-loop.ts +559 -14
- package/src/lib/server/mcp-client.test.ts +16 -0
- package/src/lib/server/mcp-client.ts +25 -0
- package/src/lib/server/memory-integration.test.ts +719 -0
- package/src/lib/server/memory-policy.test.ts +43 -0
- package/src/lib/server/memory-policy.ts +132 -0
- package/src/lib/server/memory-tiers.test.ts +60 -0
- package/src/lib/server/memory-tiers.ts +16 -0
- package/src/lib/server/ollama-runtime.ts +58 -0
- package/src/lib/server/openclaw-deploy.test.ts +109 -1
- package/src/lib/server/openclaw-deploy.ts +557 -81
- package/src/lib/server/openclaw-gateway.test.ts +131 -0
- package/src/lib/server/openclaw-gateway.ts +10 -4
- package/src/lib/server/openclaw-health.test.ts +35 -0
- package/src/lib/server/openclaw-health.ts +215 -47
- package/src/lib/server/orchestrator-lg.ts +3 -2
- package/src/lib/server/orchestrator.ts +2 -0
- package/src/lib/server/plugins-advanced.test.ts +351 -0
- package/src/lib/server/plugins.ts +211 -6
- package/src/lib/server/project-context.ts +162 -0
- package/src/lib/server/project-utils.ts +150 -0
- package/src/lib/server/queue-advanced.test.ts +528 -0
- package/src/lib/server/queue-followups.test.ts +409 -2
- package/src/lib/server/queue-reconcile.test.ts +128 -0
- package/src/lib/server/queue.ts +527 -68
- package/src/lib/server/scheduler.ts +29 -1
- package/src/lib/server/session-note.test.ts +36 -0
- package/src/lib/server/session-note.ts +42 -0
- package/src/lib/server/session-run-manager.ts +83 -4
- package/src/lib/server/session-tools/canvas.ts +14 -12
- package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
- package/src/lib/server/session-tools/connector.test.ts +138 -0
- package/src/lib/server/session-tools/connector.ts +366 -54
- package/src/lib/server/session-tools/context.ts +17 -3
- package/src/lib/server/session-tools/crud.ts +484 -84
- package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
- package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
- package/src/lib/server/session-tools/delegate.ts +102 -10
- package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
- package/src/lib/server/session-tools/discovery.ts +80 -12
- package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
- package/src/lib/server/session-tools/file.ts +43 -4
- package/src/lib/server/session-tools/human-loop.ts +35 -5
- package/src/lib/server/session-tools/index.ts +44 -9
- package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
- package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
- package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
- package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
- package/src/lib/server/session-tools/memory.test.ts +93 -0
- package/src/lib/server/session-tools/memory.ts +554 -75
- package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
- package/src/lib/server/session-tools/platform-access.test.ts +58 -0
- package/src/lib/server/session-tools/platform.ts +60 -19
- package/src/lib/server/session-tools/plugin-creator.ts +57 -1
- package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
- package/src/lib/server/session-tools/schedule.ts +6 -1
- package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
- package/src/lib/server/session-tools/shell.ts +22 -3
- package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
- package/src/lib/server/session-tools/wallet.ts +1374 -139
- package/src/lib/server/session-tools/web-inputs.test.ts +178 -0
- package/src/lib/server/session-tools/web.ts +621 -70
- package/src/lib/server/skill-discovery.ts +128 -0
- package/src/lib/server/skill-eligibility.test.ts +84 -0
- package/src/lib/server/skill-eligibility.ts +95 -0
- package/src/lib/server/skill-prompt-budget.test.ts +102 -0
- package/src/lib/server/skill-prompt-budget.ts +125 -0
- package/src/lib/server/skills-normalize.test.ts +54 -0
- package/src/lib/server/skills-normalize.ts +372 -26
- package/src/lib/server/solana.ts +214 -29
- package/src/lib/server/storage.ts +65 -36
- package/src/lib/server/stream-agent-chat.test.ts +437 -2
- package/src/lib/server/stream-agent-chat.ts +957 -79
- package/src/lib/server/system-events.ts +1 -1
- package/src/lib/server/tool-aliases.ts +2 -0
- package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
- package/src/lib/server/tool-capability-policy.test.ts +24 -0
- package/src/lib/server/tool-capability-policy.ts +29 -1
- package/src/lib/server/tool-loop-detection.test.ts +105 -0
- package/src/lib/server/tool-loop-detection.ts +260 -0
- package/src/lib/server/tool-planning.test.ts +44 -0
- package/src/lib/server/tool-planning.ts +271 -0
- package/src/lib/server/wallet-execution.test.ts +198 -0
- package/src/lib/server/wallet-portfolio.test.ts +98 -0
- package/src/lib/server/wallet-portfolio.ts +724 -0
- package/src/lib/server/wallet-service.test.ts +57 -0
- package/src/lib/server/wallet-service.ts +213 -0
- package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
- package/src/lib/server/watch-jobs.ts +17 -2
- package/src/lib/server/workspace-context.ts +111 -0
- package/src/lib/skill-save-payload.test.ts +39 -0
- package/src/lib/skill-save-payload.ts +37 -0
- package/src/lib/tasks.ts +28 -0
- package/src/lib/tool-definitions.ts +2 -1
- package/src/lib/tool-event-summary.test.ts +30 -0
- package/src/lib/tool-event-summary.ts +37 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/wallet-transactions.test.ts +75 -0
- package/src/lib/wallet-transactions.ts +43 -0
- package/src/lib/wallet.test.ts +17 -0
- package/src/lib/wallet.ts +183 -0
- package/src/proxy.test.ts +31 -0
- package/src/proxy.ts +34 -2
- package/src/stores/use-chat-store.ts +15 -1
- package/src/types/index.ts +249 -14
package/README.md
CHANGED
|
@@ -148,7 +148,7 @@ curl -fsSL https://raw.githubusercontent.com/swarmclawai/swarmclaw/main/install.
|
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
The installer resolves the latest stable release tag and installs that version by default.
|
|
151
|
-
To pin a version: `SWARMCLAW_VERSION=v0.
|
|
151
|
+
To pin a version: `SWARMCLAW_VERSION=v0.8.0 curl ... | bash`
|
|
152
152
|
|
|
153
153
|
Or run locally from the repo (friendly for non-technical users):
|
|
154
154
|
|
|
@@ -464,7 +464,7 @@ Policy is enforced in both chat tool construction and direct forced tool invocat
|
|
|
464
464
|
|
|
465
465
|
Configure these in **Settings**:
|
|
466
466
|
|
|
467
|
-
- **Voice** — set `ElevenLabs API Key`, `ElevenLabs Voice ID`, and `Speech Recognition Language
|
|
467
|
+
- **Voice** — set `ElevenLabs API Key`, `ElevenLabs Voice ID`, and `Speech Recognition Language`. Outbound voice-note sends retry with the built-in fallback ElevenLabs voice when a saved default voice is rejected as paid-only.
|
|
468
468
|
- **Heartbeat** — set `Heartbeat Interval (Seconds)` and `Heartbeat Prompt` for ongoing chat pings
|
|
469
469
|
- **Global heartbeat safety** — use `Stop All Heartbeats` to disable heartbeat across all chats and cancel in-flight heartbeat runs.
|
|
470
470
|
|
|
@@ -529,7 +529,7 @@ Built-in capabilities now resolve to a single canonical plugin family ID across
|
|
|
529
529
|
|
|
530
530
|
Agents in SwarmClaw are "aware" of the plugin system. If an agent lacks a tool needed for a task, it can:
|
|
531
531
|
1. **Discover**: Scan the system for all installed plugins.
|
|
532
|
-
2. **Search Marketplace**: Autonomously search **ClawHub** and the **
|
|
532
|
+
2. **Search Marketplace**: Autonomously search **ClawHub** and the **SwarmForge-backed first-party marketplace** for new capabilities.
|
|
533
533
|
3. **Request Access**: Prompt the user in-chat to enable a specific installed plugin.
|
|
534
534
|
4. **Install Request**: Suggest installing a new plugin from a marketplace URL to fill a capability gap (requires user approval).
|
|
535
535
|
|
|
@@ -663,8 +663,8 @@ npm run dev:webpack # Fallback to webpack dev server (if Turbopack crashes)
|
|
|
663
663
|
npm run dev:clean # Clear .next cache then restart dev server
|
|
664
664
|
npm run build # Production build
|
|
665
665
|
npm run build:ci # CI build (skips ESLint; lint baseline runs separately)
|
|
666
|
-
npm run start # Start production server
|
|
667
|
-
npm run start:standalone #
|
|
666
|
+
npm run start # Start the standalone production server after build
|
|
667
|
+
npm run start:standalone # Alias for the standalone production server
|
|
668
668
|
npm run lint # ESLint
|
|
669
669
|
npm run lint:baseline # Fail only on net-new lint issues vs .eslint-baseline.json
|
|
670
670
|
npm run lint:baseline:update # Refresh lint baseline intentionally
|
|
@@ -701,8 +701,8 @@ npm run update:easy # safe update helper for local installs
|
|
|
701
701
|
SwarmClaw uses tag-based releases (`vX.Y.Z`) as the stable channel.
|
|
702
702
|
|
|
703
703
|
```bash
|
|
704
|
-
# example
|
|
705
|
-
npm version
|
|
704
|
+
# example minor release (v0.8.0 style)
|
|
705
|
+
npm version minor
|
|
706
706
|
git push origin main --follow-tags
|
|
707
707
|
```
|
|
708
708
|
|
|
@@ -711,15 +711,13 @@ On `v*` tags, GitHub Actions will:
|
|
|
711
711
|
2. Create a GitHub Release
|
|
712
712
|
3. Build and publish Docker images to `ghcr.io/swarmclawai/swarmclaw` (`:vX.Y.Z`, `:latest`, `:sha-*`)
|
|
713
713
|
|
|
714
|
-
#### v0.
|
|
714
|
+
#### v0.8.0 Release Readiness Notes
|
|
715
715
|
|
|
716
|
-
Before shipping `v0.
|
|
716
|
+
Before shipping `v0.8.0`, confirm the following user-facing changes are reflected in docs:
|
|
717
717
|
|
|
718
|
-
1.
|
|
719
|
-
2.
|
|
720
|
-
3. Site and README install/version strings are updated to `v0.
|
|
721
|
-
4. Release notes summarize the user-visible operator changes from the current worktree, especially SSH deploy, remote lifecycle controls, routing preferences, and onboarding persistence.
|
|
722
|
-
5. CLI docs include the expanded `openclaw deploy-*`, `openclaw remote-*`, and verify surfaces and do not reference removed or unofficial deployment paths.
|
|
718
|
+
1. Voice and connector docs mention that outbound voice-note sends now retry with the built-in ElevenLabs fallback voice when a configured default voice is rejected as paid-only.
|
|
719
|
+
2. Release notes call out the Molly-style regression case explicitly: WhatsApp voice-note delivery should keep working even if the saved default voice ID points at a paid library voice.
|
|
720
|
+
3. Site and README install/version strings are updated to `v0.8.0`, including install snippets, release notes index text, and sidebar/footer labels.
|
|
723
721
|
|
|
724
722
|
## CLI
|
|
725
723
|
|
package/next.config.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { NextConfig } from "next";
|
|
2
2
|
import { execSync } from "child_process";
|
|
3
3
|
import { networkInterfaces } from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
4
6
|
import { DIRECT_NAV_SEGMENTS } from "./view-route-paths";
|
|
5
7
|
|
|
8
|
+
const PROJECT_ROOT = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
|
|
6
10
|
function getGitSha(): string {
|
|
7
11
|
try {
|
|
8
12
|
return execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim()
|
|
@@ -40,10 +44,17 @@ function getAllowedDevOrigins(): string[] {
|
|
|
40
44
|
|
|
41
45
|
const nextConfig: NextConfig = {
|
|
42
46
|
output: 'standalone',
|
|
47
|
+
outputFileTracingExcludes: {
|
|
48
|
+
'/api/**': ['data/browser-profiles/**/*', 'data/browser-profiles-regression/**/*'],
|
|
49
|
+
instrumentation: ['data/browser-profiles/**/*', 'data/browser-profiles-regression/**/*'],
|
|
50
|
+
'/instrumentation': ['data/browser-profiles/**/*', 'data/browser-profiles-regression/**/*'],
|
|
51
|
+
'next-server': ['data/browser-profiles/**/*', 'data/browser-profiles-regression/**/*'],
|
|
52
|
+
},
|
|
43
53
|
turbopack: {
|
|
44
54
|
// Pin workspace root to the project directory so a stale lockfile
|
|
45
|
-
// in a parent folder (e.g. ~/) doesn't confuse
|
|
46
|
-
|
|
55
|
+
// in a parent folder (e.g. ~/) or a nested launch cwd doesn't confuse
|
|
56
|
+
// native module resolution.
|
|
57
|
+
root: PROJECT_ROOT,
|
|
47
58
|
},
|
|
48
59
|
experimental: {
|
|
49
60
|
// Disable Turbopack persistent cache — concurrent HMR writes cause
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Self-hosted AI agent orchestration dashboard — manage LLM providers, orchestrate agent swarms, schedule tasks, and bridge agents to chat platforms.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -47,8 +47,9 @@
|
|
|
47
47
|
"dev:clean": "rm -rf .next && next dev --hostname 0.0.0.0 -p 3456",
|
|
48
48
|
"build": "next build",
|
|
49
49
|
"build:ci": "NEXT_DISABLE_ESLINT=1 next build",
|
|
50
|
-
"start": "next
|
|
50
|
+
"start": "node .next/standalone/server.js",
|
|
51
51
|
"start:standalone": "node .next/standalone/server.js",
|
|
52
|
+
"smoke:browser": "node ./scripts/browser-route-smoke.mjs",
|
|
52
53
|
"benchmark:autonomy": "node ./scripts/benchmark-autonomy-harness.mjs",
|
|
53
54
|
"benchmark:agent-regression": "node --import tsx ./scripts/run-agent-regression-suite.ts",
|
|
54
55
|
"lint": "eslint",
|
|
@@ -81,6 +82,7 @@
|
|
|
81
82
|
"cron-parser": "^5.5.0",
|
|
82
83
|
"cronstrue": "^3.12.0",
|
|
83
84
|
"discord.js": "^14.25.1",
|
|
85
|
+
"ethers": "^6.16.0",
|
|
84
86
|
"exceljs": "^4.4.0",
|
|
85
87
|
"grammy": "^1.40.0",
|
|
86
88
|
"highlight.js": "^11.11.1",
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
+
import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agent-availability'
|
|
2
3
|
import { ensureAgentThreadSession } from '@/lib/server/agent-thread-session'
|
|
4
|
+
import { loadAgents } from '@/lib/server/storage'
|
|
3
5
|
|
|
4
6
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
5
7
|
const { id: agentId } = await params
|
|
6
8
|
const body = await req.json().catch(() => ({}))
|
|
7
9
|
const user = body.user || 'default'
|
|
10
|
+
const agent = loadAgents()[agentId]
|
|
11
|
+
if (!agent) {
|
|
12
|
+
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
|
13
|
+
}
|
|
8
14
|
const session = ensureAgentThreadSession(agentId, user)
|
|
9
15
|
if (!session) {
|
|
16
|
+
if (isAgentDisabled(agent)) {
|
|
17
|
+
return NextResponse.json({ error: buildAgentDisabledMessage(agent, 'start new chats') }, { status: 409 })
|
|
18
|
+
}
|
|
10
19
|
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
|
11
20
|
}
|
|
12
21
|
return NextResponse.json(session)
|
|
@@ -3,6 +3,7 @@ import { genId } from '@/lib/id'
|
|
|
3
3
|
import { loadAgents, loadSessions, loadUsage, saveAgents, logActivity } from '@/lib/server/storage'
|
|
4
4
|
import { normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
|
|
5
5
|
import { notify } from '@/lib/server/ws-hub'
|
|
6
|
+
import { ensureDaemonStarted } from '@/lib/server/daemon-state'
|
|
6
7
|
import { getAgentSpendWindows } from '@/lib/server/cost'
|
|
7
8
|
import { AgentCreateSchema, formatZodError } from '@/lib/validation/schemas'
|
|
8
9
|
import { z } from 'zod'
|
|
@@ -10,6 +11,7 @@ export const dynamic = 'force-dynamic'
|
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
export async function GET(req: Request) {
|
|
14
|
+
ensureDaemonStarted('api/agents:get')
|
|
13
15
|
const agents = loadAgents()
|
|
14
16
|
const sessions = loadSessions()
|
|
15
17
|
const usage = loadUsage()
|
|
@@ -42,6 +44,7 @@ export async function GET(req: Request) {
|
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
export async function POST(req: Request) {
|
|
47
|
+
ensureDaemonStarted('api/agents:post')
|
|
45
48
|
const raw = await req.json()
|
|
46
49
|
const parsed = AgentCreateSchema.safeParse(raw)
|
|
47
50
|
if (!parsed.success) {
|
|
@@ -82,6 +85,7 @@ export async function POST(req: Request) {
|
|
|
82
85
|
capabilities: body.capabilities,
|
|
83
86
|
thinkingLevel: body.thinkingLevel || undefined,
|
|
84
87
|
autoRecovery: body.autoRecovery || false,
|
|
88
|
+
disabled: body.disabled || false,
|
|
85
89
|
heartbeatEnabled: body.heartbeatEnabled || false,
|
|
86
90
|
heartbeatInterval: body.heartbeatInterval,
|
|
87
91
|
heartbeatIntervalSec: body.heartbeatIntervalSec,
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test, { afterEach } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { POST as createAgentThread } from './[id]/thread/route'
|
|
5
|
+
import { loadAgents, loadSessions, saveAgents, saveSessions } from '@/lib/server/storage'
|
|
6
|
+
|
|
7
|
+
const originalAgents = loadAgents()
|
|
8
|
+
const originalSessions = loadSessions()
|
|
9
|
+
|
|
10
|
+
function routeParams(id: string) {
|
|
11
|
+
return { params: Promise.resolve({ id }) }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function seedAgent(id: string, overrides: Record<string, unknown> = {}) {
|
|
15
|
+
const agents = loadAgents()
|
|
16
|
+
const now = Date.now()
|
|
17
|
+
agents[id] = {
|
|
18
|
+
id,
|
|
19
|
+
name: 'Thread Test Agent',
|
|
20
|
+
description: 'Agent thread route smoke',
|
|
21
|
+
systemPrompt: 'Be helpful.',
|
|
22
|
+
provider: 'openai',
|
|
23
|
+
model: 'gpt-4o-mini',
|
|
24
|
+
credentialId: null,
|
|
25
|
+
fallbackCredentialIds: [],
|
|
26
|
+
apiEndpoint: null,
|
|
27
|
+
gatewayProfileId: null,
|
|
28
|
+
plugins: ['memory'],
|
|
29
|
+
createdAt: now,
|
|
30
|
+
updatedAt: now,
|
|
31
|
+
...overrides,
|
|
32
|
+
}
|
|
33
|
+
saveAgents(agents)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
saveAgents(originalAgents)
|
|
38
|
+
saveSessions(originalSessions)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('POST /api/agents/[id]/thread returns 409 for a disabled agent without an existing thread', async () => {
|
|
42
|
+
seedAgent('agent-thread-disabled', { disabled: true })
|
|
43
|
+
|
|
44
|
+
const response = await createAgentThread(new Request('http://local/api/agents/agent-thread-disabled/thread', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'content-type': 'application/json' },
|
|
47
|
+
body: JSON.stringify({ user: 'default' }),
|
|
48
|
+
}), routeParams('agent-thread-disabled'))
|
|
49
|
+
|
|
50
|
+
assert.equal(response.status, 409)
|
|
51
|
+
const payload = await response.json() as Record<string, unknown>
|
|
52
|
+
assert.match(String(payload.error || ''), /disabled/i)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('POST /api/agents/[id]/thread reuses an existing thread for a disabled agent', async () => {
|
|
56
|
+
seedAgent('agent-thread-disabled-existing', {
|
|
57
|
+
disabled: true,
|
|
58
|
+
threadSessionId: 'session-disabled-existing',
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const sessions = loadSessions()
|
|
62
|
+
sessions['session-disabled-existing'] = {
|
|
63
|
+
id: 'session-disabled-existing',
|
|
64
|
+
name: 'Thread Test Agent',
|
|
65
|
+
shortcutForAgentId: 'agent-thread-disabled-existing',
|
|
66
|
+
cwd: '/tmp',
|
|
67
|
+
user: 'default',
|
|
68
|
+
provider: 'openai',
|
|
69
|
+
model: 'gpt-4o-mini',
|
|
70
|
+
credentialId: null,
|
|
71
|
+
fallbackCredentialIds: [],
|
|
72
|
+
apiEndpoint: null,
|
|
73
|
+
gatewayProfileId: null,
|
|
74
|
+
routePreferredGatewayTags: [],
|
|
75
|
+
routePreferredGatewayUseCase: null,
|
|
76
|
+
claudeSessionId: null,
|
|
77
|
+
codexThreadId: null,
|
|
78
|
+
opencodeSessionId: null,
|
|
79
|
+
delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
|
|
80
|
+
messages: [],
|
|
81
|
+
createdAt: 1,
|
|
82
|
+
lastActiveAt: 1,
|
|
83
|
+
active: false,
|
|
84
|
+
sessionType: 'human',
|
|
85
|
+
agentId: 'agent-thread-disabled-existing',
|
|
86
|
+
parentSessionId: null,
|
|
87
|
+
plugins: ['memory'],
|
|
88
|
+
tools: ['memory'],
|
|
89
|
+
heartbeatEnabled: false,
|
|
90
|
+
heartbeatIntervalSec: null,
|
|
91
|
+
heartbeatTarget: null,
|
|
92
|
+
sessionResetMode: null,
|
|
93
|
+
sessionIdleTimeoutSec: null,
|
|
94
|
+
sessionMaxAgeSec: null,
|
|
95
|
+
sessionDailyResetAt: null,
|
|
96
|
+
sessionResetTimezone: null,
|
|
97
|
+
thinkingLevel: null,
|
|
98
|
+
browserProfileId: null,
|
|
99
|
+
connectorThinkLevel: null,
|
|
100
|
+
connectorSessionScope: null,
|
|
101
|
+
connectorReplyMode: null,
|
|
102
|
+
connectorThreadBinding: null,
|
|
103
|
+
connectorGroupPolicy: null,
|
|
104
|
+
connectorIdleTimeoutSec: null,
|
|
105
|
+
connectorMaxAgeSec: null,
|
|
106
|
+
mailbox: null,
|
|
107
|
+
connectorContext: null,
|
|
108
|
+
lastAutoMemoryAt: null,
|
|
109
|
+
lastHeartbeatText: null,
|
|
110
|
+
lastHeartbeatSentAt: null,
|
|
111
|
+
lastSessionResetAt: null,
|
|
112
|
+
lastSessionResetReason: null,
|
|
113
|
+
identityState: null,
|
|
114
|
+
sessionArchiveState: null,
|
|
115
|
+
pinned: false,
|
|
116
|
+
file: null,
|
|
117
|
+
queuedCount: 0,
|
|
118
|
+
currentRunId: null,
|
|
119
|
+
canvasContent: null,
|
|
120
|
+
}
|
|
121
|
+
saveSessions(sessions)
|
|
122
|
+
|
|
123
|
+
const response = await createAgentThread(new Request('http://local/api/agents/agent-thread-disabled-existing/thread', {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: { 'content-type': 'application/json' },
|
|
126
|
+
body: JSON.stringify({ user: 'default' }),
|
|
127
|
+
}), routeParams('agent-thread-disabled-existing'))
|
|
128
|
+
|
|
129
|
+
assert.equal(response.status, 200)
|
|
130
|
+
const payload = await response.json() as Record<string, unknown>
|
|
131
|
+
assert.equal(payload.id, 'session-disabled-existing')
|
|
132
|
+
assert.equal(payload.agentId, 'agent-thread-disabled-existing')
|
|
133
|
+
})
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
|
+
import test from 'node:test'
|
|
7
|
+
|
|
8
|
+
const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '../../../..')
|
|
9
|
+
|
|
10
|
+
function runWithTempDataDir(script: string) {
|
|
11
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-approvals-route-'))
|
|
12
|
+
try {
|
|
13
|
+
const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', script], {
|
|
14
|
+
cwd: repoRoot,
|
|
15
|
+
env: {
|
|
16
|
+
...process.env,
|
|
17
|
+
DATA_DIR: path.join(tempDir, 'data'),
|
|
18
|
+
WORKSPACE_DIR: path.join(tempDir, 'workspace'),
|
|
19
|
+
},
|
|
20
|
+
encoding: 'utf-8',
|
|
21
|
+
})
|
|
22
|
+
assert.equal(result.status, 0, result.stderr || result.stdout || 'subprocess failed')
|
|
23
|
+
const lines = (result.stdout || '')
|
|
24
|
+
.trim()
|
|
25
|
+
.split('\n')
|
|
26
|
+
.map((line) => line.trim())
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
const jsonLine = [...lines].reverse().find((line) => line.startsWith('{'))
|
|
29
|
+
return JSON.parse(jsonLine || '{}')
|
|
30
|
+
} finally {
|
|
31
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test('GET and POST /api/approvals smoke the pending approval flow end-to-end', () => {
|
|
36
|
+
const output = runWithTempDataDir(`
|
|
37
|
+
const storageMod = await import('./src/lib/server/storage.ts')
|
|
38
|
+
const approvalsMod = await import('./src/lib/server/approvals.ts')
|
|
39
|
+
const routeMod = await import('./src/app/api/approvals/route.ts')
|
|
40
|
+
const storage = storageMod.default || storageMod
|
|
41
|
+
const approvals = approvalsMod.default || approvalsMod
|
|
42
|
+
const route = routeMod.default || routeMod
|
|
43
|
+
|
|
44
|
+
const now = Date.now()
|
|
45
|
+
storage.saveSettings({
|
|
46
|
+
approvalsEnabled: true,
|
|
47
|
+
approvalAutoApproveCategories: [],
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const approval = await approvals.requestApprovalMaybeAutoApprove({
|
|
51
|
+
category: 'tool_access',
|
|
52
|
+
title: 'Enable Plugin: shell',
|
|
53
|
+
description: 'Need shell access for a smoke test.',
|
|
54
|
+
data: { toolId: 'shell', pluginId: 'shell' },
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const pendingBefore = await route.GET()
|
|
58
|
+
const pendingBeforeJson = await pendingBefore.json()
|
|
59
|
+
|
|
60
|
+
const approveResponse = await route.POST(new Request('http://local/api/approvals', {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: { 'content-type': 'application/json' },
|
|
63
|
+
body: JSON.stringify({ id: approval.id, approved: true }),
|
|
64
|
+
}))
|
|
65
|
+
const approvePayload = await approveResponse.json()
|
|
66
|
+
|
|
67
|
+
const pendingAfter = await route.GET()
|
|
68
|
+
const pendingAfterJson = await pendingAfter.json()
|
|
69
|
+
|
|
70
|
+
const storedApproval = storage.loadApprovals()[approval.id]
|
|
71
|
+
console.log(JSON.stringify({
|
|
72
|
+
pendingBeforeCount: Array.isArray(pendingBeforeJson) ? pendingBeforeJson.length : -1,
|
|
73
|
+
pendingBeforeId: Array.isArray(pendingBeforeJson) ? pendingBeforeJson[0]?.id || null : null,
|
|
74
|
+
approveStatus: approveResponse.status,
|
|
75
|
+
approvePayload,
|
|
76
|
+
pendingAfterCount: Array.isArray(pendingAfterJson) ? pendingAfterJson.length : -1,
|
|
77
|
+
storedStatus: storedApproval?.status || null,
|
|
78
|
+
}))
|
|
79
|
+
`)
|
|
80
|
+
|
|
81
|
+
assert.equal(output.pendingBeforeCount, 1)
|
|
82
|
+
assert.notEqual(output.pendingBeforeId, null)
|
|
83
|
+
assert.equal(output.approveStatus, 200)
|
|
84
|
+
assert.equal(output.approvePayload?.ok, true)
|
|
85
|
+
assert.equal(output.pendingAfterCount, 0)
|
|
86
|
+
assert.equal(output.storedStatus, 'approved')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('POST /api/approvals rejects invalid payloads and remains idempotent for repeated decisions', () => {
|
|
90
|
+
const output = runWithTempDataDir(`
|
|
91
|
+
const storageMod = await import('./src/lib/server/storage.ts')
|
|
92
|
+
const approvalsMod = await import('./src/lib/server/approvals.ts')
|
|
93
|
+
const routeMod = await import('./src/app/api/approvals/route.ts')
|
|
94
|
+
const storage = storageMod.default || storageMod
|
|
95
|
+
const approvals = approvalsMod.default || approvalsMod
|
|
96
|
+
const route = routeMod.default || routeMod
|
|
97
|
+
|
|
98
|
+
const now = Date.now()
|
|
99
|
+
storage.saveSettings({
|
|
100
|
+
approvalsEnabled: true,
|
|
101
|
+
approvalAutoApproveCategories: [],
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const approval = await approvals.requestApprovalMaybeAutoApprove({
|
|
105
|
+
category: 'tool_access',
|
|
106
|
+
title: 'Enable Plugin: shell',
|
|
107
|
+
description: 'Need shell access for idempotency test.',
|
|
108
|
+
data: { toolId: 'shell', pluginId: 'shell' },
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const invalidResponse = await route.POST(new Request('http://local/api/approvals', {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
headers: { 'content-type': 'application/json' },
|
|
114
|
+
body: JSON.stringify({ id: approval.id }),
|
|
115
|
+
}))
|
|
116
|
+
const invalidPayload = await invalidResponse.json()
|
|
117
|
+
|
|
118
|
+
const firstApprove = await route.POST(new Request('http://local/api/approvals', {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: { 'content-type': 'application/json' },
|
|
121
|
+
body: JSON.stringify({ id: approval.id, approved: true }),
|
|
122
|
+
}))
|
|
123
|
+
const secondApprove = await route.POST(new Request('http://local/api/approvals', {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: { 'content-type': 'application/json' },
|
|
126
|
+
body: JSON.stringify({ id: approval.id, approved: true }),
|
|
127
|
+
}))
|
|
128
|
+
|
|
129
|
+
const pending = await route.GET()
|
|
130
|
+
const pendingJson = await pending.json()
|
|
131
|
+
const storedApproval = storage.loadApprovals()[approval.id]
|
|
132
|
+
console.log(JSON.stringify({
|
|
133
|
+
invalidStatus: invalidResponse.status,
|
|
134
|
+
invalidError: invalidPayload?.error || null,
|
|
135
|
+
firstApproveStatus: firstApprove.status,
|
|
136
|
+
secondApproveStatus: secondApprove.status,
|
|
137
|
+
pendingCount: Array.isArray(pendingJson) ? pendingJson.length : -1,
|
|
138
|
+
storedStatus: storedApproval?.status || null,
|
|
139
|
+
}))
|
|
140
|
+
`)
|
|
141
|
+
|
|
142
|
+
assert.equal(output.invalidStatus, 400)
|
|
143
|
+
assert.match(String(output.invalidError || ''), /id and approved required/i)
|
|
144
|
+
assert.equal(output.firstApproveStatus, 200)
|
|
145
|
+
assert.equal(output.secondApproveStatus, 200)
|
|
146
|
+
assert.equal(output.pendingCount, 0)
|
|
147
|
+
assert.equal(output.storedStatus, 'approved')
|
|
148
|
+
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { loadSessions, saveSessions } from '@/lib/server/storage'
|
|
3
3
|
import { notify } from '@/lib/server/ws-hub'
|
|
4
|
+
import { normalizeCanvasContent } from '@/lib/canvas-content'
|
|
4
5
|
|
|
5
6
|
export async function GET(_req: Request, { params }: { params: Promise<{ sessionId: string }> }) {
|
|
6
7
|
const { sessionId } = await params
|
|
@@ -21,7 +22,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ session
|
|
|
21
22
|
const session = sessions[sessionId]
|
|
22
23
|
if (!session) return NextResponse.json({ error: 'Session not found' }, { status: 404 })
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
const nextContent = normalizeCanvasContent(body.document ?? body.content)
|
|
26
|
+
;(session as Record<string, unknown>).canvasContent = nextContent
|
|
25
27
|
session.lastActiveAt = Date.now()
|
|
26
28
|
sessions[sessionId] = session
|
|
27
29
|
saveSessions(sessions)
|
|
@@ -23,6 +23,7 @@ import { evaluateRoutingRules } from '@/lib/server/chatroom-routing'
|
|
|
23
23
|
import { markProviderFailure, markProviderSuccess } from '@/lib/server/provider-health'
|
|
24
24
|
import { applyAgentReactionsFromText } from '@/lib/server/chatroom-orchestration'
|
|
25
25
|
import { resolvePrimaryAgentRoute } from '@/lib/server/agent-runtime-config'
|
|
26
|
+
import { shouldSuppressHiddenControlText, stripHiddenControlTokens } from '@/lib/server/assistant-control'
|
|
26
27
|
import type { Chatroom, ChatroomMessage, Agent } from '@/types'
|
|
27
28
|
|
|
28
29
|
export const dynamic = 'force-dynamic'
|
|
@@ -228,7 +229,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
228
229
|
history,
|
|
229
230
|
})
|
|
230
231
|
|
|
231
|
-
const
|
|
232
|
+
const rawResponseText = result.finalResponse || result.fullText || fullText
|
|
233
|
+
const responseText = stripHiddenControlTokens(rawResponseText)
|
|
232
234
|
|
|
233
235
|
// Don't persist empty or error-only messages — they pollute chat history
|
|
234
236
|
if (!responseText.trim() && agentError) {
|
|
@@ -238,7 +240,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
238
240
|
return []
|
|
239
241
|
}
|
|
240
242
|
|
|
241
|
-
if (responseText.trim()) {
|
|
243
|
+
if (responseText.trim() && !shouldSuppressHiddenControlText(rawResponseText)) {
|
|
242
244
|
appendSyntheticSessionMessage(syntheticSession.id, 'assistant', responseText)
|
|
243
245
|
const parsedMentions = parseMentions(responseText, agents, freshChatroom.agentIds)
|
|
244
246
|
const chainedHealth = filterHealthyChatroomAgents(parsedMentions, agents)
|
|
@@ -2,6 +2,27 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { spawn } from 'child_process'
|
|
3
3
|
import { loadSessions, devServers, localIP } from '@/lib/server/storage'
|
|
4
4
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
|
+
import { resolveDevServerLaunchDir } from '@/lib/server/devserver-launch'
|
|
6
|
+
import net from 'net'
|
|
7
|
+
|
|
8
|
+
function findFreePort(): Promise<number> {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const server = net.createServer()
|
|
11
|
+
server.listen(0, '0.0.0.0', () => {
|
|
12
|
+
const address = server.address()
|
|
13
|
+
const port = typeof address === 'object' && address ? address.port : 0
|
|
14
|
+
server.close((err) => err ? reject(err) : resolve(port))
|
|
15
|
+
})
|
|
16
|
+
server.on('error', reject)
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildDevArgs(framework: string, port: number): string[] {
|
|
21
|
+
if (framework === 'next') {
|
|
22
|
+
return ['--', '--hostname', '0.0.0.0', '--port', String(port)]
|
|
23
|
+
}
|
|
24
|
+
return ['--', '--host', '0.0.0.0', '--port', String(port)]
|
|
25
|
+
}
|
|
5
26
|
|
|
6
27
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
7
28
|
const { id } = await params
|
|
@@ -17,10 +38,12 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
17
38
|
return NextResponse.json({ running: true, url: ds.url })
|
|
18
39
|
}
|
|
19
40
|
|
|
20
|
-
const
|
|
21
|
-
|
|
41
|
+
const launch = resolveDevServerLaunchDir(session.cwd)
|
|
42
|
+
const port = await findFreePort()
|
|
43
|
+
const proc = spawn('npm', ['run', 'dev', ...buildDevArgs(launch.framework, port)], {
|
|
44
|
+
cwd: launch.launchDir,
|
|
22
45
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
23
|
-
env: { ...process.env, FORCE_COLOR: '0' },
|
|
46
|
+
env: { ...process.env, FORCE_COLOR: '0', PORT: String(port) },
|
|
24
47
|
})
|
|
25
48
|
|
|
26
49
|
let output = ''
|
|
@@ -45,19 +68,37 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
45
68
|
proc.on('close', () => { devServers.delete(id); console.log(`[${id}] dev server stopped`) })
|
|
46
69
|
proc.on('error', () => devServers.delete(id))
|
|
47
70
|
|
|
48
|
-
devServers.set(id, { proc, url: `http://${localIP()}
|
|
49
|
-
console.log(`[${id}] starting dev server in ${session.cwd}`)
|
|
71
|
+
devServers.set(id, { proc, url: `http://${localIP()}:${port}` })
|
|
72
|
+
console.log(`[${id}] starting dev server in ${launch.launchDir} (session cwd=${session.cwd})`)
|
|
50
73
|
|
|
51
74
|
// Wait for URL detection
|
|
52
75
|
await new Promise(resolve => setTimeout(resolve, 4000))
|
|
53
76
|
const ds = devServers.get(id)
|
|
54
|
-
|
|
77
|
+
if (!ds) {
|
|
78
|
+
return NextResponse.json({
|
|
79
|
+
running: false,
|
|
80
|
+
error: 'Dev server exited during startup',
|
|
81
|
+
cwd: launch.launchDir,
|
|
82
|
+
sessionCwd: session.cwd,
|
|
83
|
+
framework: launch.framework,
|
|
84
|
+
output: output.slice(-4000),
|
|
85
|
+
}, { status: 502 })
|
|
86
|
+
}
|
|
87
|
+
return NextResponse.json({
|
|
88
|
+
running: true,
|
|
89
|
+
url: ds.url,
|
|
90
|
+
cwd: launch.launchDir,
|
|
91
|
+
sessionCwd: session.cwd,
|
|
92
|
+
framework: launch.framework,
|
|
93
|
+
})
|
|
55
94
|
|
|
56
95
|
} else if (action === 'stop') {
|
|
57
96
|
if (devServers.has(id)) {
|
|
58
97
|
const ds = devServers.get(id)!
|
|
59
98
|
try { ds.proc.kill('SIGTERM') } catch {}
|
|
60
|
-
|
|
99
|
+
if (typeof ds.proc.pid === 'number') {
|
|
100
|
+
try { process.kill(-ds.proc.pid, 'SIGTERM') } catch {}
|
|
101
|
+
}
|
|
61
102
|
devServers.delete(id)
|
|
62
103
|
}
|
|
63
104
|
return NextResponse.json({ running: false })
|