@swarmclawai/swarmclaw 0.7.2 → 0.7.3
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 +81 -22
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +26 -0
- package/src/app/api/agents/[id]/thread/route.ts +36 -7
- package/src/app/api/agents/route.ts +12 -1
- package/src/app/api/auth/route.ts +76 -7
- package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
- package/src/app/api/chats/[id]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/main-loop/route.ts +7 -88
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +18 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/route.ts +16 -0
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/skills/route.ts +11 -3
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/settings/route.ts +49 -7
- package/src/app/api/tasks/[id]/route.ts +15 -6
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +9 -2
- package/src/cli/index.js +4 -0
- package/src/cli/index.ts +3 -10
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +207 -16
- package/src/components/agents/inspector-panel.tsx +108 -48
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/chat/chat-area.tsx +29 -13
- package/src/components/chat/chat-card.tsx +4 -20
- package/src/components/chat/chat-header.tsx +255 -353
- package/src/components/chat/chat-list.tsx +7 -9
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +3 -1
- package/src/components/chatrooms/chatroom-view.tsx +347 -205
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/layout/app-layout.tsx +383 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +183 -0
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +245 -46
- package/src/components/tasks/approvals-panel.tsx +205 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +205 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +36 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
- package/src/lib/server/chat-execution.ts +250 -61
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +67 -2
- package/src/lib/server/chatroom-helpers.ts +45 -5
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +946 -110
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +188 -9
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/daemon-state.ts +59 -1
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/heartbeat-service.ts +13 -39
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +27 -967
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +5 -6
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +17 -6
- package/src/lib/server/orchestrator.ts +2 -2
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +207 -0
- package/src/lib/server/plugins.ts +822 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/queue.ts +3 -20
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +70 -32
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery.ts +22 -4
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +237 -24
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +56 -1
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +150 -7
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +86 -23
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +36 -3
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
- package/src/lib/server/session-tools/subagent.ts +193 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +896 -100
- package/src/lib/server/storage.ts +226 -7
- package/src/lib/server/stream-agent-chat.ts +46 -21
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -10
- package/src/lib/server/tool-aliases.ts +44 -7
- package/src/lib/server/tool-capability-policy.ts +6 -0
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +7 -0
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +0 -6
- package/src/stores/use-chat-store.ts +31 -2
- package/src/types/index.ts +287 -44
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- package/src/lib/server/session-run-manager.test.ts +0 -26
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ curl -fsSL https://raw.githubusercontent.com/swarmclawai/swarmclaw/main/install.
|
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
The installer resolves the latest stable release tag and installs that version by default.
|
|
102
|
-
To pin a version: `SWARMCLAW_VERSION=v0.7.
|
|
102
|
+
To pin a version: `SWARMCLAW_VERSION=v0.7.3 curl ... | bash`
|
|
103
103
|
|
|
104
104
|
Or run locally from the repo (friendly for non-technical users):
|
|
105
105
|
|
|
@@ -249,6 +249,8 @@ src/
|
|
|
249
249
|
| xAI (Grok) | api.x.ai | Grok 3, Grok 3 Fast, Grok 3 Mini |
|
|
250
250
|
| Fireworks AI | api.fireworks.ai | DeepSeek R1, Llama 3.3 70B, Qwen 3 |
|
|
251
251
|
|
|
252
|
+
If a provider is configured, SwarmClaw can populate the model dropdown from that provider’s advertised model list. For OpenAI this means the selector can auto-fill current OpenAI models, while still allowing users to type a newer or custom model manually if it is not in the fetched list yet.
|
|
253
|
+
|
|
252
254
|
### Local & Remote
|
|
253
255
|
|
|
254
256
|
| Provider | Type | Notes |
|
|
@@ -301,13 +303,14 @@ Agents can use the following tools when enabled:
|
|
|
301
303
|
| Edit File | Search-and-replace editing (exact match required) |
|
|
302
304
|
| Web Search | Search the web via DuckDuckGo HTML scraping |
|
|
303
305
|
| Web Fetch | Fetch and extract text content from URLs (uses cheerio) |
|
|
304
|
-
| CLI Delegation | Delegate complex tasks to Claude Code, Codex CLI, OpenCode CLI, or Gemini CLI |
|
|
305
|
-
| Spawn Subagent | Delegate a sub-task to another agent
|
|
306
|
-
| Browser | Playwright-powered web browsing via MCP
|
|
306
|
+
| CLI Delegation | Delegate complex tasks to Claude Code, Codex CLI, OpenCode CLI, or Gemini CLI, either inline or as a background job handle |
|
|
307
|
+
| Spawn Subagent | Delegate a sub-task to another agent with `status` / `list` / `wait` / `cancel` handles and inherited browser state when needed |
|
|
308
|
+
| Browser | Playwright-powered web browsing via MCP with persistent profiles, structured page reads, form helpers, verification actions, and resumable state |
|
|
307
309
|
| Canvas | Present/hide/snapshot live HTML content in a chat canvas panel |
|
|
308
310
|
| HTTP Request | Make direct API calls with method, headers, body, redirect control, and timeout |
|
|
309
311
|
| Git | Run structured git subcommands (`status`, `diff`, `log`, `add`, `commit`, `push`, etc.) with repo safety checks |
|
|
310
312
|
| Memory | Store and retrieve long-term memories with FTS5 + vector search, file references, image attachments, and linked memory graph traversal |
|
|
313
|
+
| Monitor | Inspect system state and create durable watches over files, endpoints, tasks, webhooks, and page/content changes (`monitor_tool`) |
|
|
311
314
|
| Wallet | Manage an agent-linked Solana wallet (`wallet_tool`) to check balance/address, send SOL (limits + approval), and review transaction history |
|
|
312
315
|
| Image Generation | Generate images from prompts (`generate_image`) via OpenAI, Stability, Replicate, fal.ai, Together, Fireworks, BFL, or custom endpoints; saved to uploads |
|
|
313
316
|
| Email | Send outbound email via SMTP (`email`) with `send`/`status` actions |
|
|
@@ -324,7 +327,7 @@ Agents with platform tools enabled can manage the SwarmClaw instance:
|
|
|
324
327
|
| Manage Agents | List, create, update, delete agents |
|
|
325
328
|
| Manage Tasks | Create and manage task board items with agent assignment |
|
|
326
329
|
| Manage Schedules | Create cron, interval, or one-time scheduled jobs |
|
|
327
|
-
| Reminders | Schedule a conversational wake event in the current chat (`schedule_wake`) |
|
|
330
|
+
| Reminders | Schedule a durable conversational wake event in the current chat (`schedule_wake`) |
|
|
328
331
|
| Manage Skills | List, create, update reusable skill definitions |
|
|
329
332
|
| Manage Documents | Upload/search/get/delete indexed docs for lightweight RAG workflows |
|
|
330
333
|
| Manage Webhooks | Register external webhook endpoints that trigger agent chats |
|
|
@@ -440,9 +443,9 @@ Agents and chats can have **fallback credentials**. If the primary API key gets
|
|
|
440
443
|
|
|
441
444
|
## Plugin System
|
|
442
445
|
|
|
443
|
-
SwarmClaw features a
|
|
446
|
+
SwarmClaw features a modular plugin system for agent capabilities, UI extensions, provider/connectors, and post-turn automation. It supports the native SwarmClaw hook/tool format and the **OpenClaw** register/activate formats.
|
|
444
447
|
|
|
445
|
-
Plugins can be managed in **Settings → Plugins** and installed via the Marketplace, URL, or by dropping `.js` files into `data/plugins/`.
|
|
448
|
+
Plugins can be managed in **Settings → Plugins** and installed via the Marketplace, HTTPS URL, or by dropping `.js` / `.mjs` files into `data/plugins/`.
|
|
446
449
|
|
|
447
450
|
Docs:
|
|
448
451
|
- Full docs: https://swarmclaw.ai/docs
|
|
@@ -453,17 +456,30 @@ Docs:
|
|
|
453
456
|
Unlike standard tool systems, SwarmClaw plugins can modify the application itself:
|
|
454
457
|
|
|
455
458
|
- **Agent Tools**: Define custom tools that agents can autonomously discover and use.
|
|
456
|
-
- **Lifecycle Hooks**: Intercept events like `beforeAgentStart`, `afterToolExec`, and `onMessage`.
|
|
459
|
+
- **Lifecycle Hooks**: Intercept events like `beforeAgentStart`, `beforeToolExec`, `afterToolExec`, `afterChatTurn`, and `onMessage`.
|
|
457
460
|
- **UI Extensions**:
|
|
458
461
|
- `sidebarItems`: Inject new navigation links into the main sidebar.
|
|
459
462
|
- `headerWidgets`: Add status badges or indicators to the chat header (e.g., Wallet Balance).
|
|
460
463
|
- `chatInputActions`: Add custom action buttons next to the chat input (e.g., "Quick Scan").
|
|
461
464
|
- `plugin-ui` Messages: Render rich, interactive React cards in the chat stream.
|
|
462
465
|
- **Deep Chat Hooks**:
|
|
463
|
-
- `transformInboundMessage`: Modify user messages before they reach the agent.
|
|
464
|
-
- `transformOutboundMessage`: Modify agent responses before they are
|
|
466
|
+
- `transformInboundMessage`: Modify user messages before they reach the agent runtime.
|
|
467
|
+
- `transformOutboundMessage`: Modify agent responses before they are persisted or displayed.
|
|
468
|
+
- `beforeToolExec`: Can rewrite tool input before the selected tool executes.
|
|
465
469
|
- **Custom Providers**: Add new LLM backends (e.g., a specialized local model or a new API).
|
|
466
470
|
- **Custom Connectors**: Build new chat platform bridges (e.g., a proprietary internal messenger).
|
|
471
|
+
- **Per-plugin Settings**: Declare `ui.settingsFields` and read/write them via `/api/plugins/settings`. Fields marked `type: 'secret'` are encrypted at rest.
|
|
472
|
+
|
|
473
|
+
### Canonical Plugin IDs
|
|
474
|
+
|
|
475
|
+
Built-in capabilities now resolve to a single canonical plugin family ID across agent configs, policy rules, approvals, and the Plugins UI. Legacy aliases still work, but the canonical IDs are what you should document and store going forward.
|
|
476
|
+
|
|
477
|
+
- `manage_sessions` instead of `session_info`
|
|
478
|
+
- `manage_connectors` instead of `connectors`
|
|
479
|
+
- `http_request` instead of `http`
|
|
480
|
+
- `spawn_subagent` instead of `subagent`
|
|
481
|
+
- `manage_chatrooms` instead of `chatroom`
|
|
482
|
+
- `schedule_wake` instead of `schedule`
|
|
467
483
|
|
|
468
484
|
### Autonomous Capability Discovery
|
|
469
485
|
|
|
@@ -494,12 +510,53 @@ module.exports = {
|
|
|
494
510
|
};
|
|
495
511
|
```
|
|
496
512
|
|
|
513
|
+
Hook signatures of note:
|
|
514
|
+
|
|
515
|
+
- `beforeToolExec({ toolName, input })` may return a replacement input object.
|
|
516
|
+
- `afterToolExec({ session, toolName, input, output })` observes completed tool executions.
|
|
517
|
+
- `transformInboundMessage({ session, text })` and `transformOutboundMessage({ session, text })` run sequentially across enabled plugins.
|
|
518
|
+
|
|
519
|
+
### Building Plugins
|
|
520
|
+
|
|
521
|
+
The shortest reliable workflow for a new plugin:
|
|
522
|
+
|
|
523
|
+
1. Create a focused `.js` or `.mjs` file under `data/plugins/`.
|
|
524
|
+
2. Export `name`, `description`, any `hooks`, and optional `tools` / `ui.settingsFields`.
|
|
525
|
+
3. Keep tool outputs structured when the agent needs to chain them into later steps.
|
|
526
|
+
4. Use `settingsFields` for secrets or environment-specific values instead of hardcoding them.
|
|
527
|
+
5. If the plugin needs third-party npm packages, attach a `package.json` manifest so SwarmClaw can manage it in a per-plugin workspace.
|
|
528
|
+
6. Enable the plugin in **Settings → Plugins** and test both the tool path and any hook behavior.
|
|
529
|
+
7. If you host it remotely, install from a stable HTTPS URL so SwarmClaw can record source metadata and update it later.
|
|
530
|
+
|
|
531
|
+
A fuller step-by-step walkthrough lives at https://swarmclaw.ai/docs/plugin-tutorial.
|
|
532
|
+
|
|
497
533
|
### Lifecycle Management
|
|
498
534
|
|
|
499
535
|
- **Versioning**: All plugins support semantic versioning (e.g., `v1.2.3`).
|
|
500
|
-
- **Updates**:
|
|
501
|
-
- **Hot
|
|
502
|
-
- **
|
|
536
|
+
- **Updates**: External plugins installed from a recorded source URL can be updated individually or in bulk via the Plugins manager. Built-ins update with the app release.
|
|
537
|
+
- **Hot Reload**: Changes inside `data/plugins/` invalidate the plugin registry automatically, and installs/updates trigger an immediate reload.
|
|
538
|
+
- **Plugin Workspaces**: Plugins with a manifest are managed under `data/plugins/.workspaces/<plugin>/`, and dependency installs can be triggered from the plugin detail sheet or `POST /api/plugins/dependencies`.
|
|
539
|
+
- **Stability Guardrails**: Consecutive plugin failures are tracked in `data/plugin-failures.json`; failing external plugins are auto-disabled, a warning notification is emitted in-app, and users can re-enable manually from the Plugins manager.
|
|
540
|
+
- **Source Metadata**: Marketplace/URL installs record the normalized source URL and source hash in `data/plugins.json`.
|
|
541
|
+
- **Settings Safety**: Plugin settings are validated against declared `settingsFields`; unknown keys are ignored and `secret` values are stored encrypted.
|
|
542
|
+
|
|
543
|
+
### Browser, Watch, and Delegation Upgrades
|
|
544
|
+
|
|
545
|
+
- **Persistent Browser Profiles**: The built-in `browser` plugin now keeps a reusable profile per chat/session, and subagents inherit the parent profile by default. Profiles live under `~/.swarmclaw/browser-profiles` unless you override `BROWSER_PROFILES_DIR`, so cookies, storage, and authenticated state survive longer-running work without polluting the project tree. Browser state is exposed at `GET /api/chats/[id]/browser`.
|
|
546
|
+
- **Higher-Level Browser Actions**: In addition to raw Playwright-style actions, `browser` supports workflow-oriented actions such as `read_page`, `extract_links`, `extract_form_fields`, `extract_table`, `fill_form`, `submit_form`, `scroll_until`, `download_file`, `complete_web_task`, `verify_text`, `verify_element`, `verify_list`, `verify_value`, `profile`, and `reset_profile`.
|
|
547
|
+
- **Structured Browser State**: Browser sessions persist recent observations, tabs, artifacts (screenshots / PDFs / downloads), current URL, and last errors in `browser_sessions`, which makes autonomous browser tasks easier to resume, inspect, and hand off across turns.
|
|
548
|
+
- **Durable Watches**: `schedule_wake` now uses persisted watch jobs instead of an in-memory timer, and `monitor_tool` supports `create_watch`, `list_watches`, `get_watch`, and `cancel_watch` across `time`, `http`, `file`, `task`, `webhook`, and `page` conditions. The same watch system also powers the new `mailbox`, session-mailbox, and approval waits used by human-loop flows. Watches support common checks like status/status sets, regex or text matches, content changes, existence checks, inbound mailbox correlation IDs, and webhook event filters.
|
|
549
|
+
- **Long-Running Delegation Handles**: `delegate` and `spawn_subagent` support handle-based flows instead of only synchronous final text. Use `background=true` or `waitForCompletion=false` to launch long-running work, then inspect or stop it with `action=status|list|wait|cancel`.
|
|
550
|
+
- **Delegation Job Persistence**: Delegate and subagent runs are recorded in `delegation_jobs` with checkpoints, backend/session metadata, resume IDs, child session IDs, and terminal-status recovery after daemon restarts. Late completions no longer overwrite cancelled jobs.
|
|
551
|
+
|
|
552
|
+
### New Primitive Plugins
|
|
553
|
+
|
|
554
|
+
- **Mailbox / Inbox Automation**: The built-in `mailbox` plugin adds IMAP/SMTP inbox access with `status`, `list_messages`, `list_threads`, `search_messages`, `read_message`, `download_attachment`, `reply`, and `wait_for_email`. It supports durable inbound-email waits and reuses plugin settings / connector config where possible. Configure it in **Settings → Plugins** with `imapHost`, `smtpHost`, `user`, `password`, and optional reply defaults.
|
|
555
|
+
- **Human-in-the-Loop Requests**: The built-in `ask_human` plugin provides `request_input`, `request_approval`, `wait_for_reply`, `wait_for_approval`, `list_mailbox`, `ack_mailbox`, and `status`. It is backed by session mailbox envelopes plus approval records so agents can pause and resume on real human responses instead of polling ad hoc state.
|
|
556
|
+
- **Document Parsing / OCR**: The built-in `document` plugin adds `read`, `metadata`, `ocr`, `extract_tables`, `store`, `list`, `search`, `get`, and `delete`. It uses the shared document extraction helpers for PDFs, Office docs, OCR-able images, HTML, CSV/TSV/XLSX, ZIP inspection, and plain text files.
|
|
557
|
+
- **Schema-Driven Extraction**: The built-in `extract` plugin adds `extract_structured` and `summarize`, using the current session model/provider to turn raw text or local files into validated JSON objects. This is the primitive to combine with browser / document / crawl output when an agent needs structured records instead of prose.
|
|
558
|
+
- **Tabular Data Operations**: The built-in `table` plugin adds `read`, `load_csv`, `load_xlsx`, `summarize`, `filter`, `sort`, `group`, `pivot`, `dedupe`, `join`, and `write`. It operates on CSV, TSV, JSON array-of-objects, or XLSX inputs without forcing agents to drop into shell or Python for basic spreadsheet work, and transformed tables can be persisted with `outputPath` / `saveTo`.
|
|
559
|
+
- **Multi-Page Crawling**: The built-in `crawl` plugin adds `crawl_site`, `follow_pagination`, `extract_sitemap`, `dedupe_pages`, and `batch_extract`. It handles BFS-style same-origin site traversal, accepts either a fresh start URL or an explicit page list, and can hand the aggregate page set directly into structured extraction for research-heavy autonomous tasks.
|
|
503
560
|
|
|
504
561
|
## Deploy to a VPS
|
|
505
562
|
|
|
@@ -600,7 +657,7 @@ npm run update:easy # safe update helper for local installs
|
|
|
600
657
|
SwarmClaw uses tag-based releases (`vX.Y.Z`) as the stable channel.
|
|
601
658
|
|
|
602
659
|
```bash
|
|
603
|
-
# example patch release (v0.7.
|
|
660
|
+
# example patch release (v0.7.3 style)
|
|
604
661
|
npm version patch
|
|
605
662
|
git push origin main --follow-tags
|
|
606
663
|
```
|
|
@@ -610,16 +667,18 @@ On `v*` tags, GitHub Actions will:
|
|
|
610
667
|
2. Create a GitHub Release
|
|
611
668
|
3. Build and publish Docker images to `ghcr.io/swarmclawai/swarmclaw` (`:vX.Y.Z`, `:latest`, `:sha-*`)
|
|
612
669
|
|
|
613
|
-
#### v0.7.
|
|
670
|
+
#### v0.7.3 Release Readiness Notes
|
|
614
671
|
|
|
615
|
-
Before shipping `v0.7.
|
|
672
|
+
Before shipping `v0.7.3`, confirm the following user-facing changes are reflected in docs:
|
|
616
673
|
|
|
617
|
-
1.
|
|
618
|
-
2.
|
|
619
|
-
3. Plugin
|
|
620
|
-
4.
|
|
621
|
-
5.
|
|
622
|
-
6.
|
|
674
|
+
1. New primitive plugins are documented in README and site docs: `mailbox`, `ask_human`, `document`, `extract`, `table`, and `crawl`.
|
|
675
|
+
2. Browser persistence, higher-level browser actions, durable watch jobs, and delegation handles are covered in docs.
|
|
676
|
+
3. Plugin docs mention canonical built-in IDs, source-backed installs/updates, hot reload, and encrypted `secret` settings.
|
|
677
|
+
4. Plugin docs cover per-plugin workspaces and dependency installs for plugins that ship with a `package.json` manifest.
|
|
678
|
+
5. Connector docs and CLI docs cover the connector doctor diagnostics endpoints/commands.
|
|
679
|
+
6. The plugin tutorial reflects the current hook behavior, including `beforeToolExec` input rewriting and `settingsFields`.
|
|
680
|
+
7. Site install/version strings are updated to `v0.7.3`, including the release notes index, install snippets, and sidebar footer.
|
|
681
|
+
8. Provider docs mention that configured OpenAI models can populate the model dropdown while still allowing custom/manual entries.
|
|
623
682
|
|
|
624
683
|
## CLI
|
|
625
684
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
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
|
"repository": {
|
|
@@ -11,17 +11,43 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
11
11
|
const body = await req.json()
|
|
12
12
|
const result = mutateItem(ops, id, (agent) => {
|
|
13
13
|
Object.assign(agent, body, { updatedAt: Date.now() })
|
|
14
|
+
if (body.platformAssignScope === 'all' || body.platformAssignScope === 'self') {
|
|
15
|
+
agent.platformAssignScope = body.platformAssignScope
|
|
16
|
+
agent.isOrchestrator = body.platformAssignScope === 'all'
|
|
17
|
+
} else if (agent.platformAssignScope === 'all' || agent.platformAssignScope === 'self') {
|
|
18
|
+
agent.isOrchestrator = agent.platformAssignScope === 'all'
|
|
19
|
+
}
|
|
14
20
|
if (body.apiEndpoint !== undefined) {
|
|
15
21
|
agent.apiEndpoint = normalizeProviderEndpoint(
|
|
16
22
|
body.provider || agent.provider,
|
|
17
23
|
body.apiEndpoint,
|
|
18
24
|
)
|
|
19
25
|
}
|
|
26
|
+
delete (agent as Record<string, unknown>).isOrchestrator
|
|
27
|
+
agent.isOrchestrator = agent.platformAssignScope === 'all'
|
|
20
28
|
delete (agent as Record<string, unknown>).id
|
|
21
29
|
agent.id = id
|
|
22
30
|
return agent
|
|
23
31
|
})
|
|
24
32
|
if (!result) return notFound()
|
|
33
|
+
|
|
34
|
+
if (result.threadSessionId) {
|
|
35
|
+
const sessions = loadSessions()
|
|
36
|
+
const shortcut = sessions[result.threadSessionId]
|
|
37
|
+
if (shortcut) {
|
|
38
|
+
let changed = false
|
|
39
|
+
if (shortcut.name !== result.name) {
|
|
40
|
+
shortcut.name = result.name
|
|
41
|
+
changed = true
|
|
42
|
+
}
|
|
43
|
+
if (shortcut.shortcutForAgentId !== id) {
|
|
44
|
+
shortcut.shortcutForAgentId = id
|
|
45
|
+
changed = true
|
|
46
|
+
}
|
|
47
|
+
if (changed) saveSessions(sessions)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
25
51
|
logActivity({ entityType: 'agent', entityId: id, action: 'updated', actor: 'user', summary: `Agent updated: "${result.name}"` })
|
|
26
52
|
return NextResponse.json(result)
|
|
27
53
|
}
|
|
@@ -15,28 +15,57 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
15
15
|
const user = body.user || 'default'
|
|
16
16
|
const sessions = loadSessions()
|
|
17
17
|
|
|
18
|
-
// If agent already has a
|
|
18
|
+
// If the agent already has a shortcut chat session, return it.
|
|
19
19
|
if (agent.threadSessionId && sessions[agent.threadSessionId]) {
|
|
20
|
-
|
|
20
|
+
const existing = sessions[agent.threadSessionId] as Record<string, unknown>
|
|
21
|
+
let changed = false
|
|
22
|
+
if (existing.shortcutForAgentId !== agentId) {
|
|
23
|
+
existing.shortcutForAgentId = agentId
|
|
24
|
+
changed = true
|
|
25
|
+
}
|
|
26
|
+
if (existing.name !== agent.name) {
|
|
27
|
+
existing.name = agent.name
|
|
28
|
+
changed = true
|
|
29
|
+
}
|
|
30
|
+
if (changed) saveSessions(sessions)
|
|
31
|
+
return NextResponse.json(existing)
|
|
21
32
|
}
|
|
22
33
|
|
|
23
|
-
//
|
|
34
|
+
// Legacy fallback for older shortcut sessions that were named using the
|
|
35
|
+
// old agent-thread convention before the explicit link was persisted.
|
|
24
36
|
const existing = Object.values(sessions).find(
|
|
25
|
-
(s: Record<string, unknown>) =>
|
|
37
|
+
(s: Record<string, unknown>) =>
|
|
38
|
+
(
|
|
39
|
+
s.shortcutForAgentId === agentId
|
|
40
|
+
|| s.name === `agent-thread:${agentId}`
|
|
41
|
+
)
|
|
42
|
+
&& s.user === user
|
|
26
43
|
)
|
|
27
44
|
if (existing) {
|
|
28
45
|
agent.threadSessionId = (existing as Record<string, unknown>).id as string
|
|
29
46
|
agent.updatedAt = Date.now()
|
|
30
47
|
saveAgents(agents)
|
|
48
|
+
let changed = false
|
|
49
|
+
const existingRecord = existing as Record<string, unknown>
|
|
50
|
+
if (existingRecord.shortcutForAgentId !== agentId) {
|
|
51
|
+
existingRecord.shortcutForAgentId = agentId
|
|
52
|
+
changed = true
|
|
53
|
+
}
|
|
54
|
+
if (existingRecord.name !== agent.name) {
|
|
55
|
+
existingRecord.name = agent.name
|
|
56
|
+
changed = true
|
|
57
|
+
}
|
|
58
|
+
if (changed) saveSessions(sessions)
|
|
31
59
|
return NextResponse.json(existing)
|
|
32
60
|
}
|
|
33
61
|
|
|
34
|
-
// Create a new
|
|
35
|
-
const sessionId = `agent-
|
|
62
|
+
// Create a new shortcut chat session for this agent.
|
|
63
|
+
const sessionId = `agent-chat-${agentId}-${genId()}`
|
|
36
64
|
const now = Date.now()
|
|
37
65
|
const session = {
|
|
38
66
|
id: sessionId,
|
|
39
|
-
name:
|
|
67
|
+
name: agent.name,
|
|
68
|
+
shortcutForAgentId: agentId,
|
|
40
69
|
cwd: WORKSPACE_DIR,
|
|
41
70
|
user: user,
|
|
42
71
|
provider: agent.provider,
|
|
@@ -13,6 +13,9 @@ export async function GET(req: Request) {
|
|
|
13
13
|
const agents = loadAgents()
|
|
14
14
|
const sessions = loadSessions()
|
|
15
15
|
const usage = loadUsage()
|
|
16
|
+
for (const agent of Object.values(agents)) {
|
|
17
|
+
agent.isOrchestrator = agent.platformAssignScope === 'all'
|
|
18
|
+
}
|
|
16
19
|
// Enrich agents that have spend limits with current spend windows
|
|
17
20
|
for (const agent of Object.values(agents)) {
|
|
18
21
|
if (
|
|
@@ -48,6 +51,7 @@ export async function POST(req: Request) {
|
|
|
48
51
|
const id = genId()
|
|
49
52
|
const now = Date.now()
|
|
50
53
|
const agents = loadAgents()
|
|
54
|
+
const platformAssignScope = body.platformAssignScope
|
|
51
55
|
agents[id] = {
|
|
52
56
|
id,
|
|
53
57
|
name: body.name,
|
|
@@ -57,7 +61,8 @@ export async function POST(req: Request) {
|
|
|
57
61
|
model: body.model,
|
|
58
62
|
credentialId: body.credentialId,
|
|
59
63
|
apiEndpoint: normalizeProviderEndpoint(body.provider, body.apiEndpoint || null),
|
|
60
|
-
isOrchestrator:
|
|
64
|
+
isOrchestrator: platformAssignScope === 'all',
|
|
65
|
+
platformAssignScope,
|
|
61
66
|
subAgentIds: body.subAgentIds,
|
|
62
67
|
plugins: body.plugins?.length ? body.plugins : (body.tools || []),
|
|
63
68
|
capabilities: body.capabilities,
|
|
@@ -68,6 +73,12 @@ export async function POST(req: Request) {
|
|
|
68
73
|
hourlyBudget: body.hourlyBudget ?? null,
|
|
69
74
|
budgetAction: body.budgetAction || 'warn',
|
|
70
75
|
soul: body.soul || undefined,
|
|
76
|
+
identityState: body.identityState ?? null,
|
|
77
|
+
sessionResetMode: body.sessionResetMode ?? null,
|
|
78
|
+
sessionIdleTimeoutSec: body.sessionIdleTimeoutSec ?? null,
|
|
79
|
+
sessionMaxAgeSec: body.sessionMaxAgeSec ?? null,
|
|
80
|
+
sessionDailyResetAt: body.sessionDailyResetAt ?? null,
|
|
81
|
+
sessionResetTimezone: body.sessionResetTimezone ?? null,
|
|
71
82
|
createdAt: now,
|
|
72
83
|
updatedAt: now,
|
|
73
84
|
}
|
|
@@ -1,27 +1,96 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { validateAccessKey, getAccessKey, isFirstTimeSetup, markSetupComplete } from '@/lib/server/storage'
|
|
3
3
|
import { ensureDaemonStarted } from '@/lib/server/daemon-state'
|
|
4
|
+
import { AUTH_COOKIE_NAME, getCookieValue } from '@/lib/auth'
|
|
4
5
|
export const dynamic = 'force-dynamic'
|
|
5
6
|
|
|
7
|
+
interface AuthAttemptEntry {
|
|
8
|
+
count: number
|
|
9
|
+
lockedUntil: number
|
|
10
|
+
}
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
const authRateLimitMap = (
|
|
13
|
+
(globalThis as Record<string, unknown>).__swarmclaw_auth_rate_limit__ ??= new Map()
|
|
14
|
+
) as Map<string, AuthAttemptEntry>
|
|
15
|
+
|
|
16
|
+
const MAX_ATTEMPTS = 5
|
|
17
|
+
const LOCKOUT_MS = 15 * 60 * 1000
|
|
18
|
+
|
|
19
|
+
function getClientIp(req: Request): string {
|
|
20
|
+
const forwarded = req.headers.get('x-forwarded-for')
|
|
21
|
+
if (forwarded) {
|
|
22
|
+
const first = forwarded.split(',')[0]?.trim()
|
|
23
|
+
if (first) return first
|
|
11
24
|
}
|
|
12
|
-
|
|
25
|
+
const realIp = req.headers.get('x-real-ip')?.trim()
|
|
26
|
+
return realIp || 'unknown'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function clearAuthCookie(response: NextResponse): NextResponse {
|
|
30
|
+
response.cookies.set(AUTH_COOKIE_NAME, '', {
|
|
31
|
+
httpOnly: true,
|
|
32
|
+
sameSite: 'lax',
|
|
33
|
+
secure: false,
|
|
34
|
+
path: '/',
|
|
35
|
+
maxAge: 0,
|
|
36
|
+
})
|
|
37
|
+
return response
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function setAuthCookie(response: NextResponse, req: Request, key: string): NextResponse {
|
|
41
|
+
response.cookies.set(AUTH_COOKIE_NAME, key, {
|
|
42
|
+
httpOnly: true,
|
|
43
|
+
sameSite: 'lax',
|
|
44
|
+
secure: new URL(req.url).protocol === 'https:',
|
|
45
|
+
path: '/',
|
|
46
|
+
maxAge: 60 * 60 * 24 * 30,
|
|
47
|
+
})
|
|
48
|
+
return response
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** GET /api/auth — returns setup state and whether the auth cookie is currently valid */
|
|
52
|
+
export async function GET(req: Request) {
|
|
53
|
+
const cookieKey = getCookieValue(req.headers.get('cookie'), AUTH_COOKIE_NAME)
|
|
54
|
+
return NextResponse.json({
|
|
55
|
+
firstTime: isFirstTimeSetup(),
|
|
56
|
+
authenticated: !!cookieKey && validateAccessKey(cookieKey),
|
|
57
|
+
})
|
|
13
58
|
}
|
|
14
59
|
|
|
15
60
|
/** POST /api/auth — validate an access key */
|
|
16
61
|
export async function POST(req: Request) {
|
|
62
|
+
const clientIp = getClientIp(req)
|
|
63
|
+
const entry = authRateLimitMap.get(clientIp)
|
|
64
|
+
if (entry && entry.lockedUntil > Date.now()) {
|
|
65
|
+
const retryAfter = Math.ceil((entry.lockedUntil - Date.now()) / 1000)
|
|
66
|
+
return clearAuthCookie(NextResponse.json(
|
|
67
|
+
{ error: 'Too many failed attempts. Try again later.', retryAfter },
|
|
68
|
+
{ status: 429, headers: { 'Retry-After': String(retryAfter) } },
|
|
69
|
+
))
|
|
70
|
+
}
|
|
71
|
+
|
|
17
72
|
const { key } = await req.json()
|
|
18
73
|
if (!key || !validateAccessKey(key)) {
|
|
19
|
-
|
|
74
|
+
const current = authRateLimitMap.get(clientIp) ?? { count: 0, lockedUntil: 0 }
|
|
75
|
+
current.count += 1
|
|
76
|
+
if (current.count >= MAX_ATTEMPTS) {
|
|
77
|
+
current.lockedUntil = Date.now() + LOCKOUT_MS
|
|
78
|
+
}
|
|
79
|
+
authRateLimitMap.set(clientIp, current)
|
|
80
|
+
return clearAuthCookie(NextResponse.json(
|
|
81
|
+
{ error: 'Invalid access key' },
|
|
82
|
+
{
|
|
83
|
+
status: 401,
|
|
84
|
+
headers: { 'X-RateLimit-Remaining': String(Math.max(0, MAX_ATTEMPTS - current.count)) },
|
|
85
|
+
},
|
|
86
|
+
))
|
|
20
87
|
}
|
|
88
|
+
|
|
89
|
+
authRateLimitMap.delete(clientIp)
|
|
21
90
|
// If this was first-time setup, mark it as claimed
|
|
22
91
|
if (isFirstTimeSetup()) {
|
|
23
92
|
markSetupComplete()
|
|
24
93
|
}
|
|
25
94
|
ensureDaemonStarted('api/auth:post')
|
|
26
|
-
return NextResponse.json({ ok: true })
|
|
95
|
+
return setAuthCookie(NextResponse.json({ ok: true }), req, key)
|
|
27
96
|
}
|
|
@@ -8,6 +8,8 @@ import { getProvider } from '@/lib/providers'
|
|
|
8
8
|
import {
|
|
9
9
|
resolveApiKey,
|
|
10
10
|
parseMentions,
|
|
11
|
+
resolveReplyTargetAgentId,
|
|
12
|
+
resolveAgentApiEndpoint,
|
|
11
13
|
compactChatroomMessages,
|
|
12
14
|
buildChatroomSystemPrompt,
|
|
13
15
|
buildSyntheticSession,
|
|
@@ -50,7 +52,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
50
52
|
|
|
51
53
|
// Persist incoming message
|
|
52
54
|
const senderName = senderId === 'user' ? 'You' : (agents[senderId]?.name || senderId)
|
|
53
|
-
|
|
55
|
+
const replyTargetAgentId = resolveReplyTargetAgentId(replyToId, chatroom.messages, chatroom.agentIds)
|
|
56
|
+
let mentions = parseMentions(text, agents, chatroom.agentIds, { replyTargetAgentId })
|
|
54
57
|
// Routing rules: if no explicit mentions, evaluate keyword/capability rules
|
|
55
58
|
if (mentions.length === 0 && chatroom.routingRules?.length) {
|
|
56
59
|
const agentList = chatroom.agentIds.map((aid) => agents[aid]).filter(Boolean)
|
|
@@ -149,13 +152,14 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
149
152
|
// Pre-flight: check if the agent's provider is usable before attempting to stream
|
|
150
153
|
const providerInfo = getProvider(agent.provider)
|
|
151
154
|
const apiKey = resolveApiKey(agent.credentialId)
|
|
155
|
+
const resolvedEndpoint = resolveAgentApiEndpoint(agent)
|
|
152
156
|
if (providerInfo?.requiresApiKey && !apiKey) {
|
|
153
157
|
writeEvent({ t: 'cr_agent_start', agentId: agent.id, agentName: agent.name })
|
|
154
158
|
writeEvent({ t: 'err', text: `${agent.name} has no API credentials configured`, agentId: agent.id, agentName: agent.name })
|
|
155
159
|
writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
|
|
156
160
|
return []
|
|
157
161
|
}
|
|
158
|
-
if (providerInfo?.requiresEndpoint && !
|
|
162
|
+
if (providerInfo?.requiresEndpoint && !resolvedEndpoint) {
|
|
159
163
|
writeEvent({ t: 'cr_agent_start', agentId: agent.id, agentName: agent.name })
|
|
160
164
|
writeEvent({ t: 'err', text: `${agent.name} has no endpoint configured`, agentId: agent.id, agentName: agent.name })
|
|
161
165
|
writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
|
|
@@ -174,6 +178,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
const syntheticSession = buildSyntheticSession(agent, id)
|
|
181
|
+
syntheticSession.apiEndpoint = resolvedEndpoint
|
|
177
182
|
const agentSystemPrompt = buildAgentSystemPromptForChatroom(agent)
|
|
178
183
|
const chatroomContext = buildChatroomSystemPrompt(freshChatroom, agents, agent.id)
|
|
179
184
|
const fullSystemPrompt = [agentSystemPrompt, chatroomContext].filter(Boolean).join('\n\n')
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { hasActiveBrowser, cleanupSessionBrowser } from '@/lib/server/session-tools'
|
|
3
|
+
import { loadBrowserSessionRecord } from '@/lib/server/browser-state'
|
|
3
4
|
|
|
4
5
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
5
6
|
const { id } = await params
|
|
6
|
-
return NextResponse.json({
|
|
7
|
+
return NextResponse.json({
|
|
8
|
+
active: hasActiveBrowser(id),
|
|
9
|
+
state: loadBrowserSessionRecord(id),
|
|
10
|
+
})
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -49,7 +49,9 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
49
49
|
mode: queueMode,
|
|
50
50
|
onEvent: (ev) => writeEvent(ev as unknown as Record<string, unknown>),
|
|
51
51
|
replyToId,
|
|
52
|
-
|
|
52
|
+
// Keep user-initiated runs alive even if the SSE transport drops so
|
|
53
|
+
// long-lived tasks can finish and be observed later via polling/history.
|
|
54
|
+
callerSignal: internal ? req.signal : undefined,
|
|
53
55
|
})
|
|
54
56
|
abortRun = run.abort
|
|
55
57
|
|
|
@@ -89,8 +91,10 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
89
91
|
})
|
|
90
92
|
},
|
|
91
93
|
cancel() {
|
|
92
|
-
// Client disconnected
|
|
93
|
-
|
|
94
|
+
// Client disconnected. User-facing runs continue in the background so
|
|
95
|
+
// they can persist results even when the transport drops. Explicit stop
|
|
96
|
+
// controls still cancel the run through the session run manager.
|
|
97
|
+
if (internal) abortRun?.()
|
|
94
98
|
},
|
|
95
99
|
})
|
|
96
100
|
|
|
@@ -1,94 +1,13 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { enqueueSessionRun } from '@/lib/server/session-run-manager'
|
|
3
|
-
import { loadSessions } from '@/lib/server/storage'
|
|
4
|
-
import {
|
|
5
|
-
buildMainLoopHeartbeatPrompt,
|
|
6
|
-
getMainLoopStateForSession,
|
|
7
|
-
isMainSession,
|
|
8
|
-
pushMainLoopEventToMainSessions,
|
|
9
|
-
setMainLoopStateForSession,
|
|
10
|
-
} from '@/lib/server/main-agent-loop'
|
|
11
2
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const sessions = loadSessions()
|
|
15
|
-
const session = sessions[id]
|
|
16
|
-
if (!session) return new NextResponse('Session not found', { status: 404 })
|
|
17
|
-
if (!isMainSession(session)) return new NextResponse('Main-loop controls only apply to agent thread sessions', { status: 400 })
|
|
18
|
-
const state = getMainLoopStateForSession(id)
|
|
19
|
-
return NextResponse.json({ sessionId: id, state })
|
|
3
|
+
function retiredResponse() {
|
|
4
|
+
return new NextResponse('Mission controls are no longer supported.', { status: 410 })
|
|
20
5
|
}
|
|
21
6
|
|
|
22
|
-
export async function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const action = typeof body.action === 'string' ? body.action.trim() : ''
|
|
26
|
-
|
|
27
|
-
const sessions = loadSessions()
|
|
28
|
-
const session = sessions[id]
|
|
29
|
-
if (!session) return new NextResponse('Session not found', { status: 404 })
|
|
30
|
-
if (!isMainSession(session)) return new NextResponse('Main-loop controls only apply to agent thread sessions', { status: 400 })
|
|
31
|
-
|
|
32
|
-
if (action === 'pause') {
|
|
33
|
-
const state = setMainLoopStateForSession(id, { paused: true })
|
|
34
|
-
return NextResponse.json({ ok: true, action, state })
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (action === 'resume') {
|
|
38
|
-
const state = setMainLoopStateForSession(id, { paused: false })
|
|
39
|
-
return NextResponse.json({ ok: true, action, state })
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (action === 'set_goal') {
|
|
43
|
-
const goal = typeof body.goal === 'string' ? body.goal.trim() : ''
|
|
44
|
-
if (!goal) return new NextResponse('goal is required for set_goal', { status: 400 })
|
|
45
|
-
const state = setMainLoopStateForSession(id, {
|
|
46
|
-
goal,
|
|
47
|
-
status: 'progress',
|
|
48
|
-
paused: false,
|
|
49
|
-
followupChainCount: 0,
|
|
50
|
-
})
|
|
51
|
-
pushMainLoopEventToMainSessions({
|
|
52
|
-
type: 'operator_goal',
|
|
53
|
-
text: `Operator set mission goal: ${goal}`,
|
|
54
|
-
user: session.user || null,
|
|
55
|
-
})
|
|
56
|
-
return NextResponse.json({ ok: true, action, state })
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (action === 'set_mode') {
|
|
60
|
-
const mode = body.mode === 'assist' ? 'assist' : body.mode === 'autonomous' ? 'autonomous' : null
|
|
61
|
-
if (!mode) return new NextResponse('mode must be "assist" or "autonomous"', { status: 400 })
|
|
62
|
-
const state = setMainLoopStateForSession(id, { autonomyMode: mode })
|
|
63
|
-
return NextResponse.json({ ok: true, action, state })
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (action === 'clear_events') {
|
|
67
|
-
const state = setMainLoopStateForSession(id, { pendingEvents: [] })
|
|
68
|
-
return NextResponse.json({ ok: true, action, state })
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (action === 'nudge') {
|
|
72
|
-
const state = getMainLoopStateForSession(id)
|
|
73
|
-
if (state?.paused) {
|
|
74
|
-
return new NextResponse('Mission loop is paused; resume first', { status: 409 })
|
|
75
|
-
}
|
|
76
|
-
const note = typeof body.note === 'string' ? body.note.trim() : ''
|
|
77
|
-
const prompt = buildMainLoopHeartbeatPrompt(
|
|
78
|
-
session,
|
|
79
|
-
'Operator requested manual nudge: execute one concrete next step now and report concise progress.',
|
|
80
|
-
)
|
|
81
|
-
const message = note ? `${prompt}\nOperator note: ${note.slice(0, 500)}` : prompt
|
|
82
|
-
const run = enqueueSessionRun({
|
|
83
|
-
sessionId: id,
|
|
84
|
-
message,
|
|
85
|
-
internal: true,
|
|
86
|
-
source: 'mission-control',
|
|
87
|
-
mode: 'collect',
|
|
88
|
-
dedupeKey: `mission-control:nudge:${id}`,
|
|
89
|
-
})
|
|
90
|
-
return NextResponse.json({ ok: true, action, runId: run.runId, position: run.position, deduped: run.deduped || false, state })
|
|
91
|
-
}
|
|
7
|
+
export async function GET() {
|
|
8
|
+
return retiredResponse()
|
|
9
|
+
}
|
|
92
10
|
|
|
93
|
-
|
|
11
|
+
export async function POST() {
|
|
12
|
+
return retiredResponse()
|
|
94
13
|
}
|