@swarmclawai/swarmclaw 1.2.8 → 1.3.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 +39 -6
- package/package.json +2 -2
- package/src/app/agents/[id]/page.tsx +1 -18
- package/src/app/api/activity/route.ts +9 -23
- package/src/app/api/agents/route.ts +17 -1
- package/src/app/api/agents/thread-route.test.ts +0 -1
- package/src/app/api/approvals/route.test.ts +6 -22
- package/src/app/api/approvals/route.ts +13 -5
- package/src/app/api/connectors/route.ts +2 -2
- package/src/app/api/credentials/[id]/route.ts +2 -0
- package/src/app/api/credentials/route.ts +4 -1
- package/src/app/api/goals/[id]/route.ts +28 -0
- package/src/app/api/goals/route.ts +33 -0
- package/src/app/api/portability/export/route.ts +8 -0
- package/src/app/api/portability/import/route.test.ts +80 -0
- package/src/app/api/portability/import/route.ts +28 -0
- package/src/app/api/protocols/templates/[id]/route.ts +2 -1
- package/src/app/api/protocols/templates/route.ts +2 -1
- package/src/app/api/settings/route.ts +13 -2
- package/src/app/api/wallets/[id]/route.ts +15 -157
- package/src/app/api/wallets/generate/route.ts +22 -0
- package/src/app/api/wallets/route.test.ts +147 -0
- package/src/app/api/wallets/route.ts +13 -95
- package/src/app/autonomy/page.tsx +2 -57
- package/src/app/home/page.tsx +3 -0
- package/src/app/protocols/page.tsx +2 -21
- package/src/app/settings/page.tsx +0 -9
- package/src/app/wallets/page.tsx +105 -5
- package/src/cli/index.js +32 -33
- package/src/cli/spec.js +26 -27
- package/src/components/agents/agent-sheet.tsx +2 -40
- package/src/components/agents/inspector-panel.tsx +0 -83
- package/src/components/chat/chat-card.tsx +0 -31
- package/src/components/chat/message-bubble.tsx +1 -108
- package/src/components/connectors/connector-sheet.tsx +25 -1
- package/src/components/layout/sidebar-rail.tsx +6 -10
- package/src/components/projects/project-detail.tsx +3 -35
- package/src/components/projects/tabs/overview-tab.tsx +3 -59
- package/src/components/projects/tabs/work-tab.tsx +7 -77
- package/src/components/protocols/structured-session-launcher.tsx +1 -22
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/components/tasks/task-card.tsx +4 -34
- package/src/components/tasks/task-sheet.tsx +6 -36
- package/src/components/wallets/wallet-list.tsx +150 -0
- package/src/lib/app/navigation.test.ts +0 -13
- package/src/lib/app/navigation.ts +2 -7
- package/src/lib/app/view-constants.ts +14 -19
- package/src/lib/server/activity/activity-log.ts +16 -1
- package/src/lib/server/agents/agent-service.ts +24 -11
- package/src/lib/server/agents/agent-thread-session.ts +0 -1
- package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
- package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
- package/src/lib/server/agents/delegation-jobs.ts +0 -25
- package/src/lib/server/agents/main-agent-loop.ts +1 -49
- package/src/lib/server/agents/subagent-runtime.ts +0 -1
- package/src/lib/server/approval-match.ts +14 -85
- package/src/lib/server/approvals/approval-hooks.ts +81 -0
- package/src/lib/server/approvals.test.ts +6 -6
- package/src/lib/server/approvals.ts +11 -6
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
- package/src/lib/server/builtin-extensions.ts +0 -2
- package/src/lib/server/capability-router.test.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +14 -14
- package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -2
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +2 -22
- package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
- package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
- package/src/lib/server/chat-execution/message-classifier.ts +1 -16
- package/src/lib/server/chat-execution/prompt-builder.test.ts +0 -1
- package/src/lib/server/chat-execution/prompt-builder.ts +0 -30
- package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
- package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
- package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +8 -123
- package/src/lib/server/chat-execution/stream-agent-chat.ts +1 -5
- package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
- package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
- package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
- package/src/lib/server/chats/chat-session-service.ts +3 -5
- package/src/lib/server/connectors/connector-inbound.ts +0 -1
- package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
- package/src/lib/server/connectors/connector-service.ts +39 -9
- package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
- package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
- package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
- package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
- package/src/lib/server/connectors/swarmdock-tasks.ts +127 -0
- package/src/lib/server/connectors/swarmdock.ts +285 -0
- package/src/lib/server/execution-brief.test.ts +2 -25
- package/src/lib/server/execution-brief.ts +30 -35
- package/src/lib/server/execution-engine/task-attempt.ts +0 -1
- package/src/lib/server/goals/goal-repository.ts +19 -0
- package/src/lib/server/goals/goal-service.ts +143 -0
- package/src/lib/server/persistence/storage-context.ts +0 -5
- package/src/lib/server/portability/export.ts +109 -0
- package/src/lib/server/portability/import.ts +159 -0
- package/src/lib/server/protocols/protocol-normalization.ts +0 -4
- package/src/lib/server/protocols/protocol-queries.ts +0 -6
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
- package/src/lib/server/protocols/protocol-service.ts +0 -1
- package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
- package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
- package/src/lib/server/protocols/protocol-swarm.ts +0 -2
- package/src/lib/server/protocols/protocol-types.ts +0 -2
- package/src/lib/server/provider-health.ts +0 -9
- package/src/lib/server/runtime/daemon-state/core.ts +0 -9
- package/src/lib/server/runtime/daemon-state.test.ts +0 -35
- package/src/lib/server/runtime/heartbeat-service.ts +3 -23
- package/src/lib/server/runtime/queue/core.ts +11 -33
- package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
- package/src/lib/server/runtime/scheduler.ts +0 -13
- package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/queries.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
- package/src/lib/server/runtime/session-run-manager.test.ts +0 -28
- package/src/lib/server/session-tools/crud.ts +0 -14
- package/src/lib/server/session-tools/delegate.ts +0 -4
- package/src/lib/server/session-tools/index.ts +0 -4
- package/src/lib/server/session-tools/team-context.ts +0 -3
- package/src/lib/server/storage-normalization.ts +13 -0
- package/src/lib/server/storage.ts +75 -45
- package/src/lib/server/tasks/task-checkout.ts +59 -0
- package/src/lib/server/tasks/task-lifecycle.ts +2 -0
- package/src/lib/server/tasks/task-route-service.ts +4 -26
- package/src/lib/server/tasks/task-service.ts +0 -7
- package/src/lib/server/tool-aliases.ts +0 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +4 -4
- package/src/lib/server/tool-capability-policy.ts +0 -2
- package/src/lib/server/tool-planning.ts +0 -12
- package/src/lib/server/universal-tool-access.ts +0 -1
- package/src/lib/server/usage/cost-rollup.ts +124 -0
- package/src/lib/server/usage/usage-repository.ts +6 -0
- package/src/lib/server/wallets/wallet-crypto.ts +33 -0
- package/src/lib/server/wallets/wallet-repository.ts +24 -0
- package/src/lib/server/wallets/wallet-service.ts +119 -0
- package/src/lib/server/working-state/extraction.ts +8 -42
- package/src/lib/server/working-state/normalization.ts +10 -103
- package/src/lib/server/working-state/service.ts +12 -21
- package/src/lib/strip-internal-metadata.test.ts +1 -1
- package/src/lib/strip-internal-metadata.ts +1 -1
- package/src/lib/tool-definitions.ts +0 -1
- package/src/lib/validation/schemas.ts +36 -32
- package/src/lib/validation/server-schemas.ts +35 -0
- package/src/stores/slices/data-slice.ts +5 -1
- package/src/stores/slices/ui-slice.ts +0 -4
- package/src/types/agent.ts +10 -84
- package/src/types/app-settings.ts +6 -2
- package/src/types/approval.ts +3 -2
- package/src/types/connector.ts +1 -0
- package/src/types/goal.ts +30 -0
- package/src/types/index.ts +2 -1
- package/src/types/message.ts +0 -1
- package/src/types/misc.ts +2 -4
- package/src/types/protocol.ts +0 -2
- package/src/types/run.ts +0 -3
- package/src/types/session.ts +1 -51
- package/src/types/swarmdock.ts +29 -0
- package/src/types/task.ts +9 -3
- package/src/types/working-state.ts +2 -9
- package/src/views/settings/section-runtime-loop.tsx +0 -14
- package/src/app/api/canvas/[sessionId]/route.ts +0 -35
- package/src/app/api/missions/[id]/actions/route.ts +0 -31
- package/src/app/api/missions/[id]/events/route.ts +0 -14
- package/src/app/api/missions/[id]/route.ts +0 -10
- package/src/app/api/missions/route.test.ts +0 -244
- package/src/app/api/missions/route.ts +0 -57
- package/src/app/api/wallets/[id]/approve/route.ts +0 -79
- package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
- package/src/app/api/wallets/[id]/send/route.ts +0 -113
- package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
- package/src/app/missions/[id]/page.tsx +0 -3
- package/src/app/missions/page.tsx +0 -685
- package/src/components/canvas/canvas-panel.tsx +0 -267
- package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
- package/src/components/wallets/wallet-panel.tsx +0 -1010
- package/src/components/wallets/wallet-section.tsx +0 -260
- package/src/features/missions/queries.ts +0 -23
- package/src/lib/canvas-content.test.ts +0 -360
- package/src/lib/canvas-content.ts +0 -198
- package/src/lib/server/canvas-content.test.ts +0 -32
- package/src/lib/server/canvas-content.ts +0 -6
- package/src/lib/server/ethereum.ts +0 -591
- package/src/lib/server/evm-swap.ts +0 -476
- package/src/lib/server/missions/mission-intent.test.ts +0 -63
- package/src/lib/server/missions/mission-intent.ts +0 -569
- package/src/lib/server/missions/mission-repository.ts +0 -74
- package/src/lib/server/missions/mission-service/actions.ts +0 -6
- package/src/lib/server/missions/mission-service/bindings.ts +0 -9
- package/src/lib/server/missions/mission-service/context.ts +0 -4
- package/src/lib/server/missions/mission-service/core.ts +0 -2271
- package/src/lib/server/missions/mission-service/queries.ts +0 -12
- package/src/lib/server/missions/mission-service/recovery.ts +0 -5
- package/src/lib/server/missions/mission-service/ticks.ts +0 -9
- package/src/lib/server/missions/mission-service.test.ts +0 -888
- package/src/lib/server/missions/mission-service.ts +0 -6
- package/src/lib/server/session-tools/canvas.ts +0 -105
- package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
- package/src/lib/server/session-tools/wallet.ts +0 -1287
- package/src/lib/server/solana.ts +0 -327
- package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
- package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
- package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
- package/src/lib/server/wallet/wallet-service.test.ts +0 -81
- package/src/lib/server/wallet/wallet-service.ts +0 -225
- package/src/lib/wallet/wallet-transactions.test.ts +0 -75
- package/src/lib/wallet/wallet-transactions.ts +0 -43
- package/src/lib/wallet/wallet.test.ts +0 -333
- package/src/lib/wallet/wallet.ts +0 -183
- package/src/types/mission.ts +0 -185
- package/src/views/settings/section-wallets.tsx +0 -35
package/README.md
CHANGED
|
@@ -100,13 +100,13 @@ A squad of agents mirroring a real engineering team — planning, building, revi
|
|
|
100
100
|
|
|
101
101
|
| Role | Agent | Tools |
|
|
102
102
|
|------|-------|-------|
|
|
103
|
-
| **Lead** | Architect | Delegation, tasks, schedules,
|
|
103
|
+
| **Lead** | Architect | Delegation, tasks, schedules, structured sessions |
|
|
104
104
|
| **Dev** | Builder | Shell, files, Claude Code / Codex / OpenCode |
|
|
105
105
|
| **QA** | Tester | Shell, browser, files, web search |
|
|
106
106
|
| **Designer** | Creative | Image generation, browser, web search, files |
|
|
107
107
|
| **Reviewer** | Critic | Files, web search, memory |
|
|
108
108
|
|
|
109
|
-
- The Lead
|
|
109
|
+
- The Lead breaks work into tasks on the board and uses structured sessions for bounded runs
|
|
110
110
|
- Dev agents pick up tasks and delegate to Claude Code, Codex, or OpenCode for implementation
|
|
111
111
|
- QA runs tests, takes screenshots, and files bugs back on the task board
|
|
112
112
|
- The Reviewer audits PRs and flags regressions
|
|
@@ -188,8 +188,39 @@ These aren't exclusive templates — they're patterns you combine. A virtual com
|
|
|
188
188
|
|
|
189
189
|
The building blocks are the same: **agents, tools, memory, delegation, schedules, connectors, and skills**. SwarmClaw just gives you the control plane to wire them together.
|
|
190
190
|
|
|
191
|
+
## SwarmDock Marketplace
|
|
192
|
+
|
|
193
|
+
SwarmClaw agents can register on [SwarmDock](https://swarmdock.ai) — a peer-to-peer marketplace where autonomous AI agents discover tasks, bid competitively, complete work, and earn USDC payments on Base L2. SwarmDock is the marketplace; SwarmClaw is the control plane.
|
|
194
|
+
|
|
195
|
+
- **Register** your agents on SwarmDock with their Ed25519 identity and skill set
|
|
196
|
+
- **Discover** paid tasks matching your agents' capabilities via polling or real-time SSE
|
|
197
|
+
- **Bid** autonomously within configured budget and confidence thresholds
|
|
198
|
+
- **Earn** USDC on Base L2 with 7% platform fee, sub-2-second settlement
|
|
199
|
+
- **Track** assignments, payouts, and task history from the SwarmClaw task board and connectors UI
|
|
200
|
+
|
|
201
|
+
Read the full setup guide in [`SWARMDOCK.md`](./SWARMDOCK.md), browse the public docs at [swarmclaw.ai/docs/swarmdock](https://swarmclaw.ai/docs/swarmdock), and visit [swarmdock.ai](https://swarmdock.ai) for the marketplace itself.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
191
205
|
## Release Notes
|
|
192
206
|
|
|
207
|
+
### v1.3.0 Highlights
|
|
208
|
+
|
|
209
|
+
- **SwarmDock SDK v0.2.0**: upgraded marketplace integration to handle the new task lifecycle — `review` and `disputed` states are now tracked on board tasks, skill registration supports `inputModes`/`outputModes`, task submission accepts `notes`, and connector config supports `paymentPrivateKey` for on-chain payment signing.
|
|
210
|
+
- **Comprehensive audit logging**: activity log now covers approval decisions, settings changes, budget modifications, and credential operations, with SQL-indexed paginated queries replacing the in-memory full-collection scan.
|
|
211
|
+
- **Push-based cost rollups**: agent spend fields (`spentHourlyCents`, `spentDailyCents`, `spentMonthlyCents`) update atomically on every usage event, with automatic budget warning/exceeded activity entries and window reset detection — replacing the pull-based full-scan approach.
|
|
212
|
+
- **Goal hierarchy**: new goals system with organization → team → project → agent → task levels, parent-child chains, and automatic injection of the "why chain" into agent execution briefs. Full CRUD API and CLI support.
|
|
213
|
+
- **Extended approval workflows**: new `agent_create`, `budget_change`, and `delegation_enable` approval categories with configurable policies in settings. When enabled, agent creation returns a pending approval instead of creating the agent directly.
|
|
214
|
+
- **Shared validation schemas**: Zod schemas in `src/lib/validation/schemas.ts` are now safe for client-side import (server-only DAG validation moved to `server-schemas.ts`), enabling form-level pre-validation.
|
|
215
|
+
|
|
216
|
+
### v1.2.9 Highlights
|
|
217
|
+
|
|
218
|
+
- **SwarmDock marketplace connector**: SwarmClaw agents can now register on SwarmDock, auto-bid on matching work, receive assignments as board tasks, and submit results back through the connector runtime.
|
|
219
|
+
- **Secure SwarmDock credentials**: Ed25519 identity keys now live in encrypted credentials instead of connector config, legacy plaintext keys auto-migrate on load, connector API responses redact secrets, and failed result submissions now surface properly for retry/recovery.
|
|
220
|
+
- **Portable config transfer**: new portability import/export flows move agents, skills, and schedules between installs, with manifest validation that rejects malformed imports cleanly instead of crashing.
|
|
221
|
+
- **Wallet surface simplification**: wallets are now lightweight Base-linked agent records with stricter validation for missing agents and invalid addresses, replacing the older action-heavy wallet route surface.
|
|
222
|
+
- **UI surface cleanup**: the app now centers work around tasks, structured sessions, autonomy, connectors, and projects, with the old Missions, Canvas, and legacy wallet action screens removed from the shipped interface.
|
|
223
|
+
|
|
193
224
|
### v1.2.8 Highlights
|
|
194
225
|
|
|
195
226
|
- **Linux/WSL compatibility**: subprocess spawning now uses `$SHELL` instead of hardcoded `/bin/zsh`, fixing `ENOENT` errors on Linux and WSL systems.
|
|
@@ -345,12 +376,12 @@ Then open `http://localhost:3456`.
|
|
|
345
376
|
|
|
346
377
|
- **Providers**: OpenClaw, OpenAI, Anthropic, Ollama, Google, DeepSeek, Groq, Together, Mistral, xAI, Fireworks, Nebius, DeepInfra, plus compatible custom endpoints.
|
|
347
378
|
- **Delegation**: built-in delegation to Claude Code, Codex CLI, OpenCode CLI, Gemini CLI, and native SwarmClaw subagents.
|
|
348
|
-
- **Autonomy**: heartbeat loops, schedules, background jobs, task execution, supervisor recovery,
|
|
349
|
-
- **Orchestration**: durable structured execution with branching, repeat loops, parallel branches, explicit joins, restart-safe run state, and contextual launch from chats, chatrooms, tasks,
|
|
379
|
+
- **Autonomy**: heartbeat loops, schedules, background jobs, task execution, supervisor recovery, and agent wakeups.
|
|
380
|
+
- **Orchestration**: durable structured execution with branching, repeat loops, parallel branches, explicit joins, restart-safe run state, and contextual launch from chats, chatrooms, tasks, schedules, and API flows.
|
|
350
381
|
- **Structured Sessions**: reusable bounded runs with templates, facilitators, participants, hidden live rooms, chatroom `/breakout`, durable transcripts, outputs, and operator controls.
|
|
351
382
|
- **Memory**: hybrid recall, graph traversal, journaling, durable documents, project-scoped context, automatic reflection memory, communication preferences, profile and boundary memory, significant events, and open follow-up loops.
|
|
352
|
-
- **Wallets**:
|
|
353
|
-
- **Connectors**: Discord, Slack, Telegram, WhatsApp, Teams, Matrix, OpenClaw, and more.
|
|
383
|
+
- **Wallets**: linked Base wallet generation, address management, approval-oriented limits, and agent payout identity.
|
|
384
|
+
- **Connectors**: Discord, Slack, Telegram, WhatsApp, Teams, Matrix, OpenClaw, SwarmDock, and more.
|
|
354
385
|
- **Extensions**: external tool extensions, UI modules, hooks, and install/update flows.
|
|
355
386
|
|
|
356
387
|
## Requirements
|
|
@@ -373,5 +404,7 @@ Then open `http://localhost:3456`.
|
|
|
373
404
|
- OpenClaw setup: https://swarmclaw.ai/docs/openclaw-setup
|
|
374
405
|
- Agents: https://swarmclaw.ai/docs/agents
|
|
375
406
|
- Connectors: https://swarmclaw.ai/docs/connectors
|
|
407
|
+
- SwarmDock: https://swarmclaw.ai/docs/swarmdock
|
|
408
|
+
- SwarmDock marketplace: https://swarmdock.ai
|
|
376
409
|
- Extensions: https://swarmclaw.ai/docs/extensions
|
|
377
410
|
- CLI reference: https://swarmclaw.ai/docs/cli
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Self-hosted AI runtime for OpenClaw, delegation, autonomy, runtime skills, crypto wallets, and chat platform connectors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"@multiavatar/multiavatar": "^1.0.7",
|
|
88
88
|
"@playwright/mcp": "^0.0.68",
|
|
89
89
|
"@slack/bolt": "^4.6.0",
|
|
90
|
-
"@
|
|
90
|
+
"@swarmdock/sdk": "^0.2.0",
|
|
91
91
|
"@tailwindcss/postcss": "^4",
|
|
92
92
|
"@tanstack/react-query": "^5.91.0",
|
|
93
93
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
4
|
import { useParams } from 'next/navigation'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
|
-
import { selectActiveSessionId } from '@/stores/slices/session-slice'
|
|
7
6
|
import { ChatArea } from '@/components/chat/chat-area'
|
|
8
|
-
import { CanvasPanel } from '@/components/canvas/canvas-panel'
|
|
9
7
|
|
|
10
8
|
export default function AgentChatPage() {
|
|
11
9
|
const { id } = useParams<{ id: string }>()
|
|
12
10
|
const setCurrentAgent = useAppStore((s) => s.setCurrentAgent)
|
|
13
|
-
const activeSessionId = useAppStore(selectActiveSessionId)
|
|
14
|
-
const sessions = useAppStore((s) => s.sessions)
|
|
15
|
-
const agents = useAppStore((s) => s.agents)
|
|
16
|
-
const [canvasDismissedFor, setCanvasDismissedFor] = useState<string | null>(null)
|
|
17
11
|
|
|
18
12
|
// Sync URL param to store
|
|
19
13
|
useEffect(() => {
|
|
@@ -22,22 +16,11 @@ export default function AgentChatPage() {
|
|
|
22
16
|
}
|
|
23
17
|
}, [id, setCurrentAgent])
|
|
24
18
|
|
|
25
|
-
const currentSession = activeSessionId ? sessions[activeSessionId] : null
|
|
26
|
-
const hasCanvas = !!(currentSession?.canvasContent && canvasDismissedFor !== activeSessionId)
|
|
27
|
-
const canvasAgentName = currentSession?.agentId && agents[currentSession.agentId] ? agents[currentSession.agentId].name : undefined
|
|
28
|
-
|
|
29
19
|
return (
|
|
30
20
|
<div className="flex-1 flex h-full min-h-0 min-w-0">
|
|
31
21
|
<div className="flex-1 min-h-0 min-w-0 overflow-hidden">
|
|
32
22
|
<ChatArea key={id} />
|
|
33
23
|
</div>
|
|
34
|
-
{hasCanvas && activeSessionId && (
|
|
35
|
-
<CanvasPanel
|
|
36
|
-
sessionId={activeSessionId}
|
|
37
|
-
agentName={canvasAgentName}
|
|
38
|
-
onClose={() => activeSessionId && setCanvasDismissedFor(activeSessionId)}
|
|
39
|
-
/>
|
|
40
|
-
)}
|
|
41
24
|
</div>
|
|
42
25
|
)
|
|
43
26
|
}
|
|
@@ -1,32 +1,18 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import {
|
|
2
|
+
import { queryActivity } from '@/lib/server/activity/activity-log'
|
|
3
3
|
export const dynamic = 'force-dynamic'
|
|
4
4
|
|
|
5
5
|
export async function GET(req: Request) {
|
|
6
6
|
const { searchParams } = new URL(req.url)
|
|
7
|
-
const entityType = searchParams.get('entityType')
|
|
8
|
-
const entityId = searchParams.get('entityId')
|
|
9
|
-
const actor = searchParams.get('actor')
|
|
10
|
-
const action = searchParams.get('action')
|
|
11
|
-
const
|
|
7
|
+
const entityType = searchParams.get('entityType') ?? undefined
|
|
8
|
+
const entityId = searchParams.get('entityId') ?? undefined
|
|
9
|
+
const actor = searchParams.get('actor') ?? undefined
|
|
10
|
+
const action = searchParams.get('action') ?? undefined
|
|
11
|
+
const sinceRaw = searchParams.get('since')
|
|
12
|
+
const since = sinceRaw ? Number(sinceRaw) : undefined
|
|
12
13
|
const limit = Math.min(200, Math.max(1, Number(searchParams.get('limit')) || 50))
|
|
14
|
+
const offset = Math.max(0, Number(searchParams.get('offset')) || 0)
|
|
13
15
|
|
|
14
|
-
const
|
|
15
|
-
let entries = Object.values(all) as unknown as Array<Record<string, unknown>>
|
|
16
|
-
|
|
17
|
-
if (entityType) entries = entries.filter((e) => e.entityType === entityType)
|
|
18
|
-
if (entityId) entries = entries.filter((e) => e.entityId === entityId)
|
|
19
|
-
if (actor) entries = entries.filter((e) => e.actor === actor)
|
|
20
|
-
if (action) entries = entries.filter((e) => e.action === action)
|
|
21
|
-
if (since) {
|
|
22
|
-
const sinceMs = Number(since)
|
|
23
|
-
if (Number.isFinite(sinceMs)) {
|
|
24
|
-
entries = entries.filter((e) => typeof e.timestamp === 'number' && e.timestamp >= sinceMs)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
entries.sort((a, b) => (b.timestamp as number) - (a.timestamp as number))
|
|
29
|
-
entries = entries.slice(0, limit)
|
|
30
|
-
|
|
16
|
+
const entries = queryActivity({ entityType, entityId, actor, action, since, limit, offset })
|
|
31
17
|
return NextResponse.json(entries)
|
|
32
18
|
}
|
|
@@ -5,6 +5,8 @@ import { AgentCreateSchema, formatZodError } from '@/lib/validation/schemas'
|
|
|
5
5
|
import { ensureDaemonProcessRunning } from '@/lib/server/daemon/controller'
|
|
6
6
|
import { z } from 'zod'
|
|
7
7
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
8
|
+
import { loadSettings } from '@/lib/server/storage'
|
|
9
|
+
import { requestApproval } from '@/lib/server/approvals'
|
|
8
10
|
export const dynamic = 'force-dynamic'
|
|
9
11
|
|
|
10
12
|
|
|
@@ -36,6 +38,20 @@ export async function POST(req: Request) {
|
|
|
36
38
|
if (!parsed.success) {
|
|
37
39
|
return NextResponse.json(formatZodError(parsed.error as z.ZodError), { status: 400 })
|
|
38
40
|
}
|
|
39
|
-
const
|
|
41
|
+
const body = parsed.data as unknown as Record<string, unknown>
|
|
42
|
+
|
|
43
|
+
// Check approval policy — if enabled, create an approval request instead of the agent
|
|
44
|
+
const settings = loadSettings()
|
|
45
|
+
if (settings.approvalPolicies?.requireApprovalForAgentCreate) {
|
|
46
|
+
const approval = requestApproval({
|
|
47
|
+
category: 'agent_create',
|
|
48
|
+
title: `Create agent: ${body.name}`,
|
|
49
|
+
description: `Request to create agent "${body.name}" with provider ${body.provider}`,
|
|
50
|
+
data: { pendingAgentConfig: body, agentName: String(body.name || ''), provider: String(body.provider || '') },
|
|
51
|
+
})
|
|
52
|
+
return NextResponse.json({ pendingApproval: true, approvalId: approval.id }, { status: 202 })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const agent = createAgent({ body, rawRecord })
|
|
40
56
|
return NextResponse.json(agent)
|
|
41
57
|
}
|
|
@@ -46,11 +46,6 @@ test('GET and POST /api/approvals handle human-loop approvals only', () => {
|
|
|
46
46
|
title: 'Approve deploy',
|
|
47
47
|
data: { question: 'Deploy now?' },
|
|
48
48
|
})
|
|
49
|
-
approvals.requestApproval({
|
|
50
|
-
category: 'wallet_action',
|
|
51
|
-
title: 'Legacy wallet approval',
|
|
52
|
-
data: { action: 'sign_message' },
|
|
53
|
-
})
|
|
54
49
|
|
|
55
50
|
const pendingBefore = await route.GET()
|
|
56
51
|
const pendingBeforeJson = await pendingBefore.json()
|
|
@@ -84,43 +79,32 @@ test('GET and POST /api/approvals handle human-loop approvals only', () => {
|
|
|
84
79
|
assert.equal(output.storedStatus, 'approved')
|
|
85
80
|
})
|
|
86
81
|
|
|
87
|
-
test('POST /api/approvals rejects invalid
|
|
82
|
+
test('POST /api/approvals rejects invalid payloads', () => {
|
|
88
83
|
const output = runWithTempDataDir(`
|
|
89
84
|
const approvalsMod = await import('./src/lib/server/approvals')
|
|
90
85
|
const routeMod = await import('./src/app/api/approvals/route')
|
|
91
86
|
const approvals = approvalsMod.default || approvalsMod
|
|
92
87
|
const route = routeMod.default || routeMod
|
|
93
88
|
|
|
94
|
-
const
|
|
95
|
-
category: '
|
|
96
|
-
title: '
|
|
97
|
-
data: {
|
|
89
|
+
const humanApproval = approvals.requestApproval({
|
|
90
|
+
category: 'human_loop',
|
|
91
|
+
title: 'Test approval',
|
|
92
|
+
data: { question: 'Proceed?' },
|
|
98
93
|
})
|
|
99
94
|
|
|
100
95
|
const invalidResponse = await route.POST(new Request('http://local/api/approvals', {
|
|
101
96
|
method: 'POST',
|
|
102
97
|
headers: { 'content-type': 'application/json' },
|
|
103
|
-
body: JSON.stringify({ id:
|
|
98
|
+
body: JSON.stringify({ id: humanApproval.id }),
|
|
104
99
|
}))
|
|
105
100
|
const invalidPayload = await invalidResponse.json()
|
|
106
101
|
|
|
107
|
-
const wrongCategory = await route.POST(new Request('http://local/api/approvals', {
|
|
108
|
-
method: 'POST',
|
|
109
|
-
headers: { 'content-type': 'application/json' },
|
|
110
|
-
body: JSON.stringify({ id: walletApproval.id, approved: true }),
|
|
111
|
-
}))
|
|
112
|
-
const wrongCategoryPayload = await wrongCategory.json()
|
|
113
|
-
|
|
114
102
|
console.log(JSON.stringify({
|
|
115
103
|
invalidStatus: invalidResponse.status,
|
|
116
104
|
invalidError: invalidPayload?.error || null,
|
|
117
|
-
wrongCategoryStatus: wrongCategory.status,
|
|
118
|
-
wrongCategoryError: wrongCategoryPayload?.error || null,
|
|
119
105
|
}))
|
|
120
106
|
`)
|
|
121
107
|
|
|
122
108
|
assert.equal(output.invalidStatus, 400)
|
|
123
109
|
assert.match(String(output.invalidError || ''), /id and approved required/i)
|
|
124
|
-
assert.equal(output.wrongCategoryStatus, 400)
|
|
125
|
-
assert.match(String(output.wrongCategoryError || ''), /human-loop/i)
|
|
126
110
|
})
|
|
@@ -2,11 +2,22 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { listPendingApprovals, submitDecision } from '@/lib/server/approvals'
|
|
3
3
|
import { loadApprovals } from '@/lib/server/storage'
|
|
4
4
|
import { errorMessage } from '@/lib/shared-utils'
|
|
5
|
+
import type { ApprovalCategory } from '@/types'
|
|
5
6
|
|
|
6
7
|
export const dynamic = 'force-dynamic'
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
const ALLOWED_CATEGORIES: ApprovalCategory[] = [
|
|
10
|
+
'human_loop', 'tool_access', 'extension_scaffold', 'extension_install',
|
|
11
|
+
'task_tool', 'connector_sender', 'agent_create', 'budget_change', 'delegation_enable',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
export async function GET(req: Request) {
|
|
15
|
+
const { searchParams } = new URL(req.url)
|
|
16
|
+
const categoryParam = searchParams.get('category') as ApprovalCategory | null
|
|
17
|
+
const category = categoryParam && ALLOWED_CATEGORIES.includes(categoryParam)
|
|
18
|
+
? categoryParam
|
|
19
|
+
: undefined
|
|
20
|
+
return NextResponse.json(listPendingApprovals(category))
|
|
10
21
|
}
|
|
11
22
|
|
|
12
23
|
export async function POST(req: Request) {
|
|
@@ -20,9 +31,6 @@ export async function POST(req: Request) {
|
|
|
20
31
|
if (!approval) {
|
|
21
32
|
return NextResponse.json({ error: 'approval not found' }, { status: 404 })
|
|
22
33
|
}
|
|
23
|
-
if (approval.category !== 'human_loop') {
|
|
24
|
-
return NextResponse.json({ error: 'only human-loop approvals are supported here' }, { status: 400 })
|
|
25
|
-
}
|
|
26
34
|
await submitDecision(id, approved)
|
|
27
35
|
return NextResponse.json({ ok: true })
|
|
28
36
|
} catch (err: unknown) {
|
|
@@ -6,10 +6,10 @@ import { z } from 'zod'
|
|
|
6
6
|
import {
|
|
7
7
|
autoStartConnectorIfNeeded,
|
|
8
8
|
createConnector,
|
|
9
|
+
getConnectorWithRuntime,
|
|
9
10
|
listConnectorsWithRuntime,
|
|
10
11
|
} from '@/lib/server/connectors/connector-service'
|
|
11
12
|
import { ensureDaemonProcessRunning } from '@/lib/server/daemon/controller'
|
|
12
|
-
import { loadConnector } from '@/lib/server/connectors/connector-repository'
|
|
13
13
|
export const dynamic = 'force-dynamic'
|
|
14
14
|
|
|
15
15
|
export async function GET() {
|
|
@@ -29,5 +29,5 @@ export async function POST(req: Request) {
|
|
|
29
29
|
}
|
|
30
30
|
const connector = createConnector(parsed.data as unknown as Record<string, unknown>)
|
|
31
31
|
await autoStartConnectorIfNeeded(connector, parsed.data as unknown as Record<string, unknown>)
|
|
32
|
-
return NextResponse.json(
|
|
32
|
+
return NextResponse.json(await getConnectorWithRuntime(connector.id) || connector)
|
|
33
33
|
}
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { deleteCredentialRecord } from '@/lib/server/credentials/credential-service'
|
|
3
3
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
4
|
import { log } from '@/lib/server/logger'
|
|
5
|
+
import { logActivity } from '@/lib/server/activity/activity-log'
|
|
5
6
|
|
|
6
7
|
const TAG = 'api-credentials'
|
|
7
8
|
|
|
@@ -11,5 +12,6 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
11
12
|
return notFound()
|
|
12
13
|
}
|
|
13
14
|
log.info(TAG, `deleted ${credId}`)
|
|
15
|
+
logActivity({ entityType: 'credential', entityId: credId, action: 'deleted', actor: 'user', summary: `Credential deleted: ${credId}` })
|
|
14
16
|
return new NextResponse('OK')
|
|
15
17
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { createCredentialRecord, listCredentialSummaries } from '@/lib/server/credentials/credential-service'
|
|
3
3
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
4
|
+
import { logActivity } from '@/lib/server/activity/activity-log'
|
|
4
5
|
export const dynamic = 'force-dynamic'
|
|
5
6
|
|
|
6
7
|
|
|
@@ -16,7 +17,9 @@ export async function POST(req: Request) {
|
|
|
16
17
|
return NextResponse.json({ error: 'provider and apiKey are required' }, { status: 400 })
|
|
17
18
|
}
|
|
18
19
|
try {
|
|
19
|
-
|
|
20
|
+
const result = createCredentialRecord({ provider, name, apiKey })
|
|
21
|
+
logActivity({ entityType: 'credential', entityId: result.id, action: 'created', actor: 'user', summary: `Credential created: "${name}" (${provider})` })
|
|
22
|
+
return NextResponse.json(result)
|
|
20
23
|
} catch (err: unknown) {
|
|
21
24
|
return NextResponse.json(
|
|
22
25
|
{ error: err instanceof Error ? err.message : 'Failed to create credential' },
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
3
|
+
import { getGoalById, updateGoal, deleteGoal, getGoalChain } from '@/lib/server/goals/goal-service'
|
|
4
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
|
+
export const dynamic = 'force-dynamic'
|
|
6
|
+
|
|
7
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
|
+
const { id } = await params
|
|
9
|
+
const goal = getGoalById(id)
|
|
10
|
+
if (!goal) return notFound()
|
|
11
|
+
const chain = getGoalChain(id)
|
|
12
|
+
return NextResponse.json({ ...goal, chain })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
16
|
+
const { id } = await params
|
|
17
|
+
const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
|
|
18
|
+
if (error) return error
|
|
19
|
+
const updated = updateGoal(id, body)
|
|
20
|
+
if (!updated) return notFound()
|
|
21
|
+
return NextResponse.json(updated)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
25
|
+
const { id } = await params
|
|
26
|
+
if (!deleteGoal(id)) return notFound()
|
|
27
|
+
return new NextResponse('OK')
|
|
28
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
4
|
+
import { getAllGoals, createGoal } from '@/lib/server/goals/goal-service'
|
|
5
|
+
import { formatZodError } from '@/lib/validation/schemas'
|
|
6
|
+
export const dynamic = 'force-dynamic'
|
|
7
|
+
|
|
8
|
+
const GoalCreateSchema = z.object({
|
|
9
|
+
title: z.string().min(1, 'Goal title is required'),
|
|
10
|
+
description: z.string().optional().default(''),
|
|
11
|
+
level: z.enum(['organization', 'team', 'project', 'agent', 'task']),
|
|
12
|
+
parentGoalId: z.string().nullable().optional().default(null),
|
|
13
|
+
projectId: z.string().nullable().optional().default(null),
|
|
14
|
+
agentId: z.string().nullable().optional().default(null),
|
|
15
|
+
taskId: z.string().nullable().optional().default(null),
|
|
16
|
+
objective: z.string().min(1, 'Objective is required'),
|
|
17
|
+
constraints: z.array(z.string()).optional().default([]),
|
|
18
|
+
successMetric: z.string().nullable().optional().default(null),
|
|
19
|
+
budgetUsd: z.number().positive().nullable().optional().default(null),
|
|
20
|
+
deadlineAt: z.number().nullable().optional().default(null),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export async function GET() {
|
|
24
|
+
return NextResponse.json(getAllGoals())
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function POST(req: Request) {
|
|
28
|
+
const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
|
|
29
|
+
if (error) return error
|
|
30
|
+
const parsed = GoalCreateSchema.safeParse(body)
|
|
31
|
+
if (!parsed.success) return NextResponse.json(formatZodError(parsed.error), { status: 400 })
|
|
32
|
+
return NextResponse.json(createGoal(parsed.data))
|
|
33
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
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-portability-import-'))
|
|
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('POST /api/portability/import validates manifest arrays before importing', () => {
|
|
36
|
+
const output = runWithTempDataDir(`
|
|
37
|
+
const routeMod = await import('./src/app/api/portability/import/route')
|
|
38
|
+
const route = routeMod.default || routeMod
|
|
39
|
+
|
|
40
|
+
const invalidResponse = await route.POST(new Request('http://local/api/portability/import', {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { 'content-type': 'application/json' },
|
|
43
|
+
body: JSON.stringify({ formatVersion: 1, agents: [] }),
|
|
44
|
+
}))
|
|
45
|
+
const invalidPayload = await invalidResponse.json()
|
|
46
|
+
|
|
47
|
+
const validResponse = await route.POST(new Request('http://local/api/portability/import', {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: { 'content-type': 'application/json' },
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
formatVersion: 1,
|
|
52
|
+
exportedAt: '2026-03-29T00:00:00.000Z',
|
|
53
|
+
agents: [],
|
|
54
|
+
skills: [],
|
|
55
|
+
schedules: [],
|
|
56
|
+
}),
|
|
57
|
+
}))
|
|
58
|
+
const validPayload = await validResponse.json()
|
|
59
|
+
|
|
60
|
+
console.log(JSON.stringify({
|
|
61
|
+
invalidStatus: invalidResponse.status,
|
|
62
|
+
invalidError: invalidPayload?.error || null,
|
|
63
|
+
invalidPaths: Array.isArray(invalidPayload?.issues)
|
|
64
|
+
? invalidPayload.issues.map((issue) => issue.path).sort()
|
|
65
|
+
: [],
|
|
66
|
+
validStatus: validResponse.status,
|
|
67
|
+
validAgentsCreated: validPayload?.agents?.created ?? null,
|
|
68
|
+
validSkillsCreated: validPayload?.skills?.created ?? null,
|
|
69
|
+
validSchedulesCreated: validPayload?.schedules?.created ?? null,
|
|
70
|
+
}))
|
|
71
|
+
`)
|
|
72
|
+
|
|
73
|
+
assert.equal(output.invalidStatus, 400)
|
|
74
|
+
assert.equal(output.invalidError, 'Validation failed')
|
|
75
|
+
assert.deepEqual(output.invalidPaths, ['schedules', 'skills'])
|
|
76
|
+
assert.equal(output.validStatus, 200)
|
|
77
|
+
assert.equal(output.validAgentsCreated, 0)
|
|
78
|
+
assert.equal(output.validSkillsCreated, 0)
|
|
79
|
+
assert.equal(output.validSchedulesCreated, 0)
|
|
80
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
3
|
+
import { importConfig } from '@/lib/server/portability/import'
|
|
4
|
+
import type { PortableManifest } from '@/lib/server/portability/export'
|
|
5
|
+
import { PortableManifestSchema, formatZodError } from '@/lib/validation/schemas'
|
|
6
|
+
import { z } from 'zod'
|
|
7
|
+
export const dynamic = 'force-dynamic'
|
|
8
|
+
|
|
9
|
+
export async function POST(req: Request) {
|
|
10
|
+
const { data: raw, error } = await safeParseBody(req)
|
|
11
|
+
if (error) return error
|
|
12
|
+
|
|
13
|
+
const parsed = PortableManifestSchema.safeParse(raw)
|
|
14
|
+
if (!parsed.success) {
|
|
15
|
+
return NextResponse.json(formatZodError(parsed.error as z.ZodError), { status: 400 })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const result = importConfig(parsed.data as PortableManifest)
|
|
20
|
+
return NextResponse.json(result)
|
|
21
|
+
} catch (err) {
|
|
22
|
+
const message = err instanceof Error ? err.message : 'Failed to import manifest'
|
|
23
|
+
if (/^Unsupported format version /i.test(message)) {
|
|
24
|
+
return NextResponse.json({ error: message }, { status: 400 })
|
|
25
|
+
}
|
|
26
|
+
return NextResponse.json({ error: message }, { status: 500 })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
-
import { formatZodError
|
|
4
|
+
import { formatZodError } from '@/lib/validation/schemas'
|
|
5
|
+
import { ProtocolTemplateUpsertSchema } from '@/lib/validation/server-schemas'
|
|
5
6
|
import {
|
|
6
7
|
deleteProtocolTemplateById,
|
|
7
8
|
loadProtocolTemplateById,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
-
import { formatZodError
|
|
3
|
+
import { formatZodError } from '@/lib/validation/schemas'
|
|
4
|
+
import { ProtocolTemplateUpsertSchema } from '@/lib/validation/server-schemas'
|
|
4
5
|
import {
|
|
5
6
|
createProtocolTemplate,
|
|
6
7
|
listProtocolTemplates,
|
|
@@ -6,6 +6,7 @@ import { loadPublicSettings, loadSettings, saveSettings } from '@/lib/server/sto
|
|
|
6
6
|
import { normalizeRuntimeSettingFields } from '@/lib/runtime/runtime-loop'
|
|
7
7
|
import { normalizeSupervisorSettings } from '@/lib/autonomy/supervisor-settings'
|
|
8
8
|
import { ensureDaemonProcessRunning } from '@/lib/server/daemon/controller'
|
|
9
|
+
import { logActivity } from '@/lib/server/activity/activity-log'
|
|
9
10
|
export const dynamic = 'force-dynamic'
|
|
10
11
|
|
|
11
12
|
|
|
@@ -141,11 +142,9 @@ export async function PUT(req: Request) {
|
|
|
141
142
|
settings.taskQualityGateRequireReport = parseBoolSetting(settings.taskQualityGateRequireReport, false)
|
|
142
143
|
settings.taskManagementEnabled = parseBoolSetting(settings.taskManagementEnabled, true)
|
|
143
144
|
settings.projectManagementEnabled = parseBoolSetting(settings.projectManagementEnabled, true)
|
|
144
|
-
settings.walletApprovalsEnabled = parseBoolSetting(settings.walletApprovalsEnabled, true)
|
|
145
145
|
settings.integrityMonitorEnabled = parseBoolSetting(settings.integrityMonitorEnabled, true)
|
|
146
146
|
settings.daemonAutostartEnabled = parseBoolSetting(settings.daemonAutostartEnabled, true)
|
|
147
147
|
settings.autonomyResumeApprovalsEnabled = parseBoolSetting(settings.autonomyResumeApprovalsEnabled, false)
|
|
148
|
-
settings.missionHumanLoopEnabled = parseBoolSetting(settings.missionHumanLoopEnabled, false)
|
|
149
148
|
settings.untrustedContentGuardMode = parseGuardMode(settings.untrustedContentGuardMode)
|
|
150
149
|
settings.sessionResetMode = settings.sessionResetMode === 'daily' ? 'daily' : settings.sessionResetMode === 'idle' ? 'idle' : null
|
|
151
150
|
settings.whatsappApprovedContacts = normalizeWhatsAppApprovedContacts(settings.whatsappApprovedContacts)
|
|
@@ -166,6 +165,18 @@ export async function PUT(req: Request) {
|
|
|
166
165
|
|
|
167
166
|
saveSettings(settings)
|
|
168
167
|
|
|
168
|
+
const changedKeys = Object.keys(sanitizedBody).filter((k) => !SECRET_SETTING_KEYS.includes(k as typeof SECRET_SETTING_KEYS[number]))
|
|
169
|
+
if (changedKeys.length > 0) {
|
|
170
|
+
logActivity({
|
|
171
|
+
entityType: 'settings',
|
|
172
|
+
entityId: 'app',
|
|
173
|
+
action: 'configured',
|
|
174
|
+
actor: 'user',
|
|
175
|
+
summary: `Settings updated: ${changedKeys.join(', ')}`,
|
|
176
|
+
detail: { changedKeys },
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
169
180
|
if ('daemonAutostartEnabled' in sanitizedBody && settings.daemonAutostartEnabled) {
|
|
170
181
|
void ensureDaemonProcessRunning('api/settings:put:daemon-autostart', { manualStart: true }).catch(() => {
|
|
171
182
|
// The daemon launcher may not be available during early bootstrap.
|