@swarmclawai/swarmclaw 1.2.3 → 1.2.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 +20 -0
- package/bin/daemon-cmd.js +169 -0
- package/bin/server-cmd.js +3 -0
- package/bin/swarmclaw.js +11 -0
- package/package.json +17 -16
- package/src/app/api/agents/[id]/clone/route.ts +3 -32
- package/src/app/api/agents/[id]/route.ts +6 -158
- package/src/app/api/agents/[id]/status/route.ts +2 -3
- package/src/app/api/agents/[id]/thread/route.ts +4 -17
- package/src/app/api/agents/bulk/route.ts +5 -47
- package/src/app/api/agents/route.ts +5 -119
- package/src/app/api/agents/trash/route.ts +13 -24
- package/src/app/api/auth/route.ts +3 -9
- package/src/app/api/autonomy/estop/route.ts +5 -5
- package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
- package/src/app/api/chatrooms/[id]/route.ts +23 -2
- package/src/app/api/chatrooms/route.ts +13 -2
- package/src/app/api/chats/[id]/clear/route.ts +2 -13
- package/src/app/api/chats/[id]/deploy/route.ts +2 -3
- package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
- package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
- package/src/app/api/chats/[id]/queue/route.ts +17 -64
- package/src/app/api/chats/[id]/retry/route.ts +4 -22
- package/src/app/api/chats/[id]/route.ts +10 -138
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/migrate-messages/route.ts +7 -0
- package/src/app/api/chats/route.ts +13 -134
- package/src/app/api/connectors/[id]/access/route.ts +12 -229
- package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
- package/src/app/api/connectors/[id]/health/route.ts +12 -39
- package/src/app/api/connectors/[id]/route.ts +14 -122
- package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
- package/src/app/api/connectors/doctor/route.ts +1 -1
- package/src/app/api/connectors/route.ts +12 -70
- package/src/app/api/credentials/[id]/route.ts +2 -4
- package/src/app/api/credentials/route.ts +10 -19
- package/src/app/api/daemon/health-check/route.ts +3 -4
- package/src/app/api/daemon/route.ts +10 -8
- package/src/app/api/documents/route.ts +11 -10
- package/src/app/api/external-agents/route.ts +3 -3
- package/src/app/api/gateways/[id]/health/route.ts +2 -3
- package/src/app/api/gateways/[id]/route.ts +7 -122
- package/src/app/api/gateways/route.ts +3 -103
- package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
- package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
- package/src/app/api/openclaw/directory/route.ts +2 -2
- package/src/app/api/openclaw/history/route.ts +3 -5
- package/src/app/api/providers/[id]/models/route.test.ts +60 -0
- package/src/app/api/providers/[id]/models/route.ts +33 -1
- package/src/app/api/providers/[id]/route.test.ts +49 -0
- package/src/app/api/providers/[id]/route.ts +30 -1
- package/src/app/api/providers/ollama/route.ts +6 -5
- package/src/app/api/schedules/[id]/route.ts +14 -108
- package/src/app/api/schedules/[id]/run/route.ts +6 -67
- package/src/app/api/schedules/route.ts +9 -51
- package/src/app/api/settings/route.ts +4 -3
- package/src/app/api/setup/check-provider/route.ts +15 -1
- package/src/app/api/setup/openclaw-device/route.ts +2 -2
- package/src/app/api/system/status/route.ts +2 -2
- package/src/app/api/tasks/[id]/route.ts +16 -202
- package/src/app/api/tasks/bulk/route.ts +5 -86
- package/src/app/api/tasks/metrics/route.ts +2 -1
- package/src/app/api/tasks/route.ts +11 -171
- package/src/app/api/upload/route.ts +1 -1
- package/src/app/api/uploads/[filename]/route.ts +1 -1
- package/src/app/api/uploads/route.ts +1 -1
- package/src/app/api/webhooks/[id]/history/route.ts +2 -2
- package/src/app/layout.tsx +9 -6
- package/src/app/protocols/page.tsx +71 -89
- package/src/app/tasks/page.tsx +32 -32
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-sheet.tsx +51 -25
- package/src/components/agents/inspector-panel.tsx +15 -4
- package/src/components/auth/setup-wizard/index.tsx +27 -18
- package/src/components/auth/setup-wizard/shared.tsx +2 -2
- package/src/components/auth/setup-wizard/step-agents.tsx +51 -38
- package/src/components/auth/setup-wizard/step-connect.tsx +48 -17
- package/src/components/auth/setup-wizard/types.ts +6 -4
- package/src/components/auth/setup-wizard/utils.test.ts +38 -8
- package/src/components/auth/setup-wizard/utils.ts +14 -8
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
- package/src/components/connectors/connector-list.tsx +26 -40
- package/src/components/connectors/connector-sheet.tsx +95 -149
- package/src/components/gateways/gateway-sheet.tsx +61 -110
- package/src/components/layout/live-query-sync.tsx +121 -0
- package/src/components/protocols/structured-session-launcher.tsx +24 -45
- package/src/components/providers/app-query-provider.tsx +17 -0
- package/src/components/providers/provider-list.tsx +150 -77
- package/src/components/providers/provider-sheet.tsx +102 -77
- package/src/components/shared/model-combobox.tsx +5 -4
- package/src/components/skills/skill-list.tsx +5 -18
- package/src/components/skills/skill-sheet.tsx +21 -20
- package/src/components/skills/skills-workspace.tsx +48 -87
- package/src/components/tasks/task-card.tsx +20 -13
- package/src/components/tasks/task-column.tsx +22 -7
- package/src/components/tasks/task-list.tsx +8 -11
- package/src/components/tasks/task-sheet.tsx +111 -103
- package/src/features/agents/queries.ts +20 -0
- package/src/features/chatrooms/queries.ts +20 -0
- package/src/features/chats/queries.ts +27 -0
- package/src/features/connectors/queries.ts +145 -0
- package/src/features/credentials/queries.ts +37 -0
- package/src/features/extensions/queries.ts +26 -0
- package/src/features/external-agents/queries.ts +36 -0
- package/src/features/gateways/queries.ts +274 -0
- package/src/features/missions/queries.ts +23 -0
- package/src/features/projects/queries.ts +20 -0
- package/src/features/protocols/queries.ts +149 -0
- package/src/features/providers/queries.ts +142 -0
- package/src/features/settings/queries.ts +20 -0
- package/src/features/skills/queries.ts +182 -0
- package/src/features/tasks/queries.ts +189 -0
- package/src/hooks/use-ws.ts +3 -2
- package/src/lib/agent-provider-options.test.ts +152 -0
- package/src/lib/agent-provider-options.ts +84 -0
- package/src/lib/app/api-client.ts +2 -2
- package/src/lib/providers/index.test.ts +78 -0
- package/src/lib/providers/index.ts +13 -10
- package/src/lib/query/client.ts +17 -0
- package/src/lib/server/agents/agent-runtime-config.ts +6 -6
- package/src/lib/server/agents/agent-service.ts +429 -0
- package/src/lib/server/agents/agent-thread-session.ts +6 -5
- package/src/lib/server/agents/autonomy-contract.ts +1 -4
- package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
- package/src/lib/server/agents/delegation-advisory.ts +251 -0
- package/src/lib/server/agents/main-agent-loop.ts +98 -40
- package/src/lib/server/agents/subagent-runtime.ts +12 -0
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
- package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
- package/src/lib/server/build-llm.ts +7 -15
- package/src/lib/server/capability-router.test.ts +70 -1
- package/src/lib/server/capability-router.ts +24 -99
- package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
- package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
- package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
- package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
- package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
- package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
- package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
- package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
- package/src/lib/server/chat-execution/message-classifier.ts +74 -32
- package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
- package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
- package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
- package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
- package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
- package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
- package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
- package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
- package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
- package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
- package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
- package/src/lib/server/chats/chat-session-service.ts +410 -0
- package/src/lib/server/connectors/access.ts +1 -1
- package/src/lib/server/connectors/commands.ts +7 -6
- package/src/lib/server/connectors/connector-inbound.ts +14 -7
- package/src/lib/server/connectors/connector-outbound.ts +16 -11
- package/src/lib/server/connectors/connector-service.ts +453 -0
- package/src/lib/server/connectors/delivery.ts +17 -12
- package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
- package/src/lib/server/connectors/media.ts +1 -1
- package/src/lib/server/connectors/response-media.ts +1 -1
- package/src/lib/server/connectors/session-consolidation.ts +11 -7
- package/src/lib/server/connectors/session.ts +9 -7
- package/src/lib/server/connectors/voice-note.ts +2 -1
- package/src/lib/server/context-manager.ts +20 -1
- package/src/lib/server/cost.ts +2 -3
- package/src/lib/server/credentials/credential-repository.ts +43 -4
- package/src/lib/server/credentials/credential-service.ts +112 -0
- package/src/lib/server/daemon/admin-metadata.ts +64 -0
- package/src/lib/server/daemon/controller.ts +577 -0
- package/src/lib/server/daemon/daemon-runtime.ts +352 -0
- package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
- package/src/lib/server/daemon/types.ts +101 -0
- package/src/lib/server/embeddings.ts +3 -9
- package/src/lib/server/eval/agent-regression.ts +3 -2
- package/src/lib/server/eval/runner.ts +2 -2
- package/src/lib/server/execution-brief.test.ts +167 -0
- package/src/lib/server/execution-brief.ts +295 -0
- package/src/lib/server/execution-engine/chat-turn.ts +9 -0
- package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
- package/src/lib/server/execution-engine/index.ts +35 -0
- package/src/lib/server/execution-engine/task-attempt.ts +303 -0
- package/src/lib/server/execution-engine/types.ts +33 -0
- package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
- package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
- package/src/lib/server/memory/session-archive-memory.ts +12 -10
- package/src/lib/server/messages/message-repository.ts +330 -0
- package/src/lib/server/missions/mission-service/core.ts +8 -6
- package/src/lib/server/openclaw/agent-resolver.ts +2 -3
- package/src/lib/server/openclaw/doctor.ts +1 -1
- package/src/lib/server/openclaw/gateway.test.ts +10 -1
- package/src/lib/server/openclaw/gateway.ts +5 -14
- package/src/lib/server/openclaw/health.ts +3 -11
- package/src/lib/server/openclaw/sync.ts +8 -6
- package/src/lib/server/persistence/storage-context.ts +3 -0
- package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
- package/src/lib/server/protocols/protocol-normalization.ts +1 -1
- package/src/lib/server/protocols/protocol-queries.ts +13 -7
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
- package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
- package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
- package/src/lib/server/protocols/protocol-swarm.ts +8 -8
- package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
- package/src/lib/server/protocols/protocol-templates.ts +4 -2
- package/src/lib/server/protocols/protocol-types.ts +10 -7
- package/src/lib/server/provider-endpoint.ts +7 -12
- package/src/lib/server/provider-model-discovery.ts +2 -11
- package/src/lib/server/query-expansion.ts +5 -6
- package/src/lib/server/run-context.test.ts +365 -0
- package/src/lib/server/run-context.ts +367 -0
- package/src/lib/server/runtime/heartbeat-service.ts +7 -5
- package/src/lib/server/runtime/queue/core.ts +61 -190
- package/src/lib/server/runtime/run-ledger.ts +8 -0
- package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
- package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
- package/src/lib/server/schedules/schedule-route-service.ts +230 -0
- package/src/lib/server/service-result.ts +16 -0
- package/src/lib/server/session-note.ts +2 -3
- package/src/lib/server/session-reset-policy.ts +4 -3
- package/src/lib/server/session-tools/connector.ts +9 -6
- package/src/lib/server/session-tools/context-mgmt.ts +58 -9
- package/src/lib/server/session-tools/crud.ts +162 -10
- package/src/lib/server/session-tools/delegate.ts +1 -1
- package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
- package/src/lib/server/session-tools/memory.ts +6 -4
- package/src/lib/server/session-tools/session-info.test.ts +56 -0
- package/src/lib/server/session-tools/session-info.ts +119 -12
- package/src/lib/server/session-tools/skill-runtime.ts +3 -1
- package/src/lib/server/session-tools/skills.ts +15 -15
- package/src/lib/server/session-tools/subagent.test.ts +115 -1
- package/src/lib/server/session-tools/subagent.ts +125 -7
- package/src/lib/server/session-tools/team-context.ts +4 -3
- package/src/lib/server/session-tools/wallet.ts +0 -58
- package/src/lib/server/sessions/session-lineage.ts +55 -0
- package/src/lib/server/sessions/session-repository.ts +2 -2
- package/src/lib/server/skills/learned-skills.ts +24 -23
- package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
- package/src/lib/server/skills/skill-repository.ts +136 -13
- package/src/lib/server/skills/skill-suggestions.ts +25 -28
- package/src/lib/server/storage-normalization.test.ts +42 -215
- package/src/lib/server/storage-normalization.ts +98 -0
- package/src/lib/server/storage.ts +19 -0
- package/src/lib/server/structured-extract.ts +3 -14
- package/src/lib/server/tasks/task-followups.ts +16 -11
- package/src/lib/server/tasks/task-result.test.ts +25 -29
- package/src/lib/server/tasks/task-result.ts +5 -9
- package/src/lib/server/tasks/task-route-service.ts +449 -0
- package/src/lib/server/text-normalization.ts +41 -0
- package/src/lib/server/tool-planning.ts +6 -42
- package/src/lib/server/upload-path.ts +5 -0
- package/src/lib/server/working-state/extraction.ts +614 -0
- package/src/lib/server/working-state/normalization.ts +866 -0
- package/src/lib/server/working-state/prompt.ts +60 -0
- package/src/lib/server/working-state/repository.ts +38 -0
- package/src/lib/server/working-state/service.test.ts +253 -0
- package/src/lib/server/working-state/service.ts +293 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/ws-client.ts +3 -3
- package/src/stores/slices/task-slice.ts +1 -4
- package/src/stores/use-chatroom-store.ts +2 -2
- package/src/types/index.ts +288 -22
- package/src/views/settings/section-providers.tsx +2 -2
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
|
|
5
|
+
|
|
6
|
+
test('custom providers resolve from saved provider configs', () => {
|
|
7
|
+
const output = runWithTempDataDir<{
|
|
8
|
+
providerIds: string[]
|
|
9
|
+
supportsModelDiscovery: boolean | null
|
|
10
|
+
resolvedProviderName: string | null
|
|
11
|
+
hasHandler: boolean
|
|
12
|
+
}>(`
|
|
13
|
+
const storageModule = await import('@/lib/server/storage')
|
|
14
|
+
const storage = storageModule.default || storageModule
|
|
15
|
+
storage.saveProviderConfigs({
|
|
16
|
+
'custom-llama': {
|
|
17
|
+
id: 'custom-llama',
|
|
18
|
+
name: 'Llama.cpp',
|
|
19
|
+
type: 'custom',
|
|
20
|
+
baseUrl: 'http://127.0.0.1:8080/v1',
|
|
21
|
+
models: ['llama-3.1-8b'],
|
|
22
|
+
requiresApiKey: false,
|
|
23
|
+
credentialId: null,
|
|
24
|
+
isEnabled: true,
|
|
25
|
+
createdAt: 1,
|
|
26
|
+
updatedAt: 1,
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const providersModule = await import('@/lib/providers/index')
|
|
31
|
+
const providers = providersModule.default || providersModule
|
|
32
|
+
const providerList = providers.getProviderList()
|
|
33
|
+
const resolvedProvider = providers.getProvider('custom-llama')
|
|
34
|
+
|
|
35
|
+
console.log(JSON.stringify({
|
|
36
|
+
providerIds: providerList.map((provider) => provider.id),
|
|
37
|
+
supportsModelDiscovery: providerList.find((provider) => provider.id === 'custom-llama')?.supportsModelDiscovery ?? null,
|
|
38
|
+
resolvedProviderName: resolvedProvider?.name ?? null,
|
|
39
|
+
hasHandler: typeof resolvedProvider?.handler?.streamChat === 'function',
|
|
40
|
+
}))
|
|
41
|
+
`)
|
|
42
|
+
|
|
43
|
+
assert.equal(output.providerIds.includes('custom-llama'), true)
|
|
44
|
+
assert.equal(output.supportsModelDiscovery, false)
|
|
45
|
+
assert.equal(output.resolvedProviderName, 'Llama.cpp')
|
|
46
|
+
assert.equal(output.hasHandler, true)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('builtin provider override records do not surface as custom providers', () => {
|
|
50
|
+
const output = runWithTempDataDir<{ openAiCount: number }>(`
|
|
51
|
+
const storageModule = await import('@/lib/server/storage')
|
|
52
|
+
const storage = storageModule.default || storageModule
|
|
53
|
+
storage.saveProviderConfigs({
|
|
54
|
+
openai: {
|
|
55
|
+
id: 'openai',
|
|
56
|
+
name: 'OpenAI',
|
|
57
|
+
type: 'builtin',
|
|
58
|
+
baseUrl: '',
|
|
59
|
+
models: [],
|
|
60
|
+
requiresApiKey: true,
|
|
61
|
+
credentialId: null,
|
|
62
|
+
isEnabled: false,
|
|
63
|
+
createdAt: 1,
|
|
64
|
+
updatedAt: 1,
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const providersModule = await import('@/lib/providers/index')
|
|
69
|
+
const providers = providersModule.default || providersModule
|
|
70
|
+
const providerList = providers.getProviderList()
|
|
71
|
+
|
|
72
|
+
console.log(JSON.stringify({
|
|
73
|
+
openAiCount: providerList.filter((provider) => provider.id === 'openai').length,
|
|
74
|
+
}))
|
|
75
|
+
`)
|
|
76
|
+
|
|
77
|
+
assert.equal(output.openAiCount, 1)
|
|
78
|
+
})
|
|
@@ -9,7 +9,7 @@ import { streamOpenClawChat } from './openclaw'
|
|
|
9
9
|
import { errorMessage, sleep, jitteredBackoff } from '@/lib/shared-utils'
|
|
10
10
|
import { classifyProviderError } from './error-classification'
|
|
11
11
|
import { log } from '@/lib/server/logger'
|
|
12
|
-
import type { ProviderInfo, ProviderConfig as CustomProviderConfig, ProviderType } from '../../types'
|
|
12
|
+
import type { ProviderInfo, ProviderConfig as CustomProviderConfig, ProviderType, ProviderId } from '../../types'
|
|
13
13
|
|
|
14
14
|
const TAG = 'providers'
|
|
15
15
|
|
|
@@ -281,8 +281,11 @@ export const PROVIDERS: Record<string, BuiltinProviderConfig> = {
|
|
|
281
281
|
function getCustomProviders(): Record<string, CustomProviderConfig> {
|
|
282
282
|
try {
|
|
283
283
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
284
|
-
const { loadProviderConfigs } = require('
|
|
285
|
-
|
|
284
|
+
const { loadProviderConfigs } = require('@/lib/server/storage') as typeof import('@/lib/server/storage')
|
|
285
|
+
const configs = loadProviderConfigs() as Record<string, CustomProviderConfig>
|
|
286
|
+
return Object.fromEntries(
|
|
287
|
+
Object.entries(configs).filter(([, config]) => config?.type === 'custom'),
|
|
288
|
+
)
|
|
286
289
|
} catch {
|
|
287
290
|
return {}
|
|
288
291
|
}
|
|
@@ -291,7 +294,7 @@ function getCustomProviders(): Record<string, CustomProviderConfig> {
|
|
|
291
294
|
function getModelOverrides(): Record<string, string[]> {
|
|
292
295
|
try {
|
|
293
296
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
294
|
-
const { loadModelOverrides } = require('
|
|
297
|
+
const { loadModelOverrides } = require('@/lib/server/storage') as typeof import('@/lib/server/storage')
|
|
295
298
|
return loadModelOverrides()
|
|
296
299
|
} catch {
|
|
297
300
|
return {}
|
|
@@ -316,11 +319,11 @@ export function getProviderList(): ProviderInfo[] {
|
|
|
316
319
|
const customs: ProviderInfo[] = Object.values(getCustomProviders())
|
|
317
320
|
.filter((c) => c.isEnabled)
|
|
318
321
|
.map((c) => ({
|
|
319
|
-
id: c.id as
|
|
322
|
+
id: c.id as ProviderId,
|
|
320
323
|
name: c.name,
|
|
321
324
|
models: c.models,
|
|
322
325
|
defaultModels: c.models,
|
|
323
|
-
supportsModelDiscovery:
|
|
326
|
+
supportsModelDiscovery: false,
|
|
324
327
|
requiresApiKey: c.requiresApiKey,
|
|
325
328
|
requiresEndpoint: false as boolean,
|
|
326
329
|
defaultEndpoint: c.baseUrl,
|
|
@@ -331,7 +334,7 @@ export function getProviderList(): ProviderInfo[] {
|
|
|
331
334
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
332
335
|
const { getExtensionManager } = require('../server/extensions')
|
|
333
336
|
extensionProviders = getExtensionManager().getProviders().map((p: Record<string, unknown>) => ({
|
|
334
|
-
id: String(p.id) as
|
|
337
|
+
id: String(p.id) as ProviderId,
|
|
335
338
|
name: String(p.name),
|
|
336
339
|
models: p.models as string[],
|
|
337
340
|
defaultModels: p.models as string[],
|
|
@@ -353,7 +356,7 @@ export function getProvider(id: string): BuiltinProviderConfig | null {
|
|
|
353
356
|
const custom = customs[id]
|
|
354
357
|
if (custom?.isEnabled) {
|
|
355
358
|
return {
|
|
356
|
-
id: custom.id as
|
|
359
|
+
id: custom.id as ProviderId,
|
|
357
360
|
name: custom.name,
|
|
358
361
|
models: custom.models,
|
|
359
362
|
requiresApiKey: custom.requiresApiKey,
|
|
@@ -376,7 +379,7 @@ export function getProvider(id: string): BuiltinProviderConfig | null {
|
|
|
376
379
|
const found = extensionProviders.find((p: Record<string, unknown>) => p.id === id)
|
|
377
380
|
if (found) {
|
|
378
381
|
return {
|
|
379
|
-
id: found.id as
|
|
382
|
+
id: found.id as ProviderId,
|
|
380
383
|
name: found.name,
|
|
381
384
|
models: found.models,
|
|
382
385
|
requiresApiKey: found.requiresApiKey,
|
|
@@ -421,7 +424,7 @@ export async function streamChatWithFailover(
|
|
|
421
424
|
if (credId && i > 0) {
|
|
422
425
|
// Need to decrypt fallback credential
|
|
423
426
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
424
|
-
const { loadCredentials, decryptKey } = require('
|
|
427
|
+
const { loadCredentials, decryptKey } = require('@/lib/server/storage') as typeof import('@/lib/server/storage')
|
|
425
428
|
const creds = loadCredentials()
|
|
426
429
|
const cred = creds[credId]
|
|
427
430
|
if (cred?.encryptedKey) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { QueryClient } from '@tanstack/react-query'
|
|
2
|
+
|
|
3
|
+
export function createAppQueryClient(): QueryClient {
|
|
4
|
+
return new QueryClient({
|
|
5
|
+
defaultOptions: {
|
|
6
|
+
queries: {
|
|
7
|
+
staleTime: 15_000,
|
|
8
|
+
gcTime: 5 * 60_000,
|
|
9
|
+
retry: 0,
|
|
10
|
+
refetchOnWindowFocus: false,
|
|
11
|
+
},
|
|
12
|
+
mutations: {
|
|
13
|
+
retry: 0,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
}
|
|
@@ -3,11 +3,11 @@ import type {
|
|
|
3
3
|
AgentRoutingStrategy,
|
|
4
4
|
AgentRoutingTarget,
|
|
5
5
|
GatewayProfile,
|
|
6
|
-
|
|
6
|
+
ProviderId,
|
|
7
7
|
} from '@/types'
|
|
8
8
|
import { deriveOpenClawWsUrl, normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
|
|
9
9
|
import { resolveProviderApiEndpoint, resolveProviderCredentialId } from '@/lib/server/provider-endpoint'
|
|
10
|
-
import { loadGatewayProfiles } from '@/lib/server/
|
|
10
|
+
import { loadGatewayProfiles } from '@/lib/server/gateways/gateway-profile-repository'
|
|
11
11
|
import { isProviderCoolingDown } from '@/lib/server/provider-health'
|
|
12
12
|
|
|
13
13
|
const DEFAULT_OPENCLAW_ENDPOINT = 'http://localhost:18789/v1'
|
|
@@ -16,7 +16,7 @@ const DEFAULT_OPENCLAW_MODEL = 'default'
|
|
|
16
16
|
export interface ResolvedAgentRoute {
|
|
17
17
|
id: string
|
|
18
18
|
label: string
|
|
19
|
-
provider:
|
|
19
|
+
provider: ProviderId
|
|
20
20
|
model: string
|
|
21
21
|
ollamaMode?: Agent['ollamaMode']
|
|
22
22
|
credentialId?: string | null
|
|
@@ -36,7 +36,7 @@ interface GatewayRoutePreferences {
|
|
|
36
36
|
interface RouteSeed {
|
|
37
37
|
id: string
|
|
38
38
|
label?: string
|
|
39
|
-
provider?:
|
|
39
|
+
provider?: ProviderId | null
|
|
40
40
|
model?: string | null
|
|
41
41
|
ollamaMode?: Agent['ollamaMode']
|
|
42
42
|
credentialId?: string | null
|
|
@@ -258,7 +258,7 @@ function buildRouteFromSeed(
|
|
|
258
258
|
routePreferences?: GatewayRoutePreferences | null,
|
|
259
259
|
agentGatewayProfileId?: string | null,
|
|
260
260
|
): ResolvedAgentRoute | null {
|
|
261
|
-
const provider =
|
|
261
|
+
const provider: ProviderId = seed.provider || 'claude-cli'
|
|
262
262
|
const mergedPreferences = normalizeRoutePreferences({
|
|
263
263
|
preferredGatewayTags: seed.preferredGatewayTags ?? routePreferences?.preferredGatewayTags,
|
|
264
264
|
preferredGatewayUseCase: seed.preferredGatewayUseCase ?? routePreferences?.preferredGatewayUseCase,
|
|
@@ -406,7 +406,7 @@ export function resolvePrimaryAgentRoute(
|
|
|
406
406
|
}
|
|
407
407
|
|
|
408
408
|
export function applyResolvedRoute<T extends {
|
|
409
|
-
provider:
|
|
409
|
+
provider: ProviderId
|
|
410
410
|
model: string
|
|
411
411
|
ollamaMode?: Agent['ollamaMode']
|
|
412
412
|
credentialId?: string | null
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { genId } from '@/lib/id'
|
|
2
|
+
import { resolveAgentToolSelection } from '@/lib/agent-default-tools'
|
|
3
|
+
import { normalizeAgentSandboxConfig } from '@/lib/agent-sandbox-defaults'
|
|
4
|
+
import { normalizeCapabilitySelection } from '@/lib/capability-selection'
|
|
5
|
+
import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
|
|
6
|
+
import { normalizeOrchestratorConfig } from '@/lib/orchestrator-config'
|
|
7
|
+
import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agents/agent-availability'
|
|
8
|
+
import { suspendAgentReferences, purgeAgentReferences, restoreAgentSchedules } from '@/lib/server/agents/agent-cascade'
|
|
9
|
+
import { ensureAgentThreadSession } from '@/lib/server/agents/agent-thread-session'
|
|
10
|
+
import {
|
|
11
|
+
deleteAgent,
|
|
12
|
+
loadAgents,
|
|
13
|
+
loadTrashedAgents,
|
|
14
|
+
patchAgent,
|
|
15
|
+
saveAgent,
|
|
16
|
+
} from '@/lib/server/agents/agent-repository'
|
|
17
|
+
import { logActivity } from '@/lib/server/activity/activity-log'
|
|
18
|
+
import { getAgentSpendWindows } from '@/lib/server/cost'
|
|
19
|
+
import { serviceFail, serviceOk } from '@/lib/server/service-result'
|
|
20
|
+
import { listSessions, saveSession } from '@/lib/server/sessions/session-repository'
|
|
21
|
+
import { loadUsage } from '@/lib/server/usage/usage-repository'
|
|
22
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
23
|
+
import type { Agent, Session } from '@/types'
|
|
24
|
+
import type { ServiceResult } from '@/lib/server/service-result'
|
|
25
|
+
|
|
26
|
+
function normalizeStringList(value: unknown): string[] {
|
|
27
|
+
return Array.isArray(value)
|
|
28
|
+
? value.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0)
|
|
29
|
+
: []
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeOllamaMode(value: unknown): Agent['ollamaMode'] {
|
|
33
|
+
if (value === 'cloud') return 'cloud'
|
|
34
|
+
if (value === 'local') return 'local'
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function updateThreadShortcutSession(agentId: string, agent: Agent): void {
|
|
39
|
+
if (!agent.threadSessionId) return
|
|
40
|
+
const shortcut = listSessions()[agent.threadSessionId]
|
|
41
|
+
if (!shortcut) return
|
|
42
|
+
let changed = false
|
|
43
|
+
if (shortcut.name !== agent.name) {
|
|
44
|
+
shortcut.name = agent.name
|
|
45
|
+
changed = true
|
|
46
|
+
}
|
|
47
|
+
if (shortcut.shortcutForAgentId !== agentId) {
|
|
48
|
+
shortcut.shortcutForAgentId = agentId
|
|
49
|
+
changed = true
|
|
50
|
+
}
|
|
51
|
+
if (changed) saveSession(shortcut.id, shortcut)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function detachAgentSessions(agentId: string): number {
|
|
55
|
+
let detached = 0
|
|
56
|
+
for (const session of Object.values(listSessions())) {
|
|
57
|
+
if (!session || session.agentId !== agentId) continue
|
|
58
|
+
session.agentId = null
|
|
59
|
+
session.heartbeatEnabled = false
|
|
60
|
+
saveSession(session.id, session)
|
|
61
|
+
detached += 1
|
|
62
|
+
}
|
|
63
|
+
return detached
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function listAgentsForApi(): Record<string, Agent> {
|
|
67
|
+
const agents = loadAgents()
|
|
68
|
+
const sessions = listSessions()
|
|
69
|
+
const usage = loadUsage()
|
|
70
|
+
const now = Date.now()
|
|
71
|
+
for (const agent of Object.values(agents)) {
|
|
72
|
+
if (
|
|
73
|
+
(typeof agent.monthlyBudget === 'number' && agent.monthlyBudget > 0)
|
|
74
|
+
|| (typeof agent.dailyBudget === 'number' && agent.dailyBudget > 0)
|
|
75
|
+
|| (typeof agent.hourlyBudget === 'number' && agent.hourlyBudget > 0)
|
|
76
|
+
) {
|
|
77
|
+
const spend = getAgentSpendWindows(agent.id, now, {
|
|
78
|
+
sessions,
|
|
79
|
+
usage,
|
|
80
|
+
})
|
|
81
|
+
if (typeof agent.monthlyBudget === 'number' && agent.monthlyBudget > 0) agent.monthlySpend = spend.monthly
|
|
82
|
+
if (typeof agent.dailyBudget === 'number' && agent.dailyBudget > 0) agent.dailySpend = spend.daily
|
|
83
|
+
if (typeof agent.hourlyBudget === 'number' && agent.hourlyBudget > 0) agent.hourlySpend = spend.hourly
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return agents
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createAgent(input: {
|
|
90
|
+
body: Record<string, unknown>
|
|
91
|
+
rawRecord?: Record<string, unknown> | null
|
|
92
|
+
}): Agent {
|
|
93
|
+
const body = input.body as Record<string, unknown>
|
|
94
|
+
const rawRecord = input.rawRecord || null
|
|
95
|
+
const orchestratorConfig = normalizeOrchestratorConfig({
|
|
96
|
+
provider: body.provider as string,
|
|
97
|
+
orchestratorEnabled: body.orchestratorEnabled,
|
|
98
|
+
orchestratorMission: body.orchestratorMission,
|
|
99
|
+
orchestratorWakeInterval: body.orchestratorWakeInterval,
|
|
100
|
+
orchestratorGovernance: body.orchestratorGovernance,
|
|
101
|
+
orchestratorMaxCyclesPerDay: body.orchestratorMaxCyclesPerDay,
|
|
102
|
+
})
|
|
103
|
+
const capabilitySelection = resolveAgentToolSelection({
|
|
104
|
+
hasExplicitTools: Boolean(rawRecord && Object.prototype.hasOwnProperty.call(rawRecord, 'tools')),
|
|
105
|
+
hasExplicitExtensions: Boolean(rawRecord && Object.prototype.hasOwnProperty.call(rawRecord, 'extensions')),
|
|
106
|
+
tools: Array.isArray(body.tools) ? normalizeStringList(body.tools) : undefined,
|
|
107
|
+
extensions: Array.isArray(body.extensions) ? normalizeStringList(body.extensions) : undefined,
|
|
108
|
+
})
|
|
109
|
+
const id = genId()
|
|
110
|
+
const now = Date.now()
|
|
111
|
+
const agent: Agent = {
|
|
112
|
+
id,
|
|
113
|
+
name: String(body.name || ''),
|
|
114
|
+
description: String(body.description || ''),
|
|
115
|
+
soul: typeof body.soul === 'string' && body.soul ? body.soul : undefined,
|
|
116
|
+
systemPrompt: String(body.systemPrompt || ''),
|
|
117
|
+
provider: String(body.provider || ''),
|
|
118
|
+
model: String(body.model || ''),
|
|
119
|
+
ollamaMode: body.provider === 'ollama' ? (normalizeOllamaMode(body.ollamaMode) || 'local') : null,
|
|
120
|
+
credentialId: (body.credentialId as string | null | undefined) || null,
|
|
121
|
+
fallbackCredentialIds: normalizeStringList(body.fallbackCredentialIds),
|
|
122
|
+
apiEndpoint: normalizeProviderEndpoint(String(body.provider || ''), (body.apiEndpoint as string | null | undefined) || null),
|
|
123
|
+
gatewayProfileId: (body.gatewayProfileId as string | null | undefined) || null,
|
|
124
|
+
preferredGatewayTags: normalizeStringList(body.preferredGatewayTags),
|
|
125
|
+
preferredGatewayUseCase: typeof body.preferredGatewayUseCase === 'string' && body.preferredGatewayUseCase.trim()
|
|
126
|
+
? body.preferredGatewayUseCase.trim()
|
|
127
|
+
: null,
|
|
128
|
+
routingStrategy: body.routingStrategy as Agent['routingStrategy'],
|
|
129
|
+
routingTargets: Array.isArray(body.routingTargets)
|
|
130
|
+
? body.routingTargets.map((target) => {
|
|
131
|
+
const row = target as Record<string, unknown>
|
|
132
|
+
const provider = typeof row.provider === 'string' ? row.provider : String(body.provider || '')
|
|
133
|
+
return {
|
|
134
|
+
...row,
|
|
135
|
+
provider,
|
|
136
|
+
ollamaMode: provider === 'ollama' ? (normalizeOllamaMode(row.ollamaMode) || 'local') : null,
|
|
137
|
+
apiEndpoint: normalizeProviderEndpoint(provider, (row.apiEndpoint as string | null | undefined) || null),
|
|
138
|
+
}
|
|
139
|
+
}) as Agent['routingTargets']
|
|
140
|
+
: undefined,
|
|
141
|
+
delegationEnabled: body.delegationEnabled === true,
|
|
142
|
+
delegationTargetMode: body.delegationTargetMode === 'selected' ? 'selected' : 'all',
|
|
143
|
+
delegationTargetAgentIds: (body.delegationTargetMode === 'selected' ? normalizeStringList(body.delegationTargetAgentIds) : []),
|
|
144
|
+
tools: capabilitySelection.tools,
|
|
145
|
+
extensions: capabilitySelection.extensions,
|
|
146
|
+
skills: Array.isArray(body.skills) ? body.skills as Agent['skills'] : undefined,
|
|
147
|
+
skillIds: normalizeStringList(body.skillIds),
|
|
148
|
+
mcpServerIds: normalizeStringList(body.mcpServerIds),
|
|
149
|
+
mcpDisabledTools: normalizeStringList(body.mcpDisabledTools).length ? normalizeStringList(body.mcpDisabledTools) : undefined,
|
|
150
|
+
capabilities: Array.isArray(body.capabilities) ? body.capabilities as string[] : undefined,
|
|
151
|
+
thinkingLevel: (body.thinkingLevel as Agent['thinkingLevel']) || undefined,
|
|
152
|
+
autoRecovery: body.autoRecovery === true,
|
|
153
|
+
disabled: body.disabled === true,
|
|
154
|
+
heartbeatEnabled: body.heartbeatEnabled !== false,
|
|
155
|
+
heartbeatInterval: body.heartbeatInterval as Agent['heartbeatInterval'],
|
|
156
|
+
heartbeatIntervalSec: typeof body.heartbeatIntervalSec === 'number' ? body.heartbeatIntervalSec : null,
|
|
157
|
+
heartbeatModel: typeof body.heartbeatModel === 'string' ? body.heartbeatModel : undefined,
|
|
158
|
+
heartbeatPrompt: typeof body.heartbeatPrompt === 'string' ? body.heartbeatPrompt : undefined,
|
|
159
|
+
orchestratorEnabled: orchestratorConfig.orchestratorEnabled,
|
|
160
|
+
orchestratorMission: orchestratorConfig.orchestratorMission,
|
|
161
|
+
orchestratorWakeInterval: orchestratorConfig.orchestratorWakeInterval,
|
|
162
|
+
orchestratorGovernance: orchestratorConfig.orchestratorGovernance,
|
|
163
|
+
orchestratorMaxCyclesPerDay: orchestratorConfig.orchestratorMaxCyclesPerDay,
|
|
164
|
+
elevenLabsVoiceId: typeof body.elevenLabsVoiceId === 'string' ? body.elevenLabsVoiceId : undefined,
|
|
165
|
+
monthlyBudget: typeof body.monthlyBudget === 'number' ? body.monthlyBudget : null,
|
|
166
|
+
dailyBudget: typeof body.dailyBudget === 'number' ? body.dailyBudget : null,
|
|
167
|
+
hourlyBudget: typeof body.hourlyBudget === 'number' ? body.hourlyBudget : null,
|
|
168
|
+
budgetAction: (body.budgetAction as Agent['budgetAction']) || 'warn',
|
|
169
|
+
identityState: (body.identityState as Agent['identityState']) ?? null,
|
|
170
|
+
memoryScopeMode: (body.memoryScopeMode as Agent['memoryScopeMode']) || undefined,
|
|
171
|
+
memoryTierPreference: (body.memoryTierPreference as Agent['memoryTierPreference']) || undefined,
|
|
172
|
+
proactiveMemory: body.proactiveMemory !== false,
|
|
173
|
+
autoDraftSkillSuggestions: body.autoDraftSkillSuggestions as Agent['autoDraftSkillSuggestions'],
|
|
174
|
+
projectId: typeof body.projectId === 'string' && body.projectId.trim() ? body.projectId.trim() : undefined,
|
|
175
|
+
avatarSeed: typeof body.avatarSeed === 'string' ? body.avatarSeed : undefined,
|
|
176
|
+
avatarUrl: typeof body.avatarUrl === 'string' ? body.avatarUrl : undefined,
|
|
177
|
+
sessionResetMode: (body.sessionResetMode as Agent['sessionResetMode']) ?? null,
|
|
178
|
+
sessionIdleTimeoutSec: typeof body.sessionIdleTimeoutSec === 'number' ? body.sessionIdleTimeoutSec : null,
|
|
179
|
+
sessionMaxAgeSec: typeof body.sessionMaxAgeSec === 'number' ? body.sessionMaxAgeSec : null,
|
|
180
|
+
sessionDailyResetAt: typeof body.sessionDailyResetAt === 'string' ? body.sessionDailyResetAt : null,
|
|
181
|
+
sessionResetTimezone: typeof body.sessionResetTimezone === 'string' ? body.sessionResetTimezone : null,
|
|
182
|
+
sandboxConfig: normalizeAgentSandboxConfig(body.sandboxConfig),
|
|
183
|
+
createdAt: now,
|
|
184
|
+
updatedAt: now,
|
|
185
|
+
}
|
|
186
|
+
saveAgent(id, agent)
|
|
187
|
+
logActivity({ entityType: 'agent', entityId: id, action: 'created', actor: 'user', summary: `Agent created: "${agent.name}"` })
|
|
188
|
+
notify('agents')
|
|
189
|
+
return agent
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function updateAgent(agentId: string, body: Record<string, unknown>): Agent | null {
|
|
193
|
+
const updated = patchAgent(agentId, (current) => {
|
|
194
|
+
if (!current) return null
|
|
195
|
+
const agent = { ...current, ...body, updatedAt: Date.now() }
|
|
196
|
+
if (body.tools !== undefined || body.extensions !== undefined) {
|
|
197
|
+
const nextSelection = normalizeCapabilitySelection({
|
|
198
|
+
tools: Array.isArray(body.tools) ? body.tools : agent.tools,
|
|
199
|
+
extensions: Array.isArray(body.extensions) ? body.extensions : agent.extensions,
|
|
200
|
+
})
|
|
201
|
+
agent.tools = nextSelection.tools
|
|
202
|
+
agent.extensions = nextSelection.extensions
|
|
203
|
+
}
|
|
204
|
+
if (body.delegationEnabled !== undefined) {
|
|
205
|
+
agent.delegationEnabled = body.delegationEnabled === true
|
|
206
|
+
}
|
|
207
|
+
if (body.delegationTargetMode === 'all' || body.delegationTargetMode === 'selected') {
|
|
208
|
+
agent.delegationTargetMode = body.delegationTargetMode
|
|
209
|
+
}
|
|
210
|
+
if (body.delegationTargetAgentIds !== undefined) {
|
|
211
|
+
agent.delegationTargetAgentIds = normalizeStringList(body.delegationTargetAgentIds)
|
|
212
|
+
}
|
|
213
|
+
if (agent.delegationTargetMode !== 'selected') {
|
|
214
|
+
agent.delegationTargetAgentIds = []
|
|
215
|
+
}
|
|
216
|
+
if (body.apiEndpoint !== undefined) {
|
|
217
|
+
agent.apiEndpoint = normalizeProviderEndpoint(
|
|
218
|
+
(body.provider as string) || agent.provider,
|
|
219
|
+
body.apiEndpoint as string | null | undefined,
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
if (body.provider !== undefined && body.provider !== 'ollama' && body.ollamaMode === undefined) {
|
|
223
|
+
agent.ollamaMode = null
|
|
224
|
+
}
|
|
225
|
+
if (body.sandboxConfig !== undefined) {
|
|
226
|
+
agent.sandboxConfig = normalizeAgentSandboxConfig(body.sandboxConfig)
|
|
227
|
+
}
|
|
228
|
+
if (
|
|
229
|
+
body.provider !== undefined
|
|
230
|
+
|| body.orchestratorEnabled !== undefined
|
|
231
|
+
|| body.orchestratorMission !== undefined
|
|
232
|
+
|| body.orchestratorWakeInterval !== undefined
|
|
233
|
+
|| body.orchestratorGovernance !== undefined
|
|
234
|
+
|| body.orchestratorMaxCyclesPerDay !== undefined
|
|
235
|
+
) {
|
|
236
|
+
const orchestratorConfig = normalizeOrchestratorConfig({
|
|
237
|
+
provider: typeof body.provider === 'string' ? body.provider : agent.provider,
|
|
238
|
+
orchestratorEnabled: body.orchestratorEnabled ?? agent.orchestratorEnabled,
|
|
239
|
+
orchestratorMission: body.orchestratorMission ?? agent.orchestratorMission,
|
|
240
|
+
orchestratorWakeInterval: body.orchestratorWakeInterval ?? agent.orchestratorWakeInterval,
|
|
241
|
+
orchestratorGovernance: body.orchestratorGovernance ?? agent.orchestratorGovernance,
|
|
242
|
+
orchestratorMaxCyclesPerDay: body.orchestratorMaxCyclesPerDay ?? agent.orchestratorMaxCyclesPerDay,
|
|
243
|
+
})
|
|
244
|
+
agent.orchestratorEnabled = orchestratorConfig.orchestratorEnabled
|
|
245
|
+
agent.orchestratorMission = orchestratorConfig.orchestratorMission
|
|
246
|
+
agent.orchestratorWakeInterval = orchestratorConfig.orchestratorWakeInterval
|
|
247
|
+
agent.orchestratorGovernance = orchestratorConfig.orchestratorGovernance
|
|
248
|
+
agent.orchestratorMaxCyclesPerDay = orchestratorConfig.orchestratorMaxCyclesPerDay
|
|
249
|
+
}
|
|
250
|
+
if (body.preferredGatewayTags !== undefined) {
|
|
251
|
+
agent.preferredGatewayTags = normalizeStringList(body.preferredGatewayTags)
|
|
252
|
+
}
|
|
253
|
+
if (body.preferredGatewayUseCase !== undefined) {
|
|
254
|
+
agent.preferredGatewayUseCase = typeof body.preferredGatewayUseCase === 'string' && body.preferredGatewayUseCase.trim()
|
|
255
|
+
? body.preferredGatewayUseCase.trim()
|
|
256
|
+
: null
|
|
257
|
+
}
|
|
258
|
+
if (body.routingTargets !== undefined && Array.isArray(body.routingTargets)) {
|
|
259
|
+
agent.routingTargets = body.routingTargets.map((target, index) => {
|
|
260
|
+
const row = target as Record<string, unknown>
|
|
261
|
+
const provider = typeof row.provider === 'string' && row.provider.trim() ? row.provider : agent.provider
|
|
262
|
+
return {
|
|
263
|
+
id: typeof row.id === 'string' && row.id.trim() ? row.id.trim() : `route-${index + 1}`,
|
|
264
|
+
label: typeof row.label === 'string' ? row.label : undefined,
|
|
265
|
+
role: row.role,
|
|
266
|
+
provider,
|
|
267
|
+
model: typeof row.model === 'string' ? row.model : '',
|
|
268
|
+
ollamaMode: provider === 'ollama'
|
|
269
|
+
? (row.ollamaMode === 'cloud' ? 'cloud' : 'local')
|
|
270
|
+
: null,
|
|
271
|
+
credentialId: row.credentialId ?? null,
|
|
272
|
+
fallbackCredentialIds: Array.isArray(row.fallbackCredentialIds) ? row.fallbackCredentialIds : [],
|
|
273
|
+
apiEndpoint: normalizeProviderEndpoint(
|
|
274
|
+
provider,
|
|
275
|
+
typeof row.apiEndpoint === 'string' ? row.apiEndpoint : null,
|
|
276
|
+
),
|
|
277
|
+
gatewayProfileId: row.gatewayProfileId ?? null,
|
|
278
|
+
preferredGatewayTags: normalizeStringList(row.preferredGatewayTags),
|
|
279
|
+
preferredGatewayUseCase: typeof row.preferredGatewayUseCase === 'string' && row.preferredGatewayUseCase.trim()
|
|
280
|
+
? row.preferredGatewayUseCase.trim()
|
|
281
|
+
: null,
|
|
282
|
+
priority: typeof row.priority === 'number' ? row.priority : index + 1,
|
|
283
|
+
}
|
|
284
|
+
}) as Agent['routingTargets']
|
|
285
|
+
}
|
|
286
|
+
delete (agent as Record<string, unknown>).platformAssignScope
|
|
287
|
+
delete (agent as Record<string, unknown>).subAgentIds
|
|
288
|
+
delete (agent as Record<string, unknown>).id
|
|
289
|
+
agent.id = agentId
|
|
290
|
+
return agent as Agent
|
|
291
|
+
})
|
|
292
|
+
if (!updated) return null
|
|
293
|
+
|
|
294
|
+
if (updated.threadSessionId) {
|
|
295
|
+
ensureAgentThreadSession(agentId)
|
|
296
|
+
updateThreadShortcutSession(agentId, updated)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
logActivity({ entityType: 'agent', entityId: agentId, action: 'updated', actor: 'user', summary: `Agent updated: "${updated.name}"` })
|
|
300
|
+
return updated
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function trashAgent(agentId: string): { ok: false } | { ok: true; detachedSessions: number; cascade: ReturnType<typeof suspendAgentReferences> } {
|
|
304
|
+
const trashed = patchAgent(agentId, (current) => {
|
|
305
|
+
if (!current) return null
|
|
306
|
+
return { ...current, trashedAt: Date.now() }
|
|
307
|
+
})
|
|
308
|
+
if (!trashed) return { ok: false }
|
|
309
|
+
|
|
310
|
+
logActivity({ entityType: 'agent', entityId: agentId, action: 'deleted', actor: 'user', summary: `Agent trashed: "${trashed.name}"` })
|
|
311
|
+
const detachedSessions = detachAgentSessions(agentId)
|
|
312
|
+
const cascade = suspendAgentReferences(agentId)
|
|
313
|
+
return { ok: true, detachedSessions, cascade }
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function restoreTrashedAgent(agentId: string): Agent | null {
|
|
317
|
+
const agent = patchAgent(agentId, (current) => {
|
|
318
|
+
if (!current || !current.trashedAt) return null
|
|
319
|
+
const next = { ...current }
|
|
320
|
+
delete next.trashedAt
|
|
321
|
+
next.updatedAt = Date.now()
|
|
322
|
+
return next
|
|
323
|
+
})
|
|
324
|
+
if (!agent) return null
|
|
325
|
+
notify('agents')
|
|
326
|
+
const restoredSchedules = restoreAgentSchedules(agentId)
|
|
327
|
+
if (restoredSchedules) notify('schedules')
|
|
328
|
+
return agent
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function permanentlyDeleteTrashedAgent(agentId: string): { ok: false; reason: 'not_found' | 'not_trashed' } | { ok: true; purged: ReturnType<typeof purgeAgentReferences> } {
|
|
332
|
+
const agent = loadAgents({ includeTrashed: true })[agentId]
|
|
333
|
+
if (!agent) return { ok: false, reason: 'not_found' }
|
|
334
|
+
if (!agent.trashedAt) return { ok: false, reason: 'not_trashed' }
|
|
335
|
+
|
|
336
|
+
const purged = purgeAgentReferences(agentId)
|
|
337
|
+
deleteAgent(agentId)
|
|
338
|
+
notify('agents')
|
|
339
|
+
return { ok: true, purged }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function cloneAgent(agentId: string): Agent | null {
|
|
343
|
+
const source = loadAgents({ includeTrashed: true })[agentId]
|
|
344
|
+
if (!source) return null
|
|
345
|
+
const newId = crypto.randomUUID()
|
|
346
|
+
const now = Date.now()
|
|
347
|
+
const cloned = JSON.parse(JSON.stringify(source)) as Agent
|
|
348
|
+
cloned.id = newId
|
|
349
|
+
cloned.name = `${source.name} (Copy)`
|
|
350
|
+
cloned.createdAt = now
|
|
351
|
+
cloned.updatedAt = now
|
|
352
|
+
cloned.totalCost = 0
|
|
353
|
+
cloned.lastUsedAt = undefined
|
|
354
|
+
cloned.threadSessionId = null
|
|
355
|
+
cloned.pinned = false
|
|
356
|
+
cloned.trashedAt = undefined
|
|
357
|
+
|
|
358
|
+
saveAgent(newId, cloned)
|
|
359
|
+
logActivity({
|
|
360
|
+
entityType: 'agent',
|
|
361
|
+
entityId: newId,
|
|
362
|
+
action: 'created',
|
|
363
|
+
actor: 'user',
|
|
364
|
+
summary: `Agent cloned from "${source.name}": "${cloned.name}"`,
|
|
365
|
+
})
|
|
366
|
+
notify('agents')
|
|
367
|
+
return cloned
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function bulkPatchAgents(patches: unknown): { updated: number; errors: string[] } {
|
|
371
|
+
if (!Array.isArray(patches) || patches.length === 0) {
|
|
372
|
+
return { updated: 0, errors: ['patches must be a non-empty array'] }
|
|
373
|
+
}
|
|
374
|
+
let updated = 0
|
|
375
|
+
const errors: string[] = []
|
|
376
|
+
for (const entry of patches) {
|
|
377
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
378
|
+
errors.push('Invalid patch entry (not an object)')
|
|
379
|
+
continue
|
|
380
|
+
}
|
|
381
|
+
const { id, patch } = entry as { id?: unknown; patch?: unknown }
|
|
382
|
+
if (typeof id !== 'string' || !id.trim()) {
|
|
383
|
+
errors.push('Patch entry missing valid id')
|
|
384
|
+
continue
|
|
385
|
+
}
|
|
386
|
+
if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
|
|
387
|
+
errors.push(`Patch for ${id} is not a valid object`)
|
|
388
|
+
continue
|
|
389
|
+
}
|
|
390
|
+
const result = patchAgent(id, (current) => current ? { ...current, ...(patch as Record<string, unknown>), updatedAt: Date.now() } : null)
|
|
391
|
+
if (!result) {
|
|
392
|
+
errors.push(`Agent ${id} not found`)
|
|
393
|
+
continue
|
|
394
|
+
}
|
|
395
|
+
updated += 1
|
|
396
|
+
logActivity({
|
|
397
|
+
entityType: 'agent',
|
|
398
|
+
entityId: id,
|
|
399
|
+
action: 'updated',
|
|
400
|
+
actor: 'user',
|
|
401
|
+
summary: `Bulk patch: updated agent "${result.name || id}"`,
|
|
402
|
+
})
|
|
403
|
+
}
|
|
404
|
+
if (updated > 0) notify('agents')
|
|
405
|
+
return { updated, errors }
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function getAgentThreadSession(agentId: string, user = 'default'): ServiceResult<Session> {
|
|
409
|
+
const agent = loadAgents()[agentId]
|
|
410
|
+
if (!agent) {
|
|
411
|
+
return serviceFail(404, 'Agent not found')
|
|
412
|
+
}
|
|
413
|
+
const session = ensureAgentThreadSession(agentId, user, agent)
|
|
414
|
+
if (!session) {
|
|
415
|
+
if (isAgentDisabled(agent)) {
|
|
416
|
+
return serviceFail(409, buildAgentDisabledMessage(agent, 'start new chats'))
|
|
417
|
+
}
|
|
418
|
+
return serviceFail(404, 'Agent not found')
|
|
419
|
+
}
|
|
420
|
+
return serviceOk(session)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export function getAgentStatus(agentId: string): Agent | null {
|
|
424
|
+
return loadAgents()[agentId] || null
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function listTrashedAgentsForApi(): Record<string, Agent> {
|
|
428
|
+
return loadTrashedAgents()
|
|
429
|
+
}
|