@swarmclawai/swarmclaw 1.1.4 → 1.1.6
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 +25 -0
- package/bin/install-root.js +3 -0
- package/bin/server-cmd.js +3 -1
- package/bin/update-cmd.js +4 -1
- package/package.json +2 -1
- package/scripts/easy-setup.mjs +5 -2
- package/src/app/api/activity/route.ts +2 -0
- package/src/app/api/agents/[id]/route.ts +4 -3
- package/src/app/api/agents/bulk/route.ts +55 -0
- package/src/app/api/agents/route.ts +3 -1
- package/src/app/api/agents/trash/route.ts +5 -2
- package/src/app/api/auth/route.ts +14 -1
- package/src/app/api/canvas/[sessionId]/route.ts +3 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +43 -4
- package/src/app/api/chatrooms/[id]/members/route.ts +7 -4
- package/src/app/api/chatrooms/[id]/moderate/route.ts +3 -1
- package/src/app/api/chatrooms/[id]/pins/route.ts +4 -2
- package/src/app/api/chatrooms/[id]/reactions/route.ts +6 -4
- package/src/app/api/chatrooms/[id]/route.ts +8 -5
- package/src/app/api/chatrooms/route.ts +15 -3
- package/src/app/api/chats/[id]/deploy/route.ts +3 -1
- package/src/app/api/chats/[id]/devserver/route.ts +4 -1
- package/src/app/api/chats/[id]/messages/route.ts +8 -4
- package/src/app/api/chats/[id]/route.ts +4 -2
- package/src/app/api/clawhub/install/route.ts +3 -1
- package/src/app/api/connectors/[id]/access/route.ts +3 -1
- package/src/app/api/connectors/[id]/route.ts +11 -9
- package/src/app/api/connectors/route.ts +3 -1
- package/src/app/api/credentials/route.ts +4 -1
- package/src/app/api/dashboard/route.ts +130 -0
- package/src/app/api/documents/[id]/revisions/route.ts +22 -0
- package/src/app/api/documents/[id]/route.ts +17 -1
- package/src/app/api/extensions/dependencies/route.ts +3 -1
- package/src/app/api/extensions/install/route.ts +5 -1
- package/src/app/api/extensions/route.ts +7 -2
- package/src/app/api/files/open/route.ts +4 -1
- package/src/app/api/mcp-servers/[id]/route.ts +3 -1
- package/src/app/api/mcp-servers/route.ts +3 -1
- package/src/app/api/notifications/route.ts +3 -1
- package/src/app/api/openclaw/agent-files/route.ts +3 -1
- package/src/app/api/openclaw/approvals/route.ts +3 -1
- package/src/app/api/openclaw/config-sync/route.ts +3 -1
- package/src/app/api/openclaw/cron/route.ts +4 -2
- package/src/app/api/openclaw/exec-config/route.ts +3 -1
- package/src/app/api/openclaw/gateway/route.ts +4 -2
- package/src/app/api/openclaw/history/route.ts +3 -1
- package/src/app/api/openclaw/permissions/route.ts +3 -1
- package/src/app/api/openclaw/sandbox-env/route.ts +3 -1
- package/src/app/api/openclaw/skills/install/route.ts +3 -1
- package/src/app/api/openclaw/skills/remove/route.ts +3 -1
- package/src/app/api/openclaw/skills/route.ts +5 -2
- package/src/app/api/openclaw/sync/route.ts +5 -3
- package/src/app/api/preview-server/route.ts +4 -1
- package/src/app/api/projects/[id]/route.ts +3 -1
- package/src/app/api/projects/route.ts +3 -1
- package/src/app/api/providers/[id]/models/route.ts +3 -1
- package/src/app/api/providers/[id]/route.ts +3 -1
- package/src/app/api/providers/route.ts +3 -1
- package/src/app/api/schedules/[id]/route.ts +3 -1
- package/src/app/api/schedules/route.ts +3 -1
- package/src/app/api/secrets/[id]/route.ts +3 -1
- package/src/app/api/secrets/route.ts +3 -1
- package/src/app/api/settings/route.ts +3 -1
- package/src/app/api/skills/[id]/route.ts +3 -1
- package/src/app/api/skills/route.ts +3 -1
- package/src/app/api/souls/[id]/route.ts +3 -1
- package/src/app/api/souls/route.ts +3 -1
- package/src/app/api/tasks/[id]/route.ts +26 -1
- package/src/app/api/tasks/bulk/route.ts +3 -1
- package/src/app/api/tasks/claim/route.ts +4 -2
- package/src/app/api/tasks/route.ts +20 -3
- package/src/app/api/uploads/route.ts +3 -1
- package/src/app/api/wallets/[id]/approve/route.ts +3 -1
- package/src/app/api/wallets/[id]/route.ts +4 -2
- package/src/app/api/wallets/[id]/send/route.ts +3 -1
- package/src/app/api/wallets/route.ts +15 -1
- package/src/app/globals.css +20 -0
- package/src/app/org-chart/page.tsx +11 -0
- package/src/cli/index.js +9 -0
- package/src/cli/spec.js +7 -0
- package/src/components/agents/agent-sheet.tsx +41 -7
- package/src/components/chat/markdown-utils.ts +2 -9
- package/src/components/chat/message-bubble.tsx +161 -272
- package/src/components/chatrooms/chatroom-message.tsx +64 -174
- package/src/components/layout/sidebar-rail.tsx +10 -0
- package/src/components/org-chart/mini-chat-bubble.tsx +267 -0
- package/src/components/org-chart/org-chart-context-menu.tsx +138 -0
- package/src/components/org-chart/org-chart-detail-panel.tsx +288 -0
- package/src/components/org-chart/org-chart-edge.tsx +66 -0
- package/src/components/org-chart/org-chart-node.tsx +281 -0
- package/src/components/org-chart/org-chart-sidebar.tsx +552 -0
- package/src/components/org-chart/org-chart-speech-bubble.tsx +37 -0
- package/src/components/org-chart/org-chart-team-panel.tsx +414 -0
- package/src/components/org-chart/org-chart-team-region.tsx +155 -0
- package/src/components/org-chart/org-chart-toolbar.tsx +52 -0
- package/src/components/org-chart/org-chart-view.tsx +885 -0
- package/src/components/org-chart/use-org-chart-drag.ts +87 -0
- package/src/components/org-chart/use-org-chart-pan-zoom.ts +108 -0
- package/src/components/shared/attachment-chip.tsx +57 -0
- package/src/components/shared/markdown-body.tsx +115 -0
- package/src/components/shared/markdown-utils.ts +9 -0
- package/src/components/shared/message-actions.tsx +102 -0
- package/src/lib/agents.ts +3 -0
- package/src/lib/app/navigation.test.ts +74 -0
- package/src/lib/app/navigation.ts +1 -0
- package/src/lib/app/view-constants.ts +9 -1
- package/src/lib/fetch-dedup.test.ts +95 -0
- package/src/lib/org-chart.ts +299 -0
- package/src/lib/personality-parser.test.ts +116 -0
- package/src/lib/providers/anthropic.ts +7 -13
- package/src/lib/providers/ollama.ts +6 -9
- package/src/lib/providers/openai.ts +12 -15
- package/src/lib/providers/openclaw.ts +3 -2
- package/src/lib/providers/provider-defaults.ts +24 -0
- package/src/lib/server/agents/agent-runtime-config.test.ts +7 -1
- package/src/lib/server/agents/capability-match.test.ts +75 -0
- package/src/lib/server/agents/capability-match.ts +47 -0
- package/src/lib/server/agents/main-agent-loop.test.ts +1 -1
- package/src/lib/server/agents/main-agent-loop.ts +16 -2
- package/src/lib/server/agents/subagent-lineage.test.ts +40 -129
- package/src/lib/server/agents/subagent-lineage.ts +17 -69
- package/src/lib/server/agents/subagent-runtime.test.ts +21 -19
- package/src/lib/server/agents/subagent-runtime.ts +35 -9
- package/src/lib/server/build-llm.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution.ts +20 -17
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +53 -1
- package/src/lib/server/chat-execution/chat-turn-state.ts +96 -0
- package/src/lib/server/chat-execution/continuation-evaluator.ts +383 -0
- package/src/lib/server/chat-execution/continuation-limits.test.ts +123 -0
- package/src/lib/server/chat-execution/continuation-limits.ts +116 -0
- package/src/lib/server/chat-execution/iteration-event-handler.ts +360 -0
- package/src/lib/server/chat-execution/iteration-timers.ts +71 -0
- package/src/lib/server/chat-execution/message-classifier.ts +274 -0
- package/src/lib/server/chat-execution/post-stream-finalization.ts +183 -0
- package/src/lib/server/chat-execution/prompt-budget.ts +60 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +456 -0
- package/src/lib/server/chat-execution/prompt-mode.ts +26 -0
- package/src/lib/server/chat-execution/prompt-sections.test.ts +162 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +354 -0
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +190 -11
- package/src/lib/server/chat-execution/stream-agent-chat.ts +310 -1610
- package/src/lib/server/chat-execution/stream-continuation.test.ts +294 -0
- package/src/lib/server/chat-execution/stream-continuation.ts +18 -3
- package/src/lib/server/chatrooms/chatroom-helpers.test.ts +80 -0
- package/src/lib/server/chatrooms/chatroom-helpers.ts +55 -11
- package/src/lib/server/chatrooms/chatroom-memory-bridge.ts +134 -0
- package/src/lib/server/chatrooms/chatroom-routing.ts +2 -1
- package/src/lib/server/connectors/connector-inbound.ts +1498 -0
- package/src/lib/server/connectors/connector-lifecycle.ts +482 -0
- package/src/lib/server/connectors/connector-outbound.ts +404 -0
- package/src/lib/server/connectors/manager.ts +55 -2943
- package/src/lib/server/cost.ts +26 -7
- package/src/lib/server/missions/mission-service.ts +9 -0
- package/src/lib/server/openclaw/sync.ts +4 -3
- package/src/lib/server/protocols/protocol-agent-turn.ts +422 -0
- package/src/lib/server/protocols/protocol-foreach.test.ts +153 -0
- package/src/lib/server/protocols/protocol-foreach.ts +263 -0
- package/src/lib/server/protocols/protocol-normalization.test.ts +316 -0
- package/src/lib/server/protocols/protocol-normalization.ts +574 -0
- package/src/lib/server/protocols/protocol-queries.ts +118 -0
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +649 -0
- package/src/lib/server/protocols/protocol-service.ts +55 -3937
- package/src/lib/server/protocols/protocol-step-helpers.test.ts +144 -0
- package/src/lib/server/protocols/protocol-step-helpers.ts +543 -0
- package/src/lib/server/protocols/protocol-step-processors.ts +727 -0
- package/src/lib/server/protocols/protocol-subflow.ts +244 -0
- package/src/lib/server/protocols/protocol-swarm.ts +353 -0
- package/src/lib/server/protocols/protocol-templates.ts +259 -0
- package/src/lib/server/protocols/protocol-types.ts +149 -0
- package/src/lib/server/provider-health.ts +4 -1
- package/src/lib/server/runtime/heartbeat-service.ts +1 -1
- package/src/lib/server/runtime/queue.ts +70 -23
- package/src/lib/server/safe-parse-body.test.ts +53 -0
- package/src/lib/server/safe-parse-body.ts +16 -0
- package/src/lib/server/session-reset-policy.test.ts +177 -85
- package/src/lib/server/session-reset-policy.ts +1 -1
- package/src/lib/server/session-tools/chatroom.ts +175 -15
- package/src/lib/server/session-tools/context.ts +4 -4
- package/src/lib/server/session-tools/index.ts +2 -0
- package/src/lib/server/session-tools/protocol.ts +286 -0
- package/src/lib/server/storage-auth.ts +71 -0
- package/src/lib/server/storage-cache.ts +142 -0
- package/src/lib/server/storage-locks.ts +67 -0
- package/src/lib/server/storage-normalization.test.ts +219 -0
- package/src/lib/server/storage-normalization.ts +493 -0
- package/src/lib/server/storage.ts +70 -689
- package/src/lib/server/tool-aliases.ts +1 -0
- package/src/lib/server/tool-loop-detection.ts +11 -11
- package/src/stores/set-if-changed.test.ts +72 -0
- package/src/stores/slices/agent-slice.ts +41 -51
- package/src/stores/slices/data-slice.ts +38 -140
- package/src/stores/slices/session-slice.ts +21 -37
- package/src/stores/slices/task-slice.ts +1 -1
- package/src/stores/store-utils.test.ts +128 -0
- package/src/stores/store-utils.ts +50 -0
- package/src/stores/use-chat-store.ts +1 -0
- package/src/stores/use-chatroom-store.ts +13 -0
- package/src/types/index.ts +29 -1
- package/bin/swarmclaw.mjs +0 -1504
package/README.md
CHANGED
|
@@ -177,6 +177,18 @@ The building blocks are the same: **agents, tools, memory, delegation, schedules
|
|
|
177
177
|
|
|
178
178
|
## Release Notes
|
|
179
179
|
|
|
180
|
+
### v1.1.6 Highlights
|
|
181
|
+
|
|
182
|
+
- **Org chart view**: visual agent hierarchy with drag-and-drop reparenting, team grouping, and context-menu actions for managing agent relationships directly from the canvas.
|
|
183
|
+
- **Dashboard API**: server-side metrics endpoint with cost tracking, usage aggregation, and budget warning thresholds for operator visibility.
|
|
184
|
+
- **Subagent lifecycle overhaul**: state-machine lineage tracking, `delegationDepth` limits, auto-announce on spawn, and cleaner parent-child session management.
|
|
185
|
+
- **Chat execution refactor**: composable prompt sections replace monolithic prompt building, continuation evaluator consolidation, and extracted stream-continuation logic for maintainability.
|
|
186
|
+
- **Per-agent cost attribution**: token costs are tracked and attributed per agent, enabling budget controls and cost reporting at the agent level.
|
|
187
|
+
- **Capability-based task routing**: tasks can match agents by declared capabilities, not just explicit assignment, enabling smarter automatic dispatch.
|
|
188
|
+
- **Bulk agent operations**: new `/api/agents/bulk` endpoint for batch updates across multiple agents in a single request.
|
|
189
|
+
- **Document revisions API**: version history for documents with `/api/documents/[id]/revisions` endpoint.
|
|
190
|
+
- **Store loader consolidation**: async loaders now use `createLoader()` and `setIfChanged` to eliminate redundant re-renders from polling.
|
|
191
|
+
|
|
180
192
|
### v1.1.4 Highlights
|
|
181
193
|
|
|
182
194
|
- **Orchestrator agents return as a first-class autonomy mode**: eligible agents can now run scheduled orchestrator wake cycles with their own mission, governance policy, wake interval, cycle cap, Autonomy-desk controls, and setup/editor support.
|
|
@@ -189,6 +201,19 @@ The building blocks are the same: **agents, tools, memory, delegation, schedules
|
|
|
189
201
|
- **Release integrity repair**: `build:ci` no longer trips over the langgraph checkpoint duplicate-column path, which restores clean build validation for the release line.
|
|
190
202
|
- **Storage writes are safer**: credential and agent saves were tightened to upsert-only behavior and bulk-delete safety guards so tests or scripts cannot accidentally wipe live state.
|
|
191
203
|
- **Plugin-to-extension cleanup finished**: remaining rename residue in scripts and tests was cleaned up so packaging and release tooling stay aligned with the current extensions model.
|
|
204
|
+
- **Safe body parsing utility**: shared `safeParseBody()` replaces scattered `await req.json()` try/catch blocks across API routes.
|
|
205
|
+
|
|
206
|
+
### v1.1.4 Highlights
|
|
207
|
+
|
|
208
|
+
- **Orchestrator agents as a real agent mode**: eligible agents can now run scheduled orchestrator wake cycles with their own mission, governance policy, wake interval, and cycle cap.
|
|
209
|
+
- **Runtime durability and recovery**: configurable parallel task execution, stuck-task idle timeout detection, orphaned running-task recovery on startup, and restart-safe swarm/provider-health persistence.
|
|
210
|
+
- **Failover and safety improvements**: provider errors classified for smarter routing, agent budget limits block task execution before it starts.
|
|
211
|
+
|
|
212
|
+
### v1.1.3 Highlights
|
|
213
|
+
|
|
214
|
+
- **`build:ci` repair**: fixed the langgraph checkpoint duplicate-column crash that blocked CI/build validation.
|
|
215
|
+
- **Safer storage writes**: credentials and agents use upsert-only save behavior, and a collection safety guard blocks accidental bulk-delete paths.
|
|
216
|
+
>>>>>>> Stashed changes
|
|
192
217
|
|
|
193
218
|
### v1.1.2 Highlights
|
|
194
219
|
|
package/bin/install-root.js
CHANGED
|
@@ -98,11 +98,14 @@ function tryRealpath(targetPath) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
const isWindows = process.platform === 'win32'
|
|
102
|
+
|
|
101
103
|
function runRootCommand(command, args, execImpl = execFileSync) {
|
|
102
104
|
try {
|
|
103
105
|
return String(execImpl(command, args, {
|
|
104
106
|
encoding: 'utf8',
|
|
105
107
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
108
|
+
...(isWindows && { shell: true }),
|
|
106
109
|
})).trim()
|
|
107
110
|
} catch {
|
|
108
111
|
return null
|
package/bin/server-cmd.js
CHANGED
|
@@ -154,6 +154,8 @@ function resolveInstalledNext(pkgRoot = PKG_ROOT) {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
const isWindows = process.platform === 'win32'
|
|
158
|
+
|
|
157
159
|
function ensurePackageDependencies(pkgRoot = PKG_ROOT) {
|
|
158
160
|
const resolved = resolveInstalledNext(pkgRoot)
|
|
159
161
|
if (resolved && fs.existsSync(resolved.nextCli)) return resolved
|
|
@@ -161,7 +163,7 @@ function ensurePackageDependencies(pkgRoot = PKG_ROOT) {
|
|
|
161
163
|
const packageManager = detectPackageManager(pkgRoot, process.env)
|
|
162
164
|
const install = getInstallCommand(packageManager)
|
|
163
165
|
log(`Installing dependencies with ${packageManager}...`)
|
|
164
|
-
execFileSync(install.command, install.args, { cwd: pkgRoot, stdio: 'inherit' })
|
|
166
|
+
execFileSync(install.command, install.args, { cwd: pkgRoot, stdio: 'inherit', ...(isWindows && { shell: true }) })
|
|
165
167
|
|
|
166
168
|
const installed = resolveInstalledNext(pkgRoot)
|
|
167
169
|
if (installed && fs.existsSync(installed.nextCli)) return installed
|
package/bin/update-cmd.js
CHANGED
|
@@ -16,6 +16,8 @@ const {
|
|
|
16
16
|
resolvePackageRoot,
|
|
17
17
|
} = require('./install-root.js')
|
|
18
18
|
|
|
19
|
+
const isWindows = process.platform === 'win32'
|
|
20
|
+
|
|
19
21
|
const PKG_ROOT = resolvePackageRoot({
|
|
20
22
|
moduleDir: __dirname,
|
|
21
23
|
argv1: process.argv[1],
|
|
@@ -83,6 +85,7 @@ function runRegistrySelfUpdate(
|
|
|
83
85
|
cwd: PKG_ROOT,
|
|
84
86
|
stdio: 'inherit',
|
|
85
87
|
timeout: 120_000,
|
|
88
|
+
...(isWindows && { shell: true }),
|
|
86
89
|
})
|
|
87
90
|
logger.log(`Global update complete via ${packageManager}.`)
|
|
88
91
|
} catch (err) {
|
|
@@ -170,7 +173,7 @@ If running from a registry install, update the global package with its owning pa
|
|
|
170
173
|
const packageManager = detectPackageManager(PKG_ROOT, process.env)
|
|
171
174
|
const install = getInstallCommand(packageManager, true)
|
|
172
175
|
log(`Package files changed — running ${packageManager} install...`)
|
|
173
|
-
execFileSync(install.command, install.args, { cwd: PKG_ROOT, stdio: 'inherit', timeout: 120_000 })
|
|
176
|
+
execFileSync(install.command, install.args, { cwd: PKG_ROOT, stdio: 'inherit', timeout: 120_000, ...(isWindows && { shell: true }) })
|
|
174
177
|
}
|
|
175
178
|
} catch {
|
|
176
179
|
// If diff fails, skip install check.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
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": {
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/postinstall.test.mjs",
|
|
70
70
|
"test:setup": "tsx --test src/app/api/setup/check-provider/route.test.ts src/lib/server/provider-model-discovery.test.ts src/components/auth/setup-wizard/utils.test.ts src/components/auth/setup-wizard/types.test.ts src/hooks/setup-done-detection.test.ts src/lib/setup-defaults.test.ts",
|
|
71
71
|
"test:openclaw": "tsx --test src/lib/openclaw/openclaw-agent-id.test.ts src/lib/openclaw/openclaw-endpoint.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw/agent-resolver.test.ts src/lib/server/openclaw/deploy.test.ts src/lib/server/openclaw/skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/tasks/task-quality-gate.test.ts src/lib/server/tasks/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/providers/openclaw-exports.test.ts src/app/api/openclaw/dashboard-url/route.test.ts",
|
|
72
|
+
"test:e2e": "tsx .workbench/browser-e2e/run.ts",
|
|
72
73
|
"test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
|
|
73
74
|
"prepack": "npm run build:ci",
|
|
74
75
|
"postinstall": "node ./scripts/postinstall.mjs"
|
package/scripts/easy-setup.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import fs from 'node:fs'
|
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { spawnSync } from 'node:child_process'
|
|
6
6
|
|
|
7
|
+
const isWindows = process.platform === 'win32'
|
|
7
8
|
const args = new Set(process.argv.slice(2))
|
|
8
9
|
const startAfterSetup = args.has('--start') || args.has('--prod')
|
|
9
10
|
const productionMode = args.has('--prod')
|
|
@@ -25,6 +26,7 @@ function run(command, commandArgs, options = {}) {
|
|
|
25
26
|
const result = spawnSync(command, commandArgs, {
|
|
26
27
|
cwd,
|
|
27
28
|
stdio: 'inherit',
|
|
29
|
+
...(isWindows && { shell: true }),
|
|
28
30
|
...options,
|
|
29
31
|
})
|
|
30
32
|
if (result.error) fail(result.error.message)
|
|
@@ -39,6 +41,7 @@ function runOptional(command, commandArgs, options = {}) {
|
|
|
39
41
|
const result = spawnSync(command, commandArgs, {
|
|
40
42
|
cwd,
|
|
41
43
|
stdio: 'inherit',
|
|
44
|
+
...(isWindows && { shell: true }),
|
|
42
45
|
...options,
|
|
43
46
|
})
|
|
44
47
|
if (result.error || (result.status ?? 1) !== 0) {
|
|
@@ -60,7 +63,7 @@ function ensureNodeVersion() {
|
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
function ensureNpm() {
|
|
63
|
-
const result = spawnSync('npm', ['--version'], { cwd, encoding: 'utf8' })
|
|
66
|
+
const result = spawnSync('npm', ['--version'], { cwd, encoding: 'utf8', ...(isWindows && { shell: true }) })
|
|
64
67
|
if (result.error || (result.status ?? 1) !== 0) {
|
|
65
68
|
fail('npm was not found. Install npm and rerun this setup command.')
|
|
66
69
|
}
|
|
@@ -69,7 +72,7 @@ function ensureNpm() {
|
|
|
69
72
|
|
|
70
73
|
function commandExists(name) {
|
|
71
74
|
const lookup = process.platform === 'win32' ? 'where' : 'which'
|
|
72
|
-
const result = spawnSync(lookup, [name], { cwd, encoding: 'utf8' })
|
|
75
|
+
const result = spawnSync(lookup, [name], { cwd, encoding: 'utf8', ...(isWindows && { shell: true }) })
|
|
73
76
|
return !result.error && (result.status ?? 1) === 0
|
|
74
77
|
}
|
|
75
78
|
|
|
@@ -6,6 +6,7 @@ export async function GET(req: Request) {
|
|
|
6
6
|
const { searchParams } = new URL(req.url)
|
|
7
7
|
const entityType = searchParams.get('entityType')
|
|
8
8
|
const entityId = searchParams.get('entityId')
|
|
9
|
+
const actor = searchParams.get('actor')
|
|
9
10
|
const action = searchParams.get('action')
|
|
10
11
|
const since = searchParams.get('since')
|
|
11
12
|
const limit = Math.min(200, Math.max(1, Number(searchParams.get('limit')) || 50))
|
|
@@ -15,6 +16,7 @@ export async function GET(req: Request) {
|
|
|
15
16
|
|
|
16
17
|
if (entityType) entries = entries.filter((e) => e.entityType === entityType)
|
|
17
18
|
if (entityId) entries = entries.filter((e) => e.entityId === entityId)
|
|
19
|
+
if (actor) entries = entries.filter((e) => e.actor === actor)
|
|
18
20
|
if (action) entries = entries.filter((e) => e.action === action)
|
|
19
21
|
if (since) {
|
|
20
22
|
const sinceMs = Number(since)
|
|
@@ -8,13 +8,15 @@ import { notify } from '@/lib/server/ws-hub'
|
|
|
8
8
|
import { normalizeAgentSandboxConfig } from '@/lib/agent-sandbox-defaults'
|
|
9
9
|
import { normalizeCapabilitySelection } from '@/lib/capability-selection'
|
|
10
10
|
import { normalizeOrchestratorConfig } from '@/lib/orchestrator-config'
|
|
11
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
11
12
|
|
|
12
13
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
14
|
const ops: CollectionOps<any> = { load: () => loadAgents({ includeTrashed: true }), save: saveAgents, topic: 'agents', table: 'agents' }
|
|
14
15
|
|
|
15
16
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
16
17
|
const { id } = await params
|
|
17
|
-
const body = await req
|
|
18
|
+
const { data: body, error } = await safeParseBody(req)
|
|
19
|
+
if (error) return error
|
|
18
20
|
const result = mutateItem(ops, id, (agent) => {
|
|
19
21
|
Object.assign(agent, body, { updatedAt: Date.now() })
|
|
20
22
|
if (body.tools !== undefined || body.extensions !== undefined) {
|
|
@@ -42,7 +44,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
42
44
|
if (body.apiEndpoint !== undefined) {
|
|
43
45
|
agent.apiEndpoint = normalizeProviderEndpoint(
|
|
44
46
|
body.provider || agent.provider,
|
|
45
|
-
body.apiEndpoint,
|
|
47
|
+
body.apiEndpoint as string | null | undefined,
|
|
46
48
|
)
|
|
47
49
|
}
|
|
48
50
|
if (body.provider !== undefined && body.provider !== 'ollama' && body.ollamaMode === undefined) {
|
|
@@ -111,7 +113,6 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
111
113
|
}
|
|
112
114
|
delete (agent as Record<string, unknown>).platformAssignScope
|
|
113
115
|
delete (agent as Record<string, unknown>).subAgentIds
|
|
114
|
-
delete (agent as Record<string, unknown>).isOrchestrator
|
|
115
116
|
delete (agent as Record<string, unknown>).id
|
|
116
117
|
agent.id = id
|
|
117
118
|
return agent
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
3
|
+
import { patchAgent } from '@/lib/server/storage'
|
|
4
|
+
import { logActivity } from '@/lib/server/storage'
|
|
5
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
6
|
+
|
|
7
|
+
export async function PATCH(req: Request) {
|
|
8
|
+
const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
|
|
9
|
+
if (error) return error
|
|
10
|
+
|
|
11
|
+
const patches = body.patches
|
|
12
|
+
if (!Array.isArray(patches) || patches.length === 0) {
|
|
13
|
+
return NextResponse.json({ error: 'patches must be a non-empty array' }, { status: 400 })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let updated = 0
|
|
17
|
+
const errors: string[] = []
|
|
18
|
+
|
|
19
|
+
for (const entry of patches) {
|
|
20
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
21
|
+
errors.push('Invalid patch entry (not an object)')
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
const { id, patch } = entry as { id?: unknown; patch?: unknown }
|
|
25
|
+
if (typeof id !== 'string' || !id.trim()) {
|
|
26
|
+
errors.push('Patch entry missing valid id')
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
|
|
30
|
+
errors.push(`Patch for ${id} is not a valid object`)
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const result = patchAgent(id, (current) => {
|
|
35
|
+
if (!current) return null
|
|
36
|
+
return { ...current, ...(patch as Record<string, unknown>), updatedAt: Date.now() }
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
if (result) {
|
|
40
|
+
updated++
|
|
41
|
+
logActivity({
|
|
42
|
+
entityType: 'agent',
|
|
43
|
+
entityId: id,
|
|
44
|
+
action: 'updated',
|
|
45
|
+
actor: 'user',
|
|
46
|
+
summary: `Bulk patch: updated agent "${result.name || id}"`,
|
|
47
|
+
})
|
|
48
|
+
} else {
|
|
49
|
+
errors.push(`Agent ${id} not found`)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (updated > 0) notify('agents')
|
|
54
|
+
return NextResponse.json({ updated, errors })
|
|
55
|
+
}
|
|
@@ -10,6 +10,7 @@ import { normalizeAgentSandboxConfig } from '@/lib/agent-sandbox-defaults'
|
|
|
10
10
|
import { normalizeOrchestratorConfig } from '@/lib/orchestrator-config'
|
|
11
11
|
import { AgentCreateSchema, formatZodError } from '@/lib/validation/schemas'
|
|
12
12
|
import { z } from 'zod'
|
|
13
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
13
14
|
export const dynamic = 'force-dynamic'
|
|
14
15
|
|
|
15
16
|
async function ensureDaemonIfNeeded(source: string) {
|
|
@@ -57,7 +58,8 @@ export async function GET(req: Request) {
|
|
|
57
58
|
|
|
58
59
|
export async function POST(req: Request) {
|
|
59
60
|
await ensureDaemonIfNeeded('api/agents:post')
|
|
60
|
-
const raw = await req
|
|
61
|
+
const { data: raw, error } = await safeParseBody(req)
|
|
62
|
+
if (error) return error
|
|
61
63
|
const rawRecord = raw && typeof raw === 'object' ? raw as Record<string, unknown> : null
|
|
62
64
|
const parsed = AgentCreateSchema.safeParse(raw)
|
|
63
65
|
if (!parsed.success) {
|
|
@@ -3,6 +3,7 @@ import { loadTrashedAgents, loadAgents, saveAgents, deleteAgent } from '@/lib/se
|
|
|
3
3
|
import { notify } from '@/lib/server/ws-hub'
|
|
4
4
|
import { badRequest, notFound } from '@/lib/server/collection-helpers'
|
|
5
5
|
import { purgeAgentReferences, restoreAgentSchedules } from '@/lib/server/agents/agent-cascade'
|
|
6
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
6
7
|
|
|
7
8
|
/** GET — list trashed agents */
|
|
8
9
|
export async function GET() {
|
|
@@ -11,7 +12,8 @@ export async function GET() {
|
|
|
11
12
|
|
|
12
13
|
/** POST { id } — restore a trashed agent */
|
|
13
14
|
export async function POST(req: Request) {
|
|
14
|
-
const body = await req
|
|
15
|
+
const { data: body, error } = await safeParseBody(req)
|
|
16
|
+
if (error) return error
|
|
15
17
|
const id = body?.id as string | undefined
|
|
16
18
|
if (!id) return badRequest('Missing agent id')
|
|
17
19
|
|
|
@@ -35,7 +37,8 @@ export async function POST(req: Request) {
|
|
|
35
37
|
|
|
36
38
|
/** DELETE { id } — permanently delete a trashed agent */
|
|
37
39
|
export async function DELETE(req: Request) {
|
|
38
|
-
const body = await req
|
|
40
|
+
const { data: body, error: parseError } = await safeParseBody(req)
|
|
41
|
+
if (parseError) return parseError
|
|
39
42
|
const id = body?.id as string | undefined
|
|
40
43
|
if (!id) return badRequest('Missing agent id')
|
|
41
44
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
2
3
|
import { getAccessKey, validateAccessKey, isFirstTimeSetup, markSetupComplete, replaceAccessKey } from '@/lib/server/storage'
|
|
3
4
|
import { AUTH_COOKIE_NAME, getCookieValue } from '@/lib/auth'
|
|
4
5
|
import { isProductionRuntime } from '@/lib/runtime/runtime-env'
|
|
@@ -65,9 +66,19 @@ export async function GET(req: Request) {
|
|
|
65
66
|
})
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
function pruneExpiredEntries() {
|
|
70
|
+
const now = Date.now()
|
|
71
|
+
for (const [ip, entry] of authRateLimitMap) {
|
|
72
|
+
if (entry.lockedUntil > 0 && entry.lockedUntil < now) {
|
|
73
|
+
authRateLimitMap.delete(ip)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
68
78
|
/** POST /api/auth — validate an access key */
|
|
69
79
|
export async function POST(req: Request) {
|
|
70
80
|
const rateLimitEnabled = isRateLimitEnabled()
|
|
81
|
+
if (rateLimitEnabled) pruneExpiredEntries()
|
|
71
82
|
const clientIp = getClientIp(req)
|
|
72
83
|
const entry = rateLimitEnabled ? authRateLimitMap.get(clientIp) : undefined
|
|
73
84
|
if (rateLimitEnabled && entry && entry.lockedUntil > Date.now()) {
|
|
@@ -78,7 +89,9 @@ export async function POST(req: Request) {
|
|
|
78
89
|
))
|
|
79
90
|
}
|
|
80
91
|
|
|
81
|
-
const {
|
|
92
|
+
const { data: body, error } = await safeParseBody<{ key: string; override?: boolean }>(req)
|
|
93
|
+
if (error) return error
|
|
94
|
+
const { key, override } = body
|
|
82
95
|
|
|
83
96
|
// During first-time setup, allow the user to replace the generated key with their own
|
|
84
97
|
if (override && isFirstTimeSetup() && typeof key === 'string' && key.trim().length >= 8) {
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { loadSessions, saveSessions } from '@/lib/server/storage'
|
|
3
3
|
import { notify } from '@/lib/server/ws-hub'
|
|
4
4
|
import { normalizeCanvasContent } from '@/lib/canvas-content'
|
|
5
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
5
6
|
|
|
6
7
|
export async function GET(_req: Request, { params }: { params: Promise<{ sessionId: string }> }) {
|
|
7
8
|
const { sessionId } = await params
|
|
@@ -17,7 +18,8 @@ export async function GET(_req: Request, { params }: { params: Promise<{ session
|
|
|
17
18
|
|
|
18
19
|
export async function POST(req: Request, { params }: { params: Promise<{ sessionId: string }> }) {
|
|
19
20
|
const { sessionId } = await params
|
|
20
|
-
const body = await req
|
|
21
|
+
const { data: body, error } = await safeParseBody(req)
|
|
22
|
+
if (error) return error
|
|
21
23
|
const sessions = loadSessions()
|
|
22
24
|
const session = sessions[sessionId]
|
|
23
25
|
if (!session) return NextResponse.json({ error: 'Session not found' }, { status: 404 })
|
|
@@ -3,6 +3,7 @@ import { genId } from '@/lib/id'
|
|
|
3
3
|
import { loadChatrooms, saveChatrooms, loadAgents } from '@/lib/server/storage'
|
|
4
4
|
import { notify } from '@/lib/server/ws-hub'
|
|
5
5
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
6
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
6
7
|
import { streamAgentChat } from '@/lib/server/chat-execution/stream-agent-chat'
|
|
7
8
|
import { getProvider } from '@/lib/providers'
|
|
8
9
|
import {
|
|
@@ -26,6 +27,7 @@ import { resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-conf
|
|
|
26
27
|
import { shouldSuppressHiddenControlText, stripHiddenControlTokens } from '@/lib/server/agents/assistant-control'
|
|
27
28
|
import type { Chatroom, ChatroomMessage, Agent } from '@/types'
|
|
28
29
|
import { errorMessage } from '@/lib/shared-utils'
|
|
30
|
+
import { persistChatroomInteractionMemory } from '@/lib/server/chatrooms/chatroom-memory-bridge'
|
|
29
31
|
|
|
30
32
|
export const dynamic = 'force-dynamic'
|
|
31
33
|
export const maxDuration = 300
|
|
@@ -34,7 +36,8 @@ const MAX_CHAIN_DEPTH = 5
|
|
|
34
36
|
|
|
35
37
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
36
38
|
const { id } = await params
|
|
37
|
-
const body = await req
|
|
39
|
+
const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
|
|
40
|
+
if (error) return error
|
|
38
41
|
|
|
39
42
|
const chatrooms = loadChatrooms()
|
|
40
43
|
const chatroom = chatrooms[id] as Chatroom | undefined
|
|
@@ -57,7 +60,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
57
60
|
// Persist incoming message
|
|
58
61
|
const senderName = senderId === 'user' ? 'You' : (agents[senderId]?.name || senderId)
|
|
59
62
|
const replyTargetAgentId = resolveReplyTargetAgentId(replyToId, chatroom.messages, chatroom.agentIds)
|
|
60
|
-
let mentions = parseMentions(text, agents, chatroom.agentIds, { replyTargetAgentId })
|
|
63
|
+
let mentions = parseMentions(text, agents, chatroom.agentIds, { replyTargetAgentId, senderId: senderId !== 'user' ? senderId : null })
|
|
61
64
|
// Routing rules: if no explicit mentions, evaluate keyword/capability rules
|
|
62
65
|
if (mentions.length === 0 && chatroom.routingRules?.length) {
|
|
63
66
|
const agentList = chatroom.agentIds.map((aid) => agents[aid]).filter(Boolean)
|
|
@@ -67,6 +70,11 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
67
70
|
if (chatroom.autoAddress && mentions.length === 0) {
|
|
68
71
|
mentions = [...chatroom.agentIds]
|
|
69
72
|
}
|
|
73
|
+
// If a specific agent is targeted, ensure they're in the mentions
|
|
74
|
+
const incomingTargetAgentId = typeof body.targetAgentId === 'string' ? body.targetAgentId : undefined
|
|
75
|
+
if (incomingTargetAgentId && chatroom.agentIds.includes(incomingTargetAgentId) && !mentions.includes(incomingTargetAgentId)) {
|
|
76
|
+
mentions.push(incomingTargetAgentId)
|
|
77
|
+
}
|
|
70
78
|
const mentionHealth = filterHealthyChatroomAgents(mentions, agents)
|
|
71
79
|
mentions = mentionHealth.healthyAgentIds
|
|
72
80
|
const userMessage: ChatroomMessage = {
|
|
@@ -81,6 +89,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
81
89
|
...(imagePath ? { imagePath } : {}),
|
|
82
90
|
...(attachedFiles ? { attachedFiles } : {}),
|
|
83
91
|
...(replyToId ? { replyToId } : {}),
|
|
92
|
+
...(incomingTargetAgentId ? { targetAgentId: incomingTargetAgentId } : {}),
|
|
84
93
|
}
|
|
85
94
|
chatroom.messages.push(userMessage)
|
|
86
95
|
compactChatroomMessages(chatroom)
|
|
@@ -90,6 +99,20 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
90
99
|
notify('chatrooms')
|
|
91
100
|
notify(`chatroom:${id}`)
|
|
92
101
|
|
|
102
|
+
// If sender is an agent (via triggerResponses tool), just persist the message — don't re-process agents
|
|
103
|
+
if (senderId !== 'user' && agents[senderId]) {
|
|
104
|
+
const encoder = new TextEncoder()
|
|
105
|
+
const noopStream = new ReadableStream({
|
|
106
|
+
start(controller) {
|
|
107
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ t: 'done' })}\n\n`))
|
|
108
|
+
controller.close()
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
return new NextResponse(noopStream, {
|
|
112
|
+
headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'X-Accel-Buffering': 'no' },
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
93
116
|
// Build reply context if replying to a message
|
|
94
117
|
let replyContext = ''
|
|
95
118
|
if (replyToId) {
|
|
@@ -243,7 +266,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
243
266
|
|
|
244
267
|
if (responseText.trim() && !shouldSuppressHiddenControlText(rawResponseText)) {
|
|
245
268
|
appendSyntheticSessionMessage(syntheticSession.id, 'assistant', responseText)
|
|
246
|
-
const parsedMentions = parseMentions(responseText, agents, freshChatroom.agentIds)
|
|
269
|
+
const parsedMentions = parseMentions(responseText, agents, freshChatroom.agentIds, { senderId: agent.id, skipImplicit: true })
|
|
247
270
|
const chainedHealth = filterHealthyChatroomAgents(parsedMentions, agents)
|
|
248
271
|
const newMentions = chainedHealth.healthyAgentIds
|
|
249
272
|
if (chainedHealth.skipped.length > 0) {
|
|
@@ -273,6 +296,17 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
273
296
|
// Extract and apply reactions (e.g. [REACTION]{"emoji":"👍","to":"..."})
|
|
274
297
|
applyAgentReactionsFromText(responseText, id, agent.id)
|
|
275
298
|
|
|
299
|
+
// Persist interaction to agent memory (fire-and-forget)
|
|
300
|
+
persistChatroomInteractionMemory({
|
|
301
|
+
agentId: agent.id,
|
|
302
|
+
agent,
|
|
303
|
+
chatroomId: id,
|
|
304
|
+
chatroomName: chatroom.name,
|
|
305
|
+
senderName,
|
|
306
|
+
inboundText: text,
|
|
307
|
+
responseText,
|
|
308
|
+
}).catch(() => {})
|
|
309
|
+
|
|
276
310
|
markProviderSuccess(agent.provider)
|
|
277
311
|
writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
|
|
278
312
|
|
|
@@ -319,7 +353,12 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
319
353
|
)
|
|
320
354
|
if (lastAgentMsg) {
|
|
321
355
|
const truncated = lastAgentMsg.text.length > 500 ? lastAgentMsg.text.slice(0, 500) + '...' : lastAgentMsg.text
|
|
322
|
-
|
|
356
|
+
const originalTruncated = text.length > 300 ? text.slice(0, 300) + '...' : text
|
|
357
|
+
item.contextMessage = [
|
|
358
|
+
`[Conversation context] The user said: "${originalTruncated}"`,
|
|
359
|
+
`${lastAgentMsg.senderName} then said: "${truncated}"`,
|
|
360
|
+
`They mentioned you — respond to the conversation naturally.`,
|
|
361
|
+
].join('\n')
|
|
323
362
|
}
|
|
324
363
|
}
|
|
325
364
|
|
|
@@ -2,16 +2,18 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { loadChatrooms, saveChatrooms, loadAgents } from '@/lib/server/storage'
|
|
3
3
|
import { notify } from '@/lib/server/ws-hub'
|
|
4
4
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
5
6
|
import { genId } from '@/lib/id'
|
|
6
7
|
|
|
7
8
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
9
|
const { id } = await params
|
|
9
|
-
const body = await req
|
|
10
|
+
const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
|
|
11
|
+
if (error) return error
|
|
10
12
|
const chatrooms = loadChatrooms()
|
|
11
13
|
const chatroom = chatrooms[id]
|
|
12
14
|
if (!chatroom) return notFound()
|
|
13
15
|
|
|
14
|
-
const agentId = body.agentId
|
|
16
|
+
const agentId = typeof body.agentId === 'string' ? body.agentId : ''
|
|
15
17
|
if (!agentId) return NextResponse.json({ error: 'agentId is required' }, { status: 400 })
|
|
16
18
|
|
|
17
19
|
if (!chatroom.agentIds.includes(agentId)) {
|
|
@@ -44,12 +46,13 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
44
46
|
|
|
45
47
|
export async function DELETE(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
46
48
|
const { id } = await params
|
|
47
|
-
const body = await req
|
|
49
|
+
const { data: body, error: delError } = await safeParseBody<Record<string, unknown>>(req)
|
|
50
|
+
if (delError) return delError
|
|
48
51
|
const chatrooms = loadChatrooms()
|
|
49
52
|
const chatroom = chatrooms[id]
|
|
50
53
|
if (!chatroom) return notFound()
|
|
51
54
|
|
|
52
|
-
const agentId = body.agentId
|
|
55
|
+
const agentId = typeof body.agentId === 'string' ? body.agentId : ''
|
|
53
56
|
if (!agentId) return NextResponse.json({ error: 'agentId is required' }, { status: 400 })
|
|
54
57
|
|
|
55
58
|
const wasPresent = chatroom.agentIds.includes(agentId)
|
|
@@ -3,6 +3,7 @@ import crypto from 'crypto'
|
|
|
3
3
|
import { loadChatrooms, saveChatrooms, appendModerationLog } from '@/lib/server/storage'
|
|
4
4
|
import { notify } from '@/lib/server/ws-hub'
|
|
5
5
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
6
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
6
7
|
import { getMembers } from '@/lib/server/chatrooms/chatroom-helpers'
|
|
7
8
|
import type { Chatroom, ChatroomMember } from '@/types'
|
|
8
9
|
|
|
@@ -26,7 +27,8 @@ function isValidRole(role: unknown): role is ChatroomMember['role'] {
|
|
|
26
27
|
|
|
27
28
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
28
29
|
const { id } = await params
|
|
29
|
-
const body = await
|
|
30
|
+
const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
|
|
31
|
+
if (error) return error
|
|
30
32
|
|
|
31
33
|
const chatrooms = loadChatrooms()
|
|
32
34
|
const chatroom = chatrooms[id] as Chatroom | undefined
|
|
@@ -2,16 +2,18 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { loadChatrooms, saveChatrooms } from '@/lib/server/storage'
|
|
3
3
|
import { notify } from '@/lib/server/ws-hub'
|
|
4
4
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
5
6
|
import type { Chatroom } from '@/types'
|
|
6
7
|
|
|
7
8
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
9
|
const { id } = await params
|
|
9
|
-
const body = await req
|
|
10
|
+
const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
|
|
11
|
+
if (error) return error
|
|
10
12
|
const chatrooms = loadChatrooms()
|
|
11
13
|
const chatroom = chatrooms[id] as Chatroom | undefined
|
|
12
14
|
if (!chatroom) return notFound()
|
|
13
15
|
|
|
14
|
-
const messageId = body.messageId
|
|
16
|
+
const messageId = typeof body.messageId === 'string' ? body.messageId : ''
|
|
15
17
|
if (!messageId) {
|
|
16
18
|
return NextResponse.json({ error: 'messageId is required' }, { status: 400 })
|
|
17
19
|
}
|
|
@@ -2,18 +2,20 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { loadChatrooms, saveChatrooms } from '@/lib/server/storage'
|
|
3
3
|
import { notify } from '@/lib/server/ws-hub'
|
|
4
4
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
5
6
|
import type { Chatroom, ChatroomMessage, ChatroomReaction } from '@/types'
|
|
6
7
|
|
|
7
8
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
9
|
const { id } = await params
|
|
9
|
-
const body = await req
|
|
10
|
+
const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
|
|
11
|
+
if (error) return error
|
|
10
12
|
const chatrooms = loadChatrooms()
|
|
11
13
|
const chatroom = chatrooms[id] as Chatroom | undefined
|
|
12
14
|
if (!chatroom) return notFound()
|
|
13
15
|
|
|
14
|
-
const messageId = body.messageId
|
|
15
|
-
const emoji = body.emoji
|
|
16
|
-
const reactorId =
|
|
16
|
+
const messageId = typeof body.messageId === 'string' ? body.messageId : ''
|
|
17
|
+
const emoji = typeof body.emoji === 'string' ? body.emoji : ''
|
|
18
|
+
const reactorId = typeof body.reactorId === 'string' && body.reactorId ? body.reactorId : 'user'
|
|
17
19
|
if (!messageId || !emoji) {
|
|
18
20
|
return NextResponse.json({ error: 'messageId and emoji are required' }, { status: 400 })
|
|
19
21
|
}
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { loadChatrooms, saveChatrooms, loadAgents, loadConnectors, saveConnectors } from '@/lib/server/storage'
|
|
3
3
|
import { notify } from '@/lib/server/ws-hub'
|
|
4
4
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
5
6
|
import { genId } from '@/lib/id'
|
|
6
7
|
|
|
7
8
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -14,7 +15,8 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str
|
|
|
14
15
|
|
|
15
16
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
16
17
|
const { id } = await params
|
|
17
|
-
const body = await req
|
|
18
|
+
const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
|
|
19
|
+
if (error) return error
|
|
18
20
|
const chatrooms = loadChatrooms()
|
|
19
21
|
const chatroom = chatrooms[id]
|
|
20
22
|
if (!chatroom) return notFound()
|
|
@@ -40,7 +42,8 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
40
42
|
)
|
|
41
43
|
}
|
|
42
44
|
const agents = loadAgents()
|
|
43
|
-
const
|
|
45
|
+
const agentIds = (body.agentIds as unknown[]).filter((v): v is string => typeof v === 'string' && v.trim().length > 0)
|
|
46
|
+
const invalidAgentIds = agentIds.filter((agentId) => !agents[agentId])
|
|
44
47
|
if (invalidAgentIds.length > 0) {
|
|
45
48
|
return NextResponse.json(
|
|
46
49
|
{ error: `Unknown chatroom member(s): ${invalidAgentIds.join(', ')}` },
|
|
@@ -49,8 +52,8 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
const oldIds = new Set(chatroom.agentIds)
|
|
52
|
-
const newIds = new Set(
|
|
53
|
-
const added =
|
|
55
|
+
const newIds = new Set(agentIds)
|
|
56
|
+
const added = agentIds.filter((aid: string) => !oldIds.has(aid))
|
|
54
57
|
const removed = chatroom.agentIds.filter((aid: string) => !newIds.has(aid))
|
|
55
58
|
|
|
56
59
|
if (added.length > 0 || removed.length > 0) {
|
|
@@ -83,7 +86,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
chatroom.agentIds =
|
|
89
|
+
chatroom.agentIds = agentIds
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
chatroom.updatedAt = Date.now()
|