@swarmclawai/swarmclaw 0.7.3 → 0.7.5
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 +47 -40
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +17 -0
- package/src/app/api/agents/[id]/thread/route.ts +4 -87
- package/src/app/api/agents/route.ts +23 -1
- package/src/app/api/auth/route.ts +1 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +16 -5
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/route.ts +12 -0
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +7 -1
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- package/src/app/api/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +1 -1
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +6 -10
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +2 -1
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/page.tsx +126 -15
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +34 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +20 -4
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-sheet.tsx +249 -7
- package/src/components/agents/inspector-panel.tsx +3 -2
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +41 -14
- package/src/components/chat/chat-card.tsx +2 -1
- package/src/components/chat/chat-header.tsx +8 -13
- package/src/components/chat/chat-list.tsx +58 -20
- package/src/components/chat/message-list.tsx +142 -18
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +157 -86
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +2 -0
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/projects/project-detail.tsx +7 -2
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/section-heartbeat.tsx +11 -6
- package/src/components/shared/settings/section-orchestrator.tsx +3 -0
- package/src/components/shared/settings/settings-page.tsx +5 -3
- package/src/components/tasks/approvals-panel.tsx +7 -1
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/agent-thread-session.test.ts +85 -0
- package/src/lib/server/agent-thread-session.ts +123 -0
- package/src/lib/server/approvals-auto-approve.test.ts +59 -0
- package/src/lib/server/build-llm.test.ts +13 -5
- package/src/lib/server/chat-execution-tool-events.test.ts +87 -2
- package/src/lib/server/chat-execution.ts +159 -71
- package/src/lib/server/chatroom-helpers.test.ts +7 -0
- package/src/lib/server/chatroom-helpers.ts +99 -6
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/manager.ts +89 -61
- package/src/lib/server/connectors/slack.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -2
- package/src/lib/server/data-dir.test.ts +56 -0
- package/src/lib/server/data-dir.ts +15 -9
- package/src/lib/server/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +23 -8
- package/src/lib/server/heartbeat-wake.ts +6 -2
- package/src/lib/server/main-agent-loop.ts +13 -6
- package/src/lib/server/openclaw-exec-config.ts +4 -2
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/orchestrator-lg.ts +1 -2
- package/src/lib/server/orchestrator.ts +3 -2
- package/src/lib/server/plugins.test.ts +9 -1
- package/src/lib/server/plugins.ts +12 -2
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +1 -1
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- package/src/lib/server/session-tools/autonomy-tools.test.ts +23 -0
- package/src/lib/server/session-tools/crud.ts +27 -3
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +18 -8
- package/src/lib/server/session-tools/file-normalize.test.ts +5 -0
- package/src/lib/server/session-tools/file.ts +8 -2
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/index.ts +31 -1
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/monitor.ts +14 -7
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform.ts +1 -1
- package/src/lib/server/session-tools/plugin-creator.ts +9 -2
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/session-info.ts +22 -1
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +23 -0
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +3 -1
- package/src/lib/server/session-tools/web.ts +73 -30
- package/src/lib/server/storage.ts +29 -3
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +139 -4
- package/src/lib/server/structured-extract.ts +1 -1
- package/src/lib/server/task-mention.ts +0 -1
- package/src/lib/server/tool-aliases.ts +37 -6
- package/src/lib/server/tool-capability-policy.ts +1 -1
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.ts +55 -1
- package/src/stores/use-app-store.ts +43 -1
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +189 -6
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -13
package/bin/update-cmd.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict'
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
4
|
|
|
4
|
-
const { execSync } = require('node:child_process')
|
|
5
|
+
const { execSync, execFileSync } = require('node:child_process')
|
|
5
6
|
const path = require('node:path')
|
|
7
|
+
const {
|
|
8
|
+
dependenciesChanged,
|
|
9
|
+
detectPackageManager,
|
|
10
|
+
getGlobalUpdateSpec,
|
|
11
|
+
getInstallCommand,
|
|
12
|
+
} = require('./package-manager.js')
|
|
6
13
|
|
|
7
14
|
const PKG_ROOT = path.resolve(__dirname, '..')
|
|
15
|
+
const PACKAGE_NAME = '@swarmclawai/swarmclaw'
|
|
8
16
|
const RELEASE_TAG_RE = /^v\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/
|
|
17
|
+
const PACKAGE_MANAGER = detectPackageManager(PKG_ROOT)
|
|
9
18
|
|
|
10
19
|
function run(cmd) {
|
|
11
20
|
return execSync(cmd, { encoding: 'utf-8', cwd: PKG_ROOT, timeout: 60_000 }).trim()
|
|
@@ -27,15 +36,37 @@ function getLatestStableTag() {
|
|
|
27
36
|
return tags.find((t) => RELEASE_TAG_RE.test(t)) || null
|
|
28
37
|
}
|
|
29
38
|
|
|
39
|
+
function runRegistrySelfUpdate(
|
|
40
|
+
packageManager = PACKAGE_MANAGER,
|
|
41
|
+
execImpl = execFileSync,
|
|
42
|
+
logger = { log, logError },
|
|
43
|
+
) {
|
|
44
|
+
const update = getGlobalUpdateSpec(packageManager, PACKAGE_NAME)
|
|
45
|
+
logger.log(`No git checkout detected. Updating the global ${PACKAGE_NAME} install via ${packageManager}...`)
|
|
46
|
+
try {
|
|
47
|
+
execImpl(update.command, update.args, {
|
|
48
|
+
cwd: PKG_ROOT,
|
|
49
|
+
stdio: 'inherit',
|
|
50
|
+
timeout: 120_000,
|
|
51
|
+
})
|
|
52
|
+
logger.log(`Global update complete via ${packageManager}.`)
|
|
53
|
+
logger.log('Restart the server to apply changes: swarmclaw server stop && swarmclaw server start')
|
|
54
|
+
return 0
|
|
55
|
+
} catch (err) {
|
|
56
|
+
logger.logError(`Registry update failed: ${err.message}`)
|
|
57
|
+
logger.logError(`Retry manually with: ${update.display}`)
|
|
58
|
+
return 1
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
30
62
|
function main() {
|
|
31
63
|
const args = process.argv.slice(3)
|
|
32
64
|
if (args.includes('-h') || args.includes('--help')) {
|
|
33
65
|
console.log(`
|
|
34
66
|
Usage: swarmclaw update
|
|
35
67
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Runs npm install if package files changed.
|
|
68
|
+
If running from a git checkout, pull the latest SwarmClaw release tag.
|
|
69
|
+
If running from a registry install, update the global package with ${PACKAGE_MANAGER}.
|
|
39
70
|
`.trim())
|
|
40
71
|
process.exit(0)
|
|
41
72
|
}
|
|
@@ -44,8 +75,7 @@ Runs npm install if package files changed.
|
|
|
44
75
|
try {
|
|
45
76
|
run('git rev-parse --git-dir')
|
|
46
77
|
} catch {
|
|
47
|
-
|
|
48
|
-
process.exit(1)
|
|
78
|
+
process.exit(runRegistrySelfUpdate(PACKAGE_MANAGER))
|
|
49
79
|
}
|
|
50
80
|
|
|
51
81
|
const beforeRef = run('git rev-parse HEAD')
|
|
@@ -105,9 +135,10 @@ Runs npm install if package files changed.
|
|
|
105
135
|
// Install deps if package files changed
|
|
106
136
|
try {
|
|
107
137
|
const diff = run(`git diff --name-only ${beforeSha}..HEAD`)
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
|
|
138
|
+
if (dependenciesChanged(diff)) {
|
|
139
|
+
const install = getInstallCommand(PACKAGE_MANAGER, true)
|
|
140
|
+
log(`Package files changed — running ${PACKAGE_MANAGER} install...`)
|
|
141
|
+
execFileSync(install.command, install.args, { cwd: PKG_ROOT, stdio: 'inherit', timeout: 120_000 })
|
|
111
142
|
}
|
|
112
143
|
} catch {
|
|
113
144
|
// If diff fails, skip install check
|
|
@@ -117,4 +148,11 @@ Runs npm install if package files changed.
|
|
|
117
148
|
log('Restart the server to apply changes: swarmclaw server stop && swarmclaw server start')
|
|
118
149
|
}
|
|
119
150
|
|
|
120
|
-
main
|
|
151
|
+
if (require.main === module) {
|
|
152
|
+
main()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = {
|
|
156
|
+
main,
|
|
157
|
+
runRegistrySelfUpdate,
|
|
158
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
|
+
|
|
4
|
+
const test = require('node:test')
|
|
5
|
+
const assert = require('node:assert/strict')
|
|
6
|
+
|
|
7
|
+
const { runRegistrySelfUpdate } = require('./update-cmd.js')
|
|
8
|
+
|
|
9
|
+
test('runRegistrySelfUpdate executes the manager-specific global update command', () => {
|
|
10
|
+
const messages = []
|
|
11
|
+
let captured = null
|
|
12
|
+
|
|
13
|
+
const exitCode = runRegistrySelfUpdate(
|
|
14
|
+
'pnpm',
|
|
15
|
+
(command, args, options) => {
|
|
16
|
+
captured = { command, args, options }
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
log: (message) => messages.push(`log:${message}`),
|
|
20
|
+
logError: (message) => messages.push(`err:${message}`),
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
assert.equal(exitCode, 0)
|
|
25
|
+
assert.deepEqual(captured, {
|
|
26
|
+
command: 'pnpm',
|
|
27
|
+
args: ['add', '-g', '@swarmclawai/swarmclaw@latest'],
|
|
28
|
+
options: {
|
|
29
|
+
cwd: process.cwd(),
|
|
30
|
+
stdio: 'inherit',
|
|
31
|
+
timeout: 120_000,
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
assert.match(messages.join('\n'), /updating the global @swarmclawai\/swarmclaw install via pnpm/i)
|
|
35
|
+
assert.match(messages.join('\n'), /global update complete via pnpm/i)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('runRegistrySelfUpdate reports a manual retry command when the registry update fails', () => {
|
|
39
|
+
const messages = []
|
|
40
|
+
|
|
41
|
+
const exitCode = runRegistrySelfUpdate(
|
|
42
|
+
'bun',
|
|
43
|
+
() => {
|
|
44
|
+
throw new Error('spawn bun ENOENT')
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
log: (message) => messages.push(`log:${message}`),
|
|
48
|
+
logError: (message) => messages.push(`err:${message}`),
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
assert.equal(exitCode, 1)
|
|
53
|
+
assert.match(messages.join('\n'), /registry update failed: spawn bun ENOENT/i)
|
|
54
|
+
assert.match(messages.join('\n'), /retry manually with: bun add -g @swarmclawai\/swarmclaw@latest/i)
|
|
55
|
+
})
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "Self-hosted AI agent orchestration dashboard — manage LLM providers, orchestrate agent swarms, schedule tasks, and bridge agents to chat platforms.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public",
|
|
8
|
+
"registry": "https://registry.npmjs.org/"
|
|
9
|
+
},
|
|
6
10
|
"repository": {
|
|
7
11
|
"type": "git",
|
|
8
12
|
"url": "https://github.com/swarmclawai/swarmclaw.git"
|
|
@@ -46,13 +50,14 @@
|
|
|
46
50
|
"start": "next start",
|
|
47
51
|
"start:standalone": "node .next/standalone/server.js",
|
|
48
52
|
"benchmark:autonomy": "node ./scripts/benchmark-autonomy-harness.mjs",
|
|
53
|
+
"benchmark:agent-regression": "node --import tsx ./scripts/run-agent-regression-suite.ts",
|
|
49
54
|
"lint": "eslint",
|
|
50
55
|
"lint:fix": "eslint --fix",
|
|
51
56
|
"lint:baseline": "node ./scripts/lint-baseline.mjs check",
|
|
52
57
|
"lint:baseline:update": "node ./scripts/lint-baseline.mjs update",
|
|
53
58
|
"cli": "node ./bin/swarmclaw.js",
|
|
54
|
-
"test:cli": "node --test src/cli
|
|
55
|
-
"test:openclaw": "tsx --test src/lib/server/connectors/
|
|
59
|
+
"test:cli": "node --test src/cli/*.test.js bin/*.test.js",
|
|
60
|
+
"test:openclaw": "tsx --test src/lib/openclaw-agent-id.test.ts src/lib/openclaw-endpoint.test.ts src/lib/server/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-skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/task-quality-gate.test.ts src/lib/server/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts",
|
|
56
61
|
"test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
|
|
57
62
|
"postinstall": "node ./scripts/postinstall.mjs"
|
|
58
63
|
},
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { writeFileSync } from 'node:fs'
|
|
3
4
|
import { spawnSync } from 'node:child_process'
|
|
5
|
+
const INSTALL_METADATA_FILE = '.swarmclaw-install.json'
|
|
6
|
+
|
|
7
|
+
function detectPackageManagerFromUserAgent(userAgent) {
|
|
8
|
+
const normalized = String(userAgent || '').toLowerCase()
|
|
9
|
+
if (normalized.startsWith('pnpm/')) return 'pnpm'
|
|
10
|
+
if (normalized.startsWith('yarn/')) return 'yarn'
|
|
11
|
+
if (normalized.startsWith('bun/')) return 'bun'
|
|
12
|
+
if (normalized.startsWith('npm/')) return 'npm'
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const installedWith = detectPackageManagerFromUserAgent(process.env.npm_config_user_agent) || 'npm'
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
writeFileSync(
|
|
20
|
+
new URL(`../${INSTALL_METADATA_FILE}`, import.meta.url),
|
|
21
|
+
JSON.stringify({
|
|
22
|
+
packageManager: installedWith,
|
|
23
|
+
installedAt: new Date().toISOString(),
|
|
24
|
+
}, null, 2),
|
|
25
|
+
'utf8',
|
|
26
|
+
)
|
|
27
|
+
} catch {
|
|
28
|
+
// Ignore metadata write failures for install resilience.
|
|
29
|
+
}
|
|
4
30
|
|
|
5
31
|
const result = spawnSync('npm', ['rebuild', 'better-sqlite3', '--silent'], {
|
|
6
32
|
stdio: 'ignore',
|
|
@@ -23,6 +23,23 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
23
23
|
body.apiEndpoint,
|
|
24
24
|
)
|
|
25
25
|
}
|
|
26
|
+
if (body.routingTargets !== undefined && Array.isArray(body.routingTargets)) {
|
|
27
|
+
agent.routingTargets = body.routingTargets.map((target: Record<string, unknown>, index: number) => ({
|
|
28
|
+
id: typeof target.id === 'string' && target.id.trim() ? target.id.trim() : `route-${index + 1}`,
|
|
29
|
+
label: typeof target.label === 'string' ? target.label : undefined,
|
|
30
|
+
role: target.role,
|
|
31
|
+
provider: (typeof target.provider === 'string' && target.provider.trim() ? target.provider : agent.provider),
|
|
32
|
+
model: typeof target.model === 'string' ? target.model : '',
|
|
33
|
+
credentialId: target.credentialId ?? null,
|
|
34
|
+
fallbackCredentialIds: Array.isArray(target.fallbackCredentialIds) ? target.fallbackCredentialIds : [],
|
|
35
|
+
apiEndpoint: normalizeProviderEndpoint(
|
|
36
|
+
typeof target.provider === 'string' ? target.provider : agent.provider,
|
|
37
|
+
typeof target.apiEndpoint === 'string' ? target.apiEndpoint : null,
|
|
38
|
+
),
|
|
39
|
+
gatewayProfileId: target.gatewayProfileId ?? null,
|
|
40
|
+
priority: typeof target.priority === 'number' ? target.priority : index + 1,
|
|
41
|
+
}))
|
|
42
|
+
}
|
|
26
43
|
delete (agent as Record<string, unknown>).isOrchestrator
|
|
27
44
|
agent.isOrchestrator = agent.platformAssignScope === 'all'
|
|
28
45
|
delete (agent as Record<string, unknown>).id
|
|
@@ -1,96 +1,13 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import {
|
|
3
|
-
import { loadAgents, saveAgents, loadSessions, saveSessions } from '@/lib/server/storage'
|
|
4
|
-
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
2
|
+
import { ensureAgentThreadSession } from '@/lib/server/agent-thread-session'
|
|
5
3
|
|
|
6
4
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
7
5
|
const { id: agentId } = await params
|
|
8
|
-
const agents = loadAgents()
|
|
9
|
-
const agent = agents[agentId]
|
|
10
|
-
if (!agent) {
|
|
11
|
-
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
|
12
|
-
}
|
|
13
|
-
|
|
14
6
|
const body = await req.json().catch(() => ({}))
|
|
15
7
|
const user = body.user || 'default'
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (agent.threadSessionId && sessions[agent.threadSessionId]) {
|
|
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)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Legacy fallback for older shortcut sessions that were named using the
|
|
35
|
-
// old agent-thread convention before the explicit link was persisted.
|
|
36
|
-
const existing = Object.values(sessions).find(
|
|
37
|
-
(s: Record<string, unknown>) =>
|
|
38
|
-
(
|
|
39
|
-
s.shortcutForAgentId === agentId
|
|
40
|
-
|| s.name === `agent-thread:${agentId}`
|
|
41
|
-
)
|
|
42
|
-
&& s.user === user
|
|
43
|
-
)
|
|
44
|
-
if (existing) {
|
|
45
|
-
agent.threadSessionId = (existing as Record<string, unknown>).id as string
|
|
46
|
-
agent.updatedAt = Date.now()
|
|
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)
|
|
59
|
-
return NextResponse.json(existing)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Create a new shortcut chat session for this agent.
|
|
63
|
-
const sessionId = `agent-chat-${agentId}-${genId()}`
|
|
64
|
-
const now = Date.now()
|
|
65
|
-
const session = {
|
|
66
|
-
id: sessionId,
|
|
67
|
-
name: agent.name,
|
|
68
|
-
shortcutForAgentId: agentId,
|
|
69
|
-
cwd: WORKSPACE_DIR,
|
|
70
|
-
user: user,
|
|
71
|
-
provider: agent.provider,
|
|
72
|
-
model: agent.model,
|
|
73
|
-
credentialId: agent.credentialId || null,
|
|
74
|
-
fallbackCredentialIds: agent.fallbackCredentialIds || [],
|
|
75
|
-
apiEndpoint: agent.apiEndpoint || null,
|
|
76
|
-
claudeSessionId: null,
|
|
77
|
-
messages: [],
|
|
78
|
-
createdAt: now,
|
|
79
|
-
lastActiveAt: now,
|
|
80
|
-
active: false,
|
|
81
|
-
sessionType: 'human' as const,
|
|
82
|
-
agentId,
|
|
83
|
-
plugins: agent.plugins || agent.tools || [],
|
|
84
|
-
heartbeatEnabled: agent.heartbeatEnabled || false,
|
|
85
|
-
heartbeatIntervalSec: agent.heartbeatIntervalSec || null,
|
|
8
|
+
const session = ensureAgentThreadSession(agentId, user)
|
|
9
|
+
if (!session) {
|
|
10
|
+
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
|
86
11
|
}
|
|
87
|
-
|
|
88
|
-
sessions[sessionId] = session as Record<string, unknown>
|
|
89
|
-
saveSessions(sessions)
|
|
90
|
-
|
|
91
|
-
agent.threadSessionId = sessionId
|
|
92
|
-
agent.updatedAt = Date.now()
|
|
93
|
-
saveAgents(agents)
|
|
94
|
-
|
|
95
12
|
return NextResponse.json(session)
|
|
96
13
|
}
|
|
@@ -56,24 +56,46 @@ export async function POST(req: Request) {
|
|
|
56
56
|
id,
|
|
57
57
|
name: body.name,
|
|
58
58
|
description: body.description,
|
|
59
|
+
soul: body.soul || undefined,
|
|
59
60
|
systemPrompt: body.systemPrompt,
|
|
60
61
|
provider: body.provider,
|
|
61
62
|
model: body.model,
|
|
62
63
|
credentialId: body.credentialId,
|
|
64
|
+
fallbackCredentialIds: body.fallbackCredentialIds,
|
|
63
65
|
apiEndpoint: normalizeProviderEndpoint(body.provider, body.apiEndpoint || null),
|
|
66
|
+
gatewayProfileId: body.gatewayProfileId,
|
|
67
|
+
routingStrategy: body.routingStrategy,
|
|
68
|
+
routingTargets: body.routingTargets?.map((target) => ({
|
|
69
|
+
...target,
|
|
70
|
+
apiEndpoint: normalizeProviderEndpoint(target.provider, target.apiEndpoint || null),
|
|
71
|
+
})),
|
|
64
72
|
isOrchestrator: platformAssignScope === 'all',
|
|
65
73
|
platformAssignScope,
|
|
66
74
|
subAgentIds: body.subAgentIds,
|
|
67
75
|
plugins: body.plugins?.length ? body.plugins : (body.tools || []),
|
|
76
|
+
skills: body.skills,
|
|
77
|
+
skillIds: body.skillIds,
|
|
78
|
+
mcpServerIds: body.mcpServerIds,
|
|
79
|
+
mcpDisabledTools: body.mcpDisabledTools?.length ? body.mcpDisabledTools : undefined,
|
|
68
80
|
capabilities: body.capabilities,
|
|
69
81
|
thinkingLevel: body.thinkingLevel || undefined,
|
|
70
82
|
autoRecovery: body.autoRecovery || false,
|
|
83
|
+
heartbeatEnabled: body.heartbeatEnabled || false,
|
|
84
|
+
heartbeatInterval: body.heartbeatInterval,
|
|
85
|
+
heartbeatIntervalSec: body.heartbeatIntervalSec,
|
|
86
|
+
heartbeatModel: body.heartbeatModel,
|
|
87
|
+
heartbeatPrompt: body.heartbeatPrompt,
|
|
88
|
+
elevenLabsVoiceId: body.elevenLabsVoiceId,
|
|
71
89
|
monthlyBudget: body.monthlyBudget ?? null,
|
|
72
90
|
dailyBudget: body.dailyBudget ?? null,
|
|
73
91
|
hourlyBudget: body.hourlyBudget ?? null,
|
|
74
92
|
budgetAction: body.budgetAction || 'warn',
|
|
75
|
-
soul: body.soul || undefined,
|
|
76
93
|
identityState: body.identityState ?? null,
|
|
94
|
+
memoryScopeMode: body.memoryScopeMode,
|
|
95
|
+
memoryTierPreference: body.memoryTierPreference,
|
|
96
|
+
projectId: body.projectId,
|
|
97
|
+
avatarSeed: body.avatarSeed,
|
|
98
|
+
avatarUrl: body.avatarUrl,
|
|
77
99
|
sessionResetMode: body.sessionResetMode ?? null,
|
|
78
100
|
sessionIdleTimeoutSec: body.sessionIdleTimeoutSec ?? null,
|
|
79
101
|
sessionMaxAgeSec: body.sessionMaxAgeSec ?? null,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { validateAccessKey,
|
|
2
|
+
import { validateAccessKey, isFirstTimeSetup, markSetupComplete } from '@/lib/server/storage'
|
|
3
3
|
import { ensureDaemonStarted } from '@/lib/server/daemon-state'
|
|
4
4
|
import { AUTH_COOKIE_NAME, getCookieValue } from '@/lib/auth'
|
|
5
5
|
export const dynamic = 'force-dynamic'
|
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
resolveAgentApiEndpoint,
|
|
13
13
|
compactChatroomMessages,
|
|
14
14
|
buildChatroomSystemPrompt,
|
|
15
|
-
|
|
15
|
+
ensureSyntheticSession,
|
|
16
|
+
appendSyntheticSessionMessage,
|
|
16
17
|
buildAgentSystemPromptForChatroom,
|
|
17
18
|
buildHistoryForAgent,
|
|
18
19
|
isMuted,
|
|
@@ -21,6 +22,7 @@ import { filterHealthyChatroomAgents } from '@/lib/server/chatroom-health'
|
|
|
21
22
|
import { evaluateRoutingRules } from '@/lib/server/chatroom-routing'
|
|
22
23
|
import { markProviderFailure, markProviderSuccess } from '@/lib/server/provider-health'
|
|
23
24
|
import { applyAgentReactionsFromText } from '@/lib/server/chatroom-orchestration'
|
|
25
|
+
import { resolvePrimaryAgentRoute } from '@/lib/server/agent-runtime-config'
|
|
24
26
|
import type { Chatroom, ChatroomMessage, Agent } from '@/types'
|
|
25
27
|
|
|
26
28
|
export const dynamic = 'force-dynamic'
|
|
@@ -150,9 +152,10 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
// Pre-flight: check if the agent's provider is usable before attempting to stream
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
const
|
|
155
|
+
const route = resolvePrimaryAgentRoute(agent)
|
|
156
|
+
const providerInfo = getProvider(route?.provider || agent.provider)
|
|
157
|
+
const apiKey = resolveApiKey(route?.credentialId || agent.credentialId)
|
|
158
|
+
const resolvedEndpoint = route?.apiEndpoint || resolveAgentApiEndpoint(agent)
|
|
156
159
|
if (providerInfo?.requiresApiKey && !apiKey) {
|
|
157
160
|
writeEvent({ t: 'cr_agent_start', agentId: agent.id, agentName: agent.name })
|
|
158
161
|
writeEvent({ t: 'err', text: `${agent.name} has no API credentials configured`, agentId: agent.id, agentName: agent.name })
|
|
@@ -177,7 +180,12 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
177
180
|
notify(`chatroom:${id}`)
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
const syntheticSession =
|
|
183
|
+
const syntheticSession = ensureSyntheticSession(agent, id)
|
|
184
|
+
syntheticSession.provider = route?.provider || syntheticSession.provider
|
|
185
|
+
syntheticSession.model = route?.model || syntheticSession.model
|
|
186
|
+
syntheticSession.credentialId = route?.credentialId ?? syntheticSession.credentialId ?? null
|
|
187
|
+
syntheticSession.fallbackCredentialIds = route?.fallbackCredentialIds || syntheticSession.fallbackCredentialIds || []
|
|
188
|
+
syntheticSession.gatewayProfileId = route?.gatewayProfileId ?? syntheticSession.gatewayProfileId ?? null
|
|
181
189
|
syntheticSession.apiEndpoint = resolvedEndpoint
|
|
182
190
|
const agentSystemPrompt = buildAgentSystemPromptForChatroom(agent)
|
|
183
191
|
const chatroomContext = buildChatroomSystemPrompt(freshChatroom, agents, agent.id)
|
|
@@ -186,6 +194,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
186
194
|
|
|
187
195
|
// Use enriched context message for chained agents, or reply context + original text
|
|
188
196
|
const messageForAgent = item.contextMessage || (replyContext + text)
|
|
197
|
+
appendSyntheticSessionMessage(syntheticSession.id, 'user', messageForAgent)
|
|
189
198
|
|
|
190
199
|
let fullText = ''
|
|
191
200
|
let agentError = ''
|
|
@@ -223,12 +232,14 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
223
232
|
|
|
224
233
|
// Don't persist empty or error-only messages — they pollute chat history
|
|
225
234
|
if (!responseText.trim() && agentError) {
|
|
235
|
+
appendSyntheticSessionMessage(syntheticSession.id, 'assistant', agentError)
|
|
226
236
|
markProviderFailure(agent.provider, agentError)
|
|
227
237
|
writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
|
|
228
238
|
return []
|
|
229
239
|
}
|
|
230
240
|
|
|
231
241
|
if (responseText.trim()) {
|
|
242
|
+
appendSyntheticSessionMessage(syntheticSession.id, 'assistant', responseText)
|
|
232
243
|
const parsedMentions = parseMentions(responseText, agents, freshChatroom.agentIds)
|
|
233
244
|
const chainedHealth = filterHealthyChatroomAgents(parsedMentions, agents)
|
|
234
245
|
const newMentions = chainedHealth.healthyAgentIds
|
|
@@ -33,7 +33,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
33
33
|
chatroom.updatedAt = Date.now()
|
|
34
34
|
chatrooms[id] = chatroom
|
|
35
35
|
saveChatrooms(chatrooms)
|
|
36
|
+
notify('chatrooms')
|
|
36
37
|
notify(`chatroom:${id}`)
|
|
37
38
|
|
|
38
|
-
return NextResponse.json(
|
|
39
|
+
return NextResponse.json(chatroom)
|
|
39
40
|
}
|
|
@@ -36,7 +36,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
36
36
|
chatroom.updatedAt = Date.now()
|
|
37
37
|
chatrooms[id] = chatroom
|
|
38
38
|
saveChatrooms(chatrooms)
|
|
39
|
+
notify('chatrooms')
|
|
39
40
|
notify(`chatroom:${id}`)
|
|
40
41
|
|
|
41
|
-
return NextResponse.json(
|
|
42
|
+
return NextResponse.json(chatroom)
|
|
42
43
|
}
|
|
@@ -33,6 +33,12 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
33
33
|
|
|
34
34
|
// Diff agentIds and inject join/leave system messages
|
|
35
35
|
if (Array.isArray(body.agentIds)) {
|
|
36
|
+
if (body.agentIds.length === 0) {
|
|
37
|
+
return NextResponse.json(
|
|
38
|
+
{ error: 'Select at least one chatroom member.' },
|
|
39
|
+
{ status: 400 },
|
|
40
|
+
)
|
|
41
|
+
}
|
|
36
42
|
const agents = loadAgents()
|
|
37
43
|
const invalidAgentIds = (body.agentIds as string[]).filter((agentId) => !agents[agentId])
|
|
38
44
|
if (invalidAgentIds.length > 0) {
|
|
@@ -2,6 +2,7 @@ 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
4
|
import { normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
|
|
5
|
+
import { resolvePrimaryAgentRoute } from '@/lib/server/agent-runtime-config'
|
|
5
6
|
|
|
6
7
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
7
8
|
const { id } = await params
|
|
@@ -17,6 +18,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
const linkedAgent = nextAgentId ? loadAgents()[nextAgentId] : null
|
|
21
|
+
const linkedRoute = linkedAgent ? resolvePrimaryAgentRoute(linkedAgent) : null
|
|
20
22
|
|
|
21
23
|
if (updates.name !== undefined) sessions[id].name = updates.name
|
|
22
24
|
if (updates.cwd !== undefined) sessions[id].cwd = updates.cwd
|
|
@@ -24,11 +26,19 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
24
26
|
else if (agentIdUpdateProvided && linkedAgent?.provider) sessions[id].provider = linkedAgent.provider
|
|
25
27
|
|
|
26
28
|
if (updates.model !== undefined) sessions[id].model = updates.model
|
|
29
|
+
else if (agentIdUpdateProvided && linkedRoute?.model) sessions[id].model = linkedRoute.model
|
|
27
30
|
else if (agentIdUpdateProvided && linkedAgent?.model !== undefined) sessions[id].model = linkedAgent.model
|
|
28
31
|
|
|
29
32
|
if (updates.credentialId !== undefined) sessions[id].credentialId = updates.credentialId
|
|
33
|
+
else if (agentIdUpdateProvided && linkedRoute) sessions[id].credentialId = linkedRoute.credentialId ?? null
|
|
30
34
|
else if (agentIdUpdateProvided && linkedAgent) sessions[id].credentialId = linkedAgent.credentialId ?? null
|
|
31
35
|
|
|
36
|
+
if (updates.fallbackCredentialIds !== undefined) sessions[id].fallbackCredentialIds = updates.fallbackCredentialIds
|
|
37
|
+
else if (agentIdUpdateProvided && linkedRoute) sessions[id].fallbackCredentialIds = [...linkedRoute.fallbackCredentialIds]
|
|
38
|
+
|
|
39
|
+
if (updates.gatewayProfileId !== undefined) sessions[id].gatewayProfileId = updates.gatewayProfileId
|
|
40
|
+
else if (agentIdUpdateProvided && linkedRoute) sessions[id].gatewayProfileId = linkedRoute.gatewayProfileId ?? null
|
|
41
|
+
|
|
32
42
|
if (updates.plugins !== undefined) sessions[id].plugins = updates.plugins
|
|
33
43
|
else if (agentIdUpdateProvided && linkedAgent) sessions[id].plugins = Array.isArray(linkedAgent.plugins) ? linkedAgent.plugins : []
|
|
34
44
|
|
|
@@ -37,6 +47,8 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
37
47
|
updates.provider || sessions[id].provider,
|
|
38
48
|
updates.apiEndpoint,
|
|
39
49
|
)
|
|
50
|
+
} else if (agentIdUpdateProvided && linkedRoute) {
|
|
51
|
+
sessions[id].apiEndpoint = linkedRoute.apiEndpoint ?? null
|
|
40
52
|
} else if (agentIdUpdateProvided && linkedAgent) {
|
|
41
53
|
sessions[id].apiEndpoint = normalizeProviderEndpoint(
|
|
42
54
|
linkedAgent.provider,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
+
import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/heartbeat-defaults'
|
|
2
3
|
import { disableAllSessionHeartbeats, loadSettings, saveSettings } from '@/lib/server/storage'
|
|
3
4
|
import { cancelAllHeartbeatRuns } from '@/lib/server/session-run-manager'
|
|
4
5
|
|
|
@@ -11,7 +12,7 @@ export async function POST(req: Request) {
|
|
|
11
12
|
|
|
12
13
|
const updatedSessions = disableAllSessionHeartbeats()
|
|
13
14
|
const settings = loadSettings()
|
|
14
|
-
if ((settings.heartbeatIntervalSec ??
|
|
15
|
+
if ((settings.heartbeatIntervalSec ?? DEFAULT_HEARTBEAT_INTERVAL_SEC) !== 0) {
|
|
15
16
|
settings.heartbeatIntervalSec = 0
|
|
16
17
|
saveSettings(settings)
|
|
17
18
|
}
|
|
@@ -7,6 +7,7 @@ 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 { applyResolvedRoute, resolvePrimaryAgentRoute } from '@/lib/server/agent-runtime-config'
|
|
10
11
|
export const dynamic = 'force-dynamic'
|
|
11
12
|
|
|
12
13
|
|
|
@@ -60,6 +61,7 @@ export async function POST(req: Request) {
|
|
|
60
61
|
const id = body.id || genId()
|
|
61
62
|
const sessions = loadSessions()
|
|
62
63
|
const agent = body.agentId ? loadAgents()[body.agentId] : null
|
|
64
|
+
const resolvedRoute = agent ? resolvePrimaryAgentRoute(agent) : null
|
|
63
65
|
const requestedPlugins = Array.isArray(body.plugins) ? body.plugins : (Array.isArray(body.tools) ? body.tools : null)
|
|
64
66
|
const resolvedPlugins = requestedPlugins ?? (Array.isArray(agent?.plugins) ? agent.plugins : (Array.isArray(agent?.tools) ? agent.tools : []))
|
|
65
67
|
|
|
@@ -70,12 +72,13 @@ export async function POST(req: Request) {
|
|
|
70
72
|
|
|
71
73
|
const sessionName = body.name || 'New Chat'
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
const nextSession = {
|
|
74
76
|
id, name: sessionName, cwd,
|
|
75
77
|
user: body.user || 'user',
|
|
76
78
|
provider: body.provider || agent?.provider || 'claude-cli',
|
|
77
79
|
model: body.model || agent?.model || '',
|
|
78
80
|
credentialId: body.credentialId || agent?.credentialId || null,
|
|
81
|
+
fallbackCredentialIds: body.fallbackCredentialIds || agent?.fallbackCredentialIds || [],
|
|
79
82
|
apiEndpoint: normalizeProviderEndpoint(
|
|
80
83
|
body.provider || agent?.provider || 'claude-cli',
|
|
81
84
|
body.apiEndpoint || agent?.apiEndpoint || null,
|
|
@@ -113,6 +116,9 @@ export async function POST(req: Request) {
|
|
|
113
116
|
identityState: body.identityState ?? agent?.identityState ?? null,
|
|
114
117
|
sessionArchiveState: body.sessionArchiveState ?? null,
|
|
115
118
|
}
|
|
119
|
+
sessions[id] = (body.provider || body.model || body.credentialId || body.apiEndpoint)
|
|
120
|
+
? nextSession
|
|
121
|
+
: applyResolvedRoute(nextSession, resolvedRoute)
|
|
116
122
|
saveSessions(sessions)
|
|
117
123
|
notify('sessions')
|
|
118
124
|
return NextResponse.json(sessions[id])
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadExternalAgents, saveExternalAgents } from '@/lib/server/storage'
|
|
3
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
5
|
+
export const dynamic = 'force-dynamic'
|
|
6
|
+
|
|
7
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
|
+
const { id } = await params
|
|
9
|
+
const body = await req.json().catch(() => ({}))
|
|
10
|
+
const items = loadExternalAgents()
|
|
11
|
+
const runtime = items[id]
|
|
12
|
+
if (!runtime) return notFound()
|
|
13
|
+
const now = Date.now()
|
|
14
|
+
runtime.lastHeartbeatAt = now
|
|
15
|
+
runtime.lastSeenAt = now
|
|
16
|
+
runtime.updatedAt = now
|
|
17
|
+
runtime.status = body.status || 'online'
|
|
18
|
+
if (body.tokenStats && typeof body.tokenStats === 'object') {
|
|
19
|
+
runtime.tokenStats = {
|
|
20
|
+
...(runtime.tokenStats || {}),
|
|
21
|
+
...body.tokenStats,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (body.metadata && typeof body.metadata === 'object') {
|
|
25
|
+
runtime.metadata = {
|
|
26
|
+
...(runtime.metadata || {}),
|
|
27
|
+
...body.metadata,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
saveExternalAgents(items)
|
|
31
|
+
notify('external_agents')
|
|
32
|
+
return NextResponse.json({ ok: true, id, lastHeartbeatAt: now })
|
|
33
|
+
}
|