@swarmclawai/swarmclaw 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -150
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +26 -0
- package/src/app/api/agents/[id]/thread/route.ts +37 -9
- package/src/app/api/agents/route.ts +13 -2
- package/src/app/api/auth/route.ts +76 -7
- package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
- package/src/app/api/{sessions → chats}/[id]/browser/route.ts +5 -1
- package/src/app/api/{sessions → chats}/[id]/chat/route.ts +7 -3
- package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
- package/src/app/api/chats/[id]/main-loop/route.ts +13 -0
- package/src/app/api/{sessions → chats}/[id]/messages/route.ts +19 -13
- package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/route.ts +22 -52
- package/src/app/api/{sessions → chats}/[id]/stop/route.ts +6 -1
- package/src/app/api/{sessions → chats}/route.ts +21 -7
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/skills/route.ts +11 -3
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +6 -26
- package/src/app/api/plugins/settings/route.ts +40 -0
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/settings/route.ts +49 -7
- package/src/app/api/tasks/[id]/route.ts +15 -6
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/usage/route.ts +30 -0
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +9 -2
- package/src/cli/index.js +39 -33
- package/src/cli/index.ts +43 -49
- package/src/cli/spec.js +29 -27
- package/src/components/agents/agent-card.tsx +16 -13
- package/src/components/agents/agent-chat-list.tsx +104 -4
- package/src/components/agents/agent-list.tsx +54 -22
- package/src/components/agents/agent-sheet.tsx +209 -18
- package/src/components/agents/cron-job-form.tsx +3 -3
- package/src/components/agents/inspector-panel.tsx +110 -50
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +5 -38
- package/src/components/chat/chat-area.tsx +39 -27
- package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +7 -23
- package/src/components/chat/chat-header.tsx +299 -314
- package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +11 -14
- package/src/components/chat/chat-tool-toggles.tsx +26 -17
- package/src/components/chat/checkpoint-timeline.tsx +4 -4
- package/src/components/chat/message-bubble.tsx +4 -1
- package/src/components/chat/message-list.tsx +5 -3
- package/src/components/chat/session-debug-panel.tsx +1 -1
- package/src/components/chat/tool-request-banner.tsx +3 -3
- package/src/components/chatrooms/agent-hover-card.tsx +3 -3
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
- package/src/components/chatrooms/chatroom-view.tsx +347 -205
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +218 -1
- package/src/components/home/home-view.tsx +129 -5
- package/src/components/layout/app-layout.tsx +392 -182
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/plugins/plugin-list.tsx +487 -254
- package/src/components/plugins/plugin-sheet.tsx +236 -13
- package/src/components/projects/project-detail.tsx +183 -0
- package/src/components/settings/gateway-connection-panel.tsx +1 -1
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/command-palette.tsx +111 -25
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +78 -1
- package/src/components/shared/settings/section-orchestrator.tsx +3 -3
- package/src/components/shared/settings/section-providers.tsx +1 -1
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +244 -56
- package/src/components/tasks/approvals-panel.tsx +205 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/usage/metrics-dashboard.tsx +147 -1
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +8 -8
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/chat.ts +1 -1
- package/src/lib/{sessions.ts → chats.ts} +28 -18
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/providers/claude-cli.ts +1 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +205 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +36 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/capability-router.ts +10 -8
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
- package/src/lib/server/chat-execution.ts +285 -165
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +67 -2
- package/src/lib/server/chatroom-helpers.ts +48 -8
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +948 -112
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +188 -9
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/cost.ts +34 -1
- package/src/lib/server/daemon-state.ts +61 -3
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/heartbeat-service.ts +14 -40
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +28 -1103
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +5 -6
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +20 -9
- package/src/lib/server/orchestrator.ts +7 -7
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +207 -0
- package/src/lib/server/plugins.ts +927 -66
- package/src/lib/server/provider-health.ts +38 -6
- package/src/lib/server/queue.ts +13 -28
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -82
- package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
- package/src/lib/server/session-tools/calendar.ts +366 -0
- package/src/lib/server/session-tools/canvas.ts +1 -1
- package/src/lib/server/session-tools/chatroom.ts +4 -2
- package/src/lib/server/session-tools/connector.ts +114 -10
- package/src/lib/server/session-tools/context.ts +21 -5
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +74 -28
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +497 -24
- package/src/lib/server/session-tools/discovery.ts +24 -6
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/edit_file.ts +4 -2
- package/src/lib/server/session-tools/email.ts +320 -0
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +241 -25
- package/src/lib/server/session-tools/git.ts +1 -1
- package/src/lib/server/session-tools/http.ts +1 -1
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +380 -0
- package/src/lib/server/session-tools/index.ts +130 -50
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/memory.ts +172 -3
- package/src/lib/server/session-tools/monitor.ts +151 -8
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
- package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +148 -7
- package/src/lib/server/session-tools/plugin-creator.ts +89 -26
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +301 -0
- package/src/lib/server/session-tools/sample-ui.ts +1 -1
- package/src/lib/server/session-tools/sandbox.ts +4 -2
- package/src/lib/server/session-tools/schedule.ts +24 -12
- package/src/lib/server/session-tools/session-info.ts +43 -7
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
- package/src/lib/server/session-tools/shell.ts +5 -2
- package/src/lib/server/session-tools/subagent.ts +194 -28
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +42 -12
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +926 -91
- package/src/lib/server/storage.ts +255 -16
- package/src/lib/server/stream-agent-chat.ts +116 -268
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -10
- package/src/lib/server/tool-aliases.ts +66 -18
- package/src/lib/server/tool-capability-policy.test.ts +9 -9
- package/src/lib/server/tool-capability-policy.ts +38 -27
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/tool-definitions.ts +4 -0
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +10 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +5 -11
- package/src/stores/use-chat-store.ts +38 -9
- package/src/types/index.ts +352 -47
- package/src/app/api/sessions/[id]/main-loop/route.ts +0 -94
- package/src/components/sessions/new-session-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -24
- package/src/lib/server/session-run-manager.test.ts +0 -23
- /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
|
@@ -15,28 +15,57 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
15
15
|
const user = body.user || 'default'
|
|
16
16
|
const sessions = loadSessions()
|
|
17
17
|
|
|
18
|
-
// If agent already has a
|
|
18
|
+
// If the agent already has a shortcut chat session, return it.
|
|
19
19
|
if (agent.threadSessionId && sessions[agent.threadSessionId]) {
|
|
20
|
-
|
|
20
|
+
const existing = sessions[agent.threadSessionId] as Record<string, unknown>
|
|
21
|
+
let changed = false
|
|
22
|
+
if (existing.shortcutForAgentId !== agentId) {
|
|
23
|
+
existing.shortcutForAgentId = agentId
|
|
24
|
+
changed = true
|
|
25
|
+
}
|
|
26
|
+
if (existing.name !== agent.name) {
|
|
27
|
+
existing.name = agent.name
|
|
28
|
+
changed = true
|
|
29
|
+
}
|
|
30
|
+
if (changed) saveSessions(sessions)
|
|
31
|
+
return NextResponse.json(existing)
|
|
21
32
|
}
|
|
22
33
|
|
|
23
|
-
//
|
|
34
|
+
// Legacy fallback for older shortcut sessions that were named using the
|
|
35
|
+
// old agent-thread convention before the explicit link was persisted.
|
|
24
36
|
const existing = Object.values(sessions).find(
|
|
25
|
-
(s: Record<string, unknown>) =>
|
|
37
|
+
(s: Record<string, unknown>) =>
|
|
38
|
+
(
|
|
39
|
+
s.shortcutForAgentId === agentId
|
|
40
|
+
|| s.name === `agent-thread:${agentId}`
|
|
41
|
+
)
|
|
42
|
+
&& s.user === user
|
|
26
43
|
)
|
|
27
44
|
if (existing) {
|
|
28
45
|
agent.threadSessionId = (existing as Record<string, unknown>).id as string
|
|
29
46
|
agent.updatedAt = Date.now()
|
|
30
47
|
saveAgents(agents)
|
|
48
|
+
let changed = false
|
|
49
|
+
const existingRecord = existing as Record<string, unknown>
|
|
50
|
+
if (existingRecord.shortcutForAgentId !== agentId) {
|
|
51
|
+
existingRecord.shortcutForAgentId = agentId
|
|
52
|
+
changed = true
|
|
53
|
+
}
|
|
54
|
+
if (existingRecord.name !== agent.name) {
|
|
55
|
+
existingRecord.name = agent.name
|
|
56
|
+
changed = true
|
|
57
|
+
}
|
|
58
|
+
if (changed) saveSessions(sessions)
|
|
31
59
|
return NextResponse.json(existing)
|
|
32
60
|
}
|
|
33
61
|
|
|
34
|
-
// Create a new
|
|
35
|
-
const sessionId = `agent-
|
|
62
|
+
// Create a new shortcut chat session for this agent.
|
|
63
|
+
const sessionId = `agent-chat-${agentId}-${genId()}`
|
|
36
64
|
const now = Date.now()
|
|
37
65
|
const session = {
|
|
38
66
|
id: sessionId,
|
|
39
|
-
name:
|
|
67
|
+
name: agent.name,
|
|
68
|
+
shortcutForAgentId: agentId,
|
|
40
69
|
cwd: WORKSPACE_DIR,
|
|
41
70
|
user: user,
|
|
42
71
|
provider: agent.provider,
|
|
@@ -49,10 +78,9 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
49
78
|
createdAt: now,
|
|
50
79
|
lastActiveAt: now,
|
|
51
80
|
active: false,
|
|
52
|
-
mainSession: true,
|
|
53
81
|
sessionType: 'human' as const,
|
|
54
82
|
agentId,
|
|
55
|
-
|
|
83
|
+
plugins: agent.plugins || agent.tools || [],
|
|
56
84
|
heartbeatEnabled: agent.heartbeatEnabled || false,
|
|
57
85
|
heartbeatIntervalSec: agent.heartbeatIntervalSec || null,
|
|
58
86
|
}
|
|
@@ -13,6 +13,9 @@ export async function GET(req: Request) {
|
|
|
13
13
|
const agents = loadAgents()
|
|
14
14
|
const sessions = loadSessions()
|
|
15
15
|
const usage = loadUsage()
|
|
16
|
+
for (const agent of Object.values(agents)) {
|
|
17
|
+
agent.isOrchestrator = agent.platformAssignScope === 'all'
|
|
18
|
+
}
|
|
16
19
|
// Enrich agents that have spend limits with current spend windows
|
|
17
20
|
for (const agent of Object.values(agents)) {
|
|
18
21
|
if (
|
|
@@ -48,6 +51,7 @@ export async function POST(req: Request) {
|
|
|
48
51
|
const id = genId()
|
|
49
52
|
const now = Date.now()
|
|
50
53
|
const agents = loadAgents()
|
|
54
|
+
const platformAssignScope = body.platformAssignScope
|
|
51
55
|
agents[id] = {
|
|
52
56
|
id,
|
|
53
57
|
name: body.name,
|
|
@@ -57,9 +61,10 @@ export async function POST(req: Request) {
|
|
|
57
61
|
model: body.model,
|
|
58
62
|
credentialId: body.credentialId,
|
|
59
63
|
apiEndpoint: normalizeProviderEndpoint(body.provider, body.apiEndpoint || null),
|
|
60
|
-
isOrchestrator:
|
|
64
|
+
isOrchestrator: platformAssignScope === 'all',
|
|
65
|
+
platformAssignScope,
|
|
61
66
|
subAgentIds: body.subAgentIds,
|
|
62
|
-
|
|
67
|
+
plugins: body.plugins?.length ? body.plugins : (body.tools || []),
|
|
63
68
|
capabilities: body.capabilities,
|
|
64
69
|
thinkingLevel: body.thinkingLevel || undefined,
|
|
65
70
|
autoRecovery: body.autoRecovery || false,
|
|
@@ -68,6 +73,12 @@ export async function POST(req: Request) {
|
|
|
68
73
|
hourlyBudget: body.hourlyBudget ?? null,
|
|
69
74
|
budgetAction: body.budgetAction || 'warn',
|
|
70
75
|
soul: body.soul || undefined,
|
|
76
|
+
identityState: body.identityState ?? null,
|
|
77
|
+
sessionResetMode: body.sessionResetMode ?? null,
|
|
78
|
+
sessionIdleTimeoutSec: body.sessionIdleTimeoutSec ?? null,
|
|
79
|
+
sessionMaxAgeSec: body.sessionMaxAgeSec ?? null,
|
|
80
|
+
sessionDailyResetAt: body.sessionDailyResetAt ?? null,
|
|
81
|
+
sessionResetTimezone: body.sessionResetTimezone ?? null,
|
|
71
82
|
createdAt: now,
|
|
72
83
|
updatedAt: now,
|
|
73
84
|
}
|
|
@@ -1,27 +1,96 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { validateAccessKey, getAccessKey, isFirstTimeSetup, markSetupComplete } from '@/lib/server/storage'
|
|
3
3
|
import { ensureDaemonStarted } from '@/lib/server/daemon-state'
|
|
4
|
+
import { AUTH_COOKIE_NAME, getCookieValue } from '@/lib/auth'
|
|
4
5
|
export const dynamic = 'force-dynamic'
|
|
5
6
|
|
|
7
|
+
interface AuthAttemptEntry {
|
|
8
|
+
count: number
|
|
9
|
+
lockedUntil: number
|
|
10
|
+
}
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
const authRateLimitMap = (
|
|
13
|
+
(globalThis as Record<string, unknown>).__swarmclaw_auth_rate_limit__ ??= new Map()
|
|
14
|
+
) as Map<string, AuthAttemptEntry>
|
|
15
|
+
|
|
16
|
+
const MAX_ATTEMPTS = 5
|
|
17
|
+
const LOCKOUT_MS = 15 * 60 * 1000
|
|
18
|
+
|
|
19
|
+
function getClientIp(req: Request): string {
|
|
20
|
+
const forwarded = req.headers.get('x-forwarded-for')
|
|
21
|
+
if (forwarded) {
|
|
22
|
+
const first = forwarded.split(',')[0]?.trim()
|
|
23
|
+
if (first) return first
|
|
11
24
|
}
|
|
12
|
-
|
|
25
|
+
const realIp = req.headers.get('x-real-ip')?.trim()
|
|
26
|
+
return realIp || 'unknown'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function clearAuthCookie(response: NextResponse): NextResponse {
|
|
30
|
+
response.cookies.set(AUTH_COOKIE_NAME, '', {
|
|
31
|
+
httpOnly: true,
|
|
32
|
+
sameSite: 'lax',
|
|
33
|
+
secure: false,
|
|
34
|
+
path: '/',
|
|
35
|
+
maxAge: 0,
|
|
36
|
+
})
|
|
37
|
+
return response
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function setAuthCookie(response: NextResponse, req: Request, key: string): NextResponse {
|
|
41
|
+
response.cookies.set(AUTH_COOKIE_NAME, key, {
|
|
42
|
+
httpOnly: true,
|
|
43
|
+
sameSite: 'lax',
|
|
44
|
+
secure: new URL(req.url).protocol === 'https:',
|
|
45
|
+
path: '/',
|
|
46
|
+
maxAge: 60 * 60 * 24 * 30,
|
|
47
|
+
})
|
|
48
|
+
return response
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** GET /api/auth — returns setup state and whether the auth cookie is currently valid */
|
|
52
|
+
export async function GET(req: Request) {
|
|
53
|
+
const cookieKey = getCookieValue(req.headers.get('cookie'), AUTH_COOKIE_NAME)
|
|
54
|
+
return NextResponse.json({
|
|
55
|
+
firstTime: isFirstTimeSetup(),
|
|
56
|
+
authenticated: !!cookieKey && validateAccessKey(cookieKey),
|
|
57
|
+
})
|
|
13
58
|
}
|
|
14
59
|
|
|
15
60
|
/** POST /api/auth — validate an access key */
|
|
16
61
|
export async function POST(req: Request) {
|
|
62
|
+
const clientIp = getClientIp(req)
|
|
63
|
+
const entry = authRateLimitMap.get(clientIp)
|
|
64
|
+
if (entry && entry.lockedUntil > Date.now()) {
|
|
65
|
+
const retryAfter = Math.ceil((entry.lockedUntil - Date.now()) / 1000)
|
|
66
|
+
return clearAuthCookie(NextResponse.json(
|
|
67
|
+
{ error: 'Too many failed attempts. Try again later.', retryAfter },
|
|
68
|
+
{ status: 429, headers: { 'Retry-After': String(retryAfter) } },
|
|
69
|
+
))
|
|
70
|
+
}
|
|
71
|
+
|
|
17
72
|
const { key } = await req.json()
|
|
18
73
|
if (!key || !validateAccessKey(key)) {
|
|
19
|
-
|
|
74
|
+
const current = authRateLimitMap.get(clientIp) ?? { count: 0, lockedUntil: 0 }
|
|
75
|
+
current.count += 1
|
|
76
|
+
if (current.count >= MAX_ATTEMPTS) {
|
|
77
|
+
current.lockedUntil = Date.now() + LOCKOUT_MS
|
|
78
|
+
}
|
|
79
|
+
authRateLimitMap.set(clientIp, current)
|
|
80
|
+
return clearAuthCookie(NextResponse.json(
|
|
81
|
+
{ error: 'Invalid access key' },
|
|
82
|
+
{
|
|
83
|
+
status: 401,
|
|
84
|
+
headers: { 'X-RateLimit-Remaining': String(Math.max(0, MAX_ATTEMPTS - current.count)) },
|
|
85
|
+
},
|
|
86
|
+
))
|
|
20
87
|
}
|
|
88
|
+
|
|
89
|
+
authRateLimitMap.delete(clientIp)
|
|
21
90
|
// If this was first-time setup, mark it as claimed
|
|
22
91
|
if (isFirstTimeSetup()) {
|
|
23
92
|
markSetupComplete()
|
|
24
93
|
}
|
|
25
94
|
ensureDaemonStarted('api/auth:post')
|
|
26
|
-
return NextResponse.json({ ok: true })
|
|
95
|
+
return setAuthCookie(NextResponse.json({ ok: true }), req, key)
|
|
27
96
|
}
|
|
@@ -8,6 +8,8 @@ import { getProvider } from '@/lib/providers'
|
|
|
8
8
|
import {
|
|
9
9
|
resolveApiKey,
|
|
10
10
|
parseMentions,
|
|
11
|
+
resolveReplyTargetAgentId,
|
|
12
|
+
resolveAgentApiEndpoint,
|
|
11
13
|
compactChatroomMessages,
|
|
12
14
|
buildChatroomSystemPrompt,
|
|
13
15
|
buildSyntheticSession,
|
|
@@ -50,7 +52,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
50
52
|
|
|
51
53
|
// Persist incoming message
|
|
52
54
|
const senderName = senderId === 'user' ? 'You' : (agents[senderId]?.name || senderId)
|
|
53
|
-
|
|
55
|
+
const replyTargetAgentId = resolveReplyTargetAgentId(replyToId, chatroom.messages, chatroom.agentIds)
|
|
56
|
+
let mentions = parseMentions(text, agents, chatroom.agentIds, { replyTargetAgentId })
|
|
54
57
|
// Routing rules: if no explicit mentions, evaluate keyword/capability rules
|
|
55
58
|
if (mentions.length === 0 && chatroom.routingRules?.length) {
|
|
56
59
|
const agentList = chatroom.agentIds.map((aid) => agents[aid]).filter(Boolean)
|
|
@@ -149,13 +152,14 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
149
152
|
// Pre-flight: check if the agent's provider is usable before attempting to stream
|
|
150
153
|
const providerInfo = getProvider(agent.provider)
|
|
151
154
|
const apiKey = resolveApiKey(agent.credentialId)
|
|
155
|
+
const resolvedEndpoint = resolveAgentApiEndpoint(agent)
|
|
152
156
|
if (providerInfo?.requiresApiKey && !apiKey) {
|
|
153
157
|
writeEvent({ t: 'cr_agent_start', agentId: agent.id, agentName: agent.name })
|
|
154
158
|
writeEvent({ t: 'err', text: `${agent.name} has no API credentials configured`, agentId: agent.id, agentName: agent.name })
|
|
155
159
|
writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
|
|
156
160
|
return []
|
|
157
161
|
}
|
|
158
|
-
if (providerInfo?.requiresEndpoint && !
|
|
162
|
+
if (providerInfo?.requiresEndpoint && !resolvedEndpoint) {
|
|
159
163
|
writeEvent({ t: 'cr_agent_start', agentId: agent.id, agentName: agent.name })
|
|
160
164
|
writeEvent({ t: 'err', text: `${agent.name} has no endpoint configured`, agentId: agent.id, agentName: agent.name })
|
|
161
165
|
writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
|
|
@@ -174,6 +178,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
const syntheticSession = buildSyntheticSession(agent, id)
|
|
181
|
+
syntheticSession.apiEndpoint = resolvedEndpoint
|
|
177
182
|
const agentSystemPrompt = buildAgentSystemPromptForChatroom(agent)
|
|
178
183
|
const chatroomContext = buildChatroomSystemPrompt(freshChatroom, agents, agent.id)
|
|
179
184
|
const fullSystemPrompt = [agentSystemPrompt, chatroomContext].filter(Boolean).join('\n\n')
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { hasActiveBrowser, cleanupSessionBrowser } from '@/lib/server/session-tools'
|
|
3
|
+
import { loadBrowserSessionRecord } from '@/lib/server/browser-state'
|
|
3
4
|
|
|
4
5
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
5
6
|
const { id } = await params
|
|
6
|
-
return NextResponse.json({
|
|
7
|
+
return NextResponse.json({
|
|
8
|
+
active: hasActiveBrowser(id),
|
|
9
|
+
state: loadBrowserSessionRecord(id),
|
|
10
|
+
})
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -49,7 +49,9 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
49
49
|
mode: queueMode,
|
|
50
50
|
onEvent: (ev) => writeEvent(ev as unknown as Record<string, unknown>),
|
|
51
51
|
replyToId,
|
|
52
|
-
|
|
52
|
+
// Keep user-initiated runs alive even if the SSE transport drops so
|
|
53
|
+
// long-lived tasks can finish and be observed later via polling/history.
|
|
54
|
+
callerSignal: internal ? req.signal : undefined,
|
|
53
55
|
})
|
|
54
56
|
abortRun = run.abort
|
|
55
57
|
|
|
@@ -89,8 +91,10 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
89
91
|
})
|
|
90
92
|
},
|
|
91
93
|
cancel() {
|
|
92
|
-
// Client disconnected
|
|
93
|
-
|
|
94
|
+
// Client disconnected. User-facing runs continue in the background so
|
|
95
|
+
// they can persist results even when the transport drops. Explicit stop
|
|
96
|
+
// controls still cancel the run through the session run manager.
|
|
97
|
+
if (internal) abortRun?.()
|
|
94
98
|
},
|
|
95
99
|
})
|
|
96
100
|
|
|
@@ -3,7 +3,7 @@ import { getCheckpointSaver } from '@/lib/server/langgraph-checkpoint'
|
|
|
3
3
|
|
|
4
4
|
export const dynamic = 'force-dynamic'
|
|
5
5
|
|
|
6
|
-
/** GET /api/
|
|
6
|
+
/** GET /api/chats/[id]/checkpoints — returns checkpoint history for a thread */
|
|
7
7
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
8
|
const { id: threadId } = await params
|
|
9
9
|
if (!threadId) return NextResponse.json({ error: 'Thread ID is required' }, { status: 400 })
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
|
|
3
|
+
function retiredResponse() {
|
|
4
|
+
return new NextResponse('Mission controls are no longer supported.', { status: 410 })
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function GET() {
|
|
8
|
+
return retiredResponse()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function POST() {
|
|
12
|
+
return retiredResponse()
|
|
13
|
+
}
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import {
|
|
2
|
+
import { active, loadStoredItem, upsertStoredItem } from '@/lib/server/storage'
|
|
3
3
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
+
import { getSessionRunState } from '@/lib/server/session-run-manager'
|
|
5
|
+
import { pruneStreamingAssistantArtifacts } from '@/lib/chat-streaming-state'
|
|
4
6
|
|
|
5
7
|
export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
8
|
const { id } = await params
|
|
7
|
-
const
|
|
8
|
-
if (!
|
|
9
|
+
const session = loadStoredItem('sessions', id)
|
|
10
|
+
if (!session) return notFound()
|
|
11
|
+
session.messages = Array.isArray(session.messages) ? session.messages : []
|
|
12
|
+
|
|
13
|
+
const run = getSessionRunState(id)
|
|
14
|
+
const hasLiveRun = active.has(id) || !!run.runningRunId
|
|
15
|
+
if (!hasLiveRun && pruneStreamingAssistantArtifacts(session.messages)) {
|
|
16
|
+
upsertStoredItem('sessions', id, session)
|
|
17
|
+
}
|
|
9
18
|
|
|
10
19
|
const url = new URL(req.url)
|
|
11
20
|
const limitParam = url.searchParams.get('limit')
|
|
12
21
|
const beforeParam = url.searchParams.get('before')
|
|
13
22
|
|
|
14
|
-
const allMessages =
|
|
23
|
+
const allMessages = Array.isArray(session.messages) ? session.messages : []
|
|
15
24
|
const total = allMessages.length
|
|
16
25
|
|
|
17
26
|
// If no limit param, return all messages (backward compatible)
|
|
@@ -41,8 +50,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
41
50
|
if (body.kind !== 'context-clear') {
|
|
42
51
|
return NextResponse.json({ error: 'Only context-clear kind is supported' }, { status: 400 })
|
|
43
52
|
}
|
|
44
|
-
const
|
|
45
|
-
const session = sessions[id]
|
|
53
|
+
const session = loadStoredItem('sessions', id)
|
|
46
54
|
if (!session) return notFound()
|
|
47
55
|
|
|
48
56
|
session.messages.push({
|
|
@@ -51,15 +59,14 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
51
59
|
kind: 'context-clear',
|
|
52
60
|
time: Date.now(),
|
|
53
61
|
})
|
|
54
|
-
|
|
62
|
+
upsertStoredItem('sessions', id, session)
|
|
55
63
|
return NextResponse.json({ ok: true })
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
59
67
|
const { id } = await params
|
|
60
68
|
const body = await req.json() as { messageIndex: number; bookmarked: boolean }
|
|
61
|
-
const
|
|
62
|
-
const session = sessions[id]
|
|
69
|
+
const session = loadStoredItem('sessions', id)
|
|
63
70
|
if (!session) return notFound()
|
|
64
71
|
|
|
65
72
|
const { messageIndex, bookmarked } = body
|
|
@@ -68,15 +75,14 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
session.messages[messageIndex].bookmarked = bookmarked
|
|
71
|
-
|
|
78
|
+
upsertStoredItem('sessions', id, session)
|
|
72
79
|
return NextResponse.json(session.messages[messageIndex])
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
export async function DELETE(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
76
83
|
const { id } = await params
|
|
77
84
|
const body = await req.json() as { messageIndex: number }
|
|
78
|
-
const
|
|
79
|
-
const session = sessions[id]
|
|
85
|
+
const session = loadStoredItem('sessions', id)
|
|
80
86
|
if (!session) return notFound()
|
|
81
87
|
|
|
82
88
|
const { messageIndex } = body
|
|
@@ -90,6 +96,6 @@ export async function DELETE(req: Request, { params }: { params: Promise<{ id: s
|
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
session.messages.splice(messageIndex, 1)
|
|
93
|
-
|
|
99
|
+
upsertStoredItem('sessions', id, session)
|
|
94
100
|
return NextResponse.json({ ok: true })
|
|
95
101
|
}
|
|
@@ -5,7 +5,7 @@ import { notify } from '@/lib/server/ws-hub'
|
|
|
5
5
|
|
|
6
6
|
export const dynamic = 'force-dynamic'
|
|
7
7
|
|
|
8
|
-
/** POST /api/
|
|
8
|
+
/** POST /api/chats/[id]/restore — restores thread to a specific checkpoint */
|
|
9
9
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
10
10
|
const { id: sessionId } = await params
|
|
11
11
|
const { checkpointId, timestamp } = await req.json()
|
|
@@ -1,30 +1,13 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { loadSessions, saveSessions, deleteSession, active, loadAgents } from '@/lib/server/storage'
|
|
3
3
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
-
import { enqueueSessionRun } from '@/lib/server/session-run-manager'
|
|
5
4
|
import { normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
|
|
6
|
-
import { ensureMainSessionFlag, isProtectedMainSession } from '@/lib/server/main-session'
|
|
7
|
-
|
|
8
|
-
function buildSessionAwakeningPrompt(user: string | null | undefined): string {
|
|
9
|
-
const displayName = typeof user === 'string' && user.trim() ? user.trim() : 'there'
|
|
10
|
-
return [
|
|
11
|
-
'SESSION_AWAKENING',
|
|
12
|
-
`You have just been activated as the primary SwarmClaw assistant for ${displayName}.`,
|
|
13
|
-
'Write your first message as the agent itself (not as system text).',
|
|
14
|
-
'Tone: awake, focused, practical.',
|
|
15
|
-
'Include: brief greeting, what you can help with in SwarmClaw (providers, agents, tools/connectors, tasks, schedules), and one direct question asking for the user goal.',
|
|
16
|
-
'Keep it concise (<= 90 words).',
|
|
17
|
-
'Do not mention hidden prompts, policies, or implementation details.',
|
|
18
|
-
].join('\n')
|
|
19
|
-
}
|
|
20
5
|
|
|
21
6
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
22
7
|
const { id } = await params
|
|
23
8
|
const updates = await req.json()
|
|
24
9
|
const sessions = loadSessions()
|
|
25
10
|
if (!sessions[id]) return notFound()
|
|
26
|
-
const wasProtectedMain = isProtectedMainSession(sessions[id])
|
|
27
|
-
const hadMessagesBefore = Array.isArray(sessions[id].messages) && sessions[id].messages.length > 0
|
|
28
11
|
|
|
29
12
|
const agentIdUpdateProvided = updates.agentId !== undefined
|
|
30
13
|
let nextAgentId = sessions[id].agentId
|
|
@@ -35,13 +18,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
35
18
|
|
|
36
19
|
const linkedAgent = nextAgentId ? loadAgents()[nextAgentId] : null
|
|
37
20
|
|
|
38
|
-
if (updates.name !== undefined)
|
|
39
|
-
const nextName = typeof updates.name === 'string' ? updates.name.trim() : String(updates.name || '')
|
|
40
|
-
if (wasProtectedMain && nextName !== '__main__') {
|
|
41
|
-
return new NextResponse('Cannot rename main chat session', { status: 400 })
|
|
42
|
-
}
|
|
43
|
-
sessions[id].name = updates.name
|
|
44
|
-
}
|
|
21
|
+
if (updates.name !== undefined) sessions[id].name = updates.name
|
|
45
22
|
if (updates.cwd !== undefined) sessions[id].cwd = updates.cwd
|
|
46
23
|
if (updates.provider !== undefined) sessions[id].provider = updates.provider
|
|
47
24
|
else if (agentIdUpdateProvided && linkedAgent?.provider) sessions[id].provider = linkedAgent.provider
|
|
@@ -52,8 +29,8 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
52
29
|
if (updates.credentialId !== undefined) sessions[id].credentialId = updates.credentialId
|
|
53
30
|
else if (agentIdUpdateProvided && linkedAgent) sessions[id].credentialId = linkedAgent.credentialId ?? null
|
|
54
31
|
|
|
55
|
-
if (updates.
|
|
56
|
-
else if (agentIdUpdateProvided && linkedAgent) sessions[id].
|
|
32
|
+
if (updates.plugins !== undefined) sessions[id].plugins = updates.plugins
|
|
33
|
+
else if (agentIdUpdateProvided && linkedAgent) sessions[id].plugins = Array.isArray(linkedAgent.plugins) ? linkedAgent.plugins : []
|
|
57
34
|
|
|
58
35
|
if (updates.apiEndpoint !== undefined) {
|
|
59
36
|
sessions[id].apiEndpoint = normalizeProviderEndpoint(
|
|
@@ -68,46 +45,39 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
68
45
|
}
|
|
69
46
|
if (updates.heartbeatEnabled !== undefined) sessions[id].heartbeatEnabled = updates.heartbeatEnabled
|
|
70
47
|
if (updates.heartbeatIntervalSec !== undefined) sessions[id].heartbeatIntervalSec = updates.heartbeatIntervalSec
|
|
48
|
+
if (updates.sessionResetMode !== undefined) sessions[id].sessionResetMode = updates.sessionResetMode
|
|
49
|
+
if (updates.sessionIdleTimeoutSec !== undefined) sessions[id].sessionIdleTimeoutSec = updates.sessionIdleTimeoutSec
|
|
50
|
+
if (updates.sessionMaxAgeSec !== undefined) sessions[id].sessionMaxAgeSec = updates.sessionMaxAgeSec
|
|
51
|
+
if (updates.sessionDailyResetAt !== undefined) sessions[id].sessionDailyResetAt = updates.sessionDailyResetAt
|
|
52
|
+
if (updates.sessionResetTimezone !== undefined) sessions[id].sessionResetTimezone = updates.sessionResetTimezone
|
|
53
|
+
if (updates.thinkingLevel !== undefined) sessions[id].thinkingLevel = updates.thinkingLevel
|
|
54
|
+
if (updates.connectorThinkLevel !== undefined) sessions[id].connectorThinkLevel = updates.connectorThinkLevel
|
|
55
|
+
if (updates.connectorSessionScope !== undefined) sessions[id].connectorSessionScope = updates.connectorSessionScope
|
|
56
|
+
if (updates.connectorReplyMode !== undefined) sessions[id].connectorReplyMode = updates.connectorReplyMode
|
|
57
|
+
if (updates.connectorThreadBinding !== undefined) sessions[id].connectorThreadBinding = updates.connectorThreadBinding
|
|
58
|
+
if (updates.connectorGroupPolicy !== undefined) sessions[id].connectorGroupPolicy = updates.connectorGroupPolicy
|
|
59
|
+
if (updates.connectorIdleTimeoutSec !== undefined) sessions[id].connectorIdleTimeoutSec = updates.connectorIdleTimeoutSec
|
|
60
|
+
if (updates.connectorMaxAgeSec !== undefined) sessions[id].connectorMaxAgeSec = updates.connectorMaxAgeSec
|
|
61
|
+
if (updates.connectorContext !== undefined) sessions[id].connectorContext = updates.connectorContext
|
|
62
|
+
if (updates.identityState !== undefined) sessions[id].identityState = updates.identityState
|
|
63
|
+
if (updates.sessionArchiveState !== undefined) sessions[id].sessionArchiveState = updates.sessionArchiveState
|
|
64
|
+
if (updates.lastSessionResetAt !== undefined) sessions[id].lastSessionResetAt = updates.lastSessionResetAt
|
|
65
|
+
if (updates.lastSessionResetReason !== undefined) sessions[id].lastSessionResetReason = updates.lastSessionResetReason
|
|
71
66
|
if (updates.pinned !== undefined) sessions[id].pinned = !!updates.pinned
|
|
72
67
|
if (updates.claudeSessionId !== undefined) sessions[id].claudeSessionId = updates.claudeSessionId
|
|
73
68
|
if (updates.codexThreadId !== undefined) sessions[id].codexThreadId = updates.codexThreadId
|
|
74
69
|
if (updates.opencodeSessionId !== undefined) sessions[id].opencodeSessionId = updates.opencodeSessionId
|
|
75
70
|
if (updates.delegateResumeIds !== undefined) sessions[id].delegateResumeIds = updates.delegateResumeIds
|
|
76
71
|
if (!Array.isArray(sessions[id].messages)) sessions[id].messages = []
|
|
77
|
-
ensureMainSessionFlag(sessions[id])
|
|
78
|
-
|
|
79
|
-
const shouldKickoffAwakening = isProtectedMainSession(sessions[id])
|
|
80
|
-
&& agentIdUpdateProvided
|
|
81
|
-
&& !!sessions[id].agentId
|
|
82
|
-
&& !hadMessagesBefore
|
|
83
|
-
&& sessions[id].messages.length === 0
|
|
84
72
|
|
|
85
73
|
saveSessions(sessions)
|
|
86
|
-
|
|
87
|
-
if (shouldKickoffAwakening) {
|
|
88
|
-
try {
|
|
89
|
-
enqueueSessionRun({
|
|
90
|
-
sessionId: id,
|
|
91
|
-
message: buildSessionAwakeningPrompt(sessions[id].user),
|
|
92
|
-
internal: true,
|
|
93
|
-
source: 'session-awakening',
|
|
94
|
-
mode: 'steer',
|
|
95
|
-
dedupeKey: `session-awakening:${id}`,
|
|
96
|
-
})
|
|
97
|
-
} catch {
|
|
98
|
-
// Best-effort kickoff only.
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
74
|
return NextResponse.json(sessions[id])
|
|
103
75
|
}
|
|
104
76
|
|
|
105
77
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
106
78
|
const { id } = await params
|
|
107
79
|
const sessions = loadSessions()
|
|
108
|
-
if (
|
|
109
|
-
return new NextResponse('Cannot delete main chat session', { status: 403 })
|
|
110
|
-
}
|
|
80
|
+
if (!sessions[id]) return notFound()
|
|
111
81
|
if (active.has(id)) {
|
|
112
82
|
try { active.get(id).kill() } catch {}
|
|
113
83
|
active.delete(id)
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import {
|
|
2
|
+
import { pruneStreamingAssistantArtifacts } from '@/lib/chat-streaming-state'
|
|
3
|
+
import { active, loadStoredItem, upsertStoredItem } from '@/lib/server/storage'
|
|
3
4
|
import { cancelSessionRuns } from '@/lib/server/session-run-manager'
|
|
4
5
|
|
|
5
6
|
export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
7
|
const { id } = await params
|
|
7
8
|
const cancel = cancelSessionRuns(id, 'Stopped by user')
|
|
9
|
+
const session = loadStoredItem('sessions', id)
|
|
10
|
+
if (session && Array.isArray(session.messages) && pruneStreamingAssistantArtifacts(session.messages)) {
|
|
11
|
+
upsertStoredItem('sessions', id, session)
|
|
12
|
+
}
|
|
8
13
|
if (active.has(id)) {
|
|
9
14
|
try { active.get(id).kill() } catch {}
|
|
10
15
|
active.delete(id)
|
|
@@ -7,7 +7,6 @@ import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
|
7
7
|
import { notify } from '@/lib/server/ws-hub'
|
|
8
8
|
import { getSessionRunState } from '@/lib/server/session-run-manager'
|
|
9
9
|
import { normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
|
|
10
|
-
import { ensureMainSessionFlag, isProtectedMainSession } from '@/lib/server/main-session'
|
|
11
10
|
export const dynamic = 'force-dynamic'
|
|
12
11
|
|
|
13
12
|
|
|
@@ -39,7 +38,7 @@ export async function DELETE(req: Request) {
|
|
|
39
38
|
const sessions = loadSessions()
|
|
40
39
|
let deleted = 0
|
|
41
40
|
for (const id of ids) {
|
|
42
|
-
if (
|
|
41
|
+
if (!sessions[id]) continue
|
|
43
42
|
if (active.has(id)) {
|
|
44
43
|
try { active.get(id).kill() } catch {}
|
|
45
44
|
active.delete(id)
|
|
@@ -61,15 +60,15 @@ export async function POST(req: Request) {
|
|
|
61
60
|
const id = body.id || genId()
|
|
62
61
|
const sessions = loadSessions()
|
|
63
62
|
const agent = body.agentId ? loadAgents()[body.agentId] : null
|
|
64
|
-
const
|
|
65
|
-
const
|
|
63
|
+
const requestedPlugins = Array.isArray(body.plugins) ? body.plugins : (Array.isArray(body.tools) ? body.tools : null)
|
|
64
|
+
const resolvedPlugins = requestedPlugins ?? (Array.isArray(agent?.plugins) ? agent.plugins : (Array.isArray(agent?.tools) ? agent.tools : []))
|
|
66
65
|
|
|
67
66
|
// If session with this ID already exists, return it as-is
|
|
68
67
|
if (body.id && sessions[id]) {
|
|
69
68
|
return NextResponse.json(sessions[id])
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
const sessionName = body.name || 'New
|
|
71
|
+
const sessionName = body.name || 'New Chat'
|
|
73
72
|
|
|
74
73
|
sessions[id] = {
|
|
75
74
|
id, name: sessionName, cwd,
|
|
@@ -94,11 +93,26 @@ export async function POST(req: Request) {
|
|
|
94
93
|
sessionType: body.sessionType || 'human',
|
|
95
94
|
agentId: body.agentId || null,
|
|
96
95
|
parentSessionId: body.parentSessionId || null,
|
|
97
|
-
|
|
96
|
+
plugins: resolvedPlugins,
|
|
98
97
|
heartbeatEnabled: body.heartbeatEnabled ?? null,
|
|
99
98
|
heartbeatIntervalSec: body.heartbeatIntervalSec ?? null,
|
|
99
|
+
sessionResetMode: body.sessionResetMode ?? agent?.sessionResetMode ?? null,
|
|
100
|
+
sessionIdleTimeoutSec: body.sessionIdleTimeoutSec ?? agent?.sessionIdleTimeoutSec ?? null,
|
|
101
|
+
sessionMaxAgeSec: body.sessionMaxAgeSec ?? agent?.sessionMaxAgeSec ?? null,
|
|
102
|
+
sessionDailyResetAt: body.sessionDailyResetAt ?? agent?.sessionDailyResetAt ?? null,
|
|
103
|
+
sessionResetTimezone: body.sessionResetTimezone ?? agent?.sessionResetTimezone ?? null,
|
|
104
|
+
thinkingLevel: body.thinkingLevel ?? null,
|
|
105
|
+
connectorThinkLevel: body.connectorThinkLevel ?? null,
|
|
106
|
+
connectorSessionScope: body.connectorSessionScope ?? null,
|
|
107
|
+
connectorReplyMode: body.connectorReplyMode ?? null,
|
|
108
|
+
connectorThreadBinding: body.connectorThreadBinding ?? null,
|
|
109
|
+
connectorGroupPolicy: body.connectorGroupPolicy ?? null,
|
|
110
|
+
connectorIdleTimeoutSec: body.connectorIdleTimeoutSec ?? null,
|
|
111
|
+
connectorMaxAgeSec: body.connectorMaxAgeSec ?? null,
|
|
112
|
+
connectorContext: body.connectorContext ?? null,
|
|
113
|
+
identityState: body.identityState ?? agent?.identityState ?? null,
|
|
114
|
+
sessionArchiveState: body.sessionArchiveState ?? null,
|
|
100
115
|
}
|
|
101
|
-
ensureMainSessionFlag(sessions[id])
|
|
102
116
|
saveSessions(sessions)
|
|
103
117
|
notify('sessions')
|
|
104
118
|
return NextResponse.json(sessions[id])
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadConnectors } from '@/lib/server/storage'
|
|
3
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
+
import { buildConnectorDoctorPreview, buildConnectorDoctorReport, type ConnectorDoctorPreviewInput } from '@/lib/server/connectors/doctor'
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic'
|
|
7
|
+
|
|
8
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
9
|
+
const { id } = await params
|
|
10
|
+
const connectors = loadConnectors()
|
|
11
|
+
const connector = connectors[id]
|
|
12
|
+
if (!connector) return notFound()
|
|
13
|
+
|
|
14
|
+
return NextResponse.json(buildConnectorDoctorReport(connector, null, { baseConnector: connector }))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
18
|
+
const { id } = await params
|
|
19
|
+
const connectors = loadConnectors()
|
|
20
|
+
const baseConnector = connectors[id]
|
|
21
|
+
if (!baseConnector) return notFound()
|
|
22
|
+
|
|
23
|
+
const body = await req.json().catch(() => ({})) as ConnectorDoctorPreviewInput
|
|
24
|
+
const connector = buildConnectorDoctorPreview({ baseConnector, input: body, fallbackId: id })
|
|
25
|
+
return NextResponse.json(buildConnectorDoctorReport(connector, body.sampleMsg, { baseConnector }))
|
|
26
|
+
}
|