@swarmclawai/swarmclaw 0.7.2 → 0.7.4
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 +116 -50
- 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 +43 -0
- package/src/app/api/agents/[id]/thread/route.ts +39 -8
- package/src/app/api/agents/route.ts +35 -2
- package/src/app/api/auth/route.ts +77 -8
- package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
- 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]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +30 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +23 -1
- 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/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/files/open/route.ts +16 -14
- 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/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +12 -4
- 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 +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- 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 +55 -17
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +16 -6
- package/src/app/api/tasks/bulk/route.ts +3 -3
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +135 -17
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +38 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +21 -12
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +456 -23
- package/src/components/agents/inspector-panel.tsx +110 -49
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +70 -27
- package/src/components/chat/chat-card.tsx +6 -21
- package/src/components/chat/chat-header.tsx +263 -366
- package/src/components/chat/chat-list.tsx +62 -26
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +145 -19
- 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 +422 -209
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +385 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- 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/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +189 -1
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- 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 +88 -6
- package/src/components/shared/settings/section-orchestrator.tsx +6 -3
- 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 +248 -47
- package/src/components/tasks/approvals-panel.tsx +211 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- 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/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -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-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- 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/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +264 -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 +44 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
- package/src/lib/server/chat-execution.ts +402 -125
- 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 +74 -2
- package/src/lib/server/chatroom-helpers.ts +144 -11
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- 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 +994 -130
- 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 +189 -10
- 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/daemon-state.ts +62 -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/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 -43
- 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 +31 -964
- 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 +6 -5
- package/src/lib/server/openclaw-gateway.ts +123 -36
- 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 +18 -8
- package/src/lib/server/orchestrator.ts +5 -4
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +215 -0
- package/src/lib/server/plugins.ts +832 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +4 -21
- 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/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 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +96 -34
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +40 -12
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +243 -24
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +87 -2
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +162 -12
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- 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-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +95 -25
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +58 -4
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +195 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +947 -108
- package/src/lib/server/storage.ts +255 -10
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +185 -25
- 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 -11
- package/src/lib/server/tool-aliases.ts +80 -12
- package/src/lib/server/tool-capability-policy.ts +7 -1
- 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/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +62 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +43 -7
- package/src/stores/use-chat-store.ts +31 -2
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +470 -44
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- package/src/lib/server/session-run-manager.test.ts +0 -26
|
@@ -15,6 +15,7 @@ const states: Map<string, ProviderHealthState> =
|
|
|
15
15
|
(globalThis as any)[gk] ?? ((globalThis as any)[gk] = new Map<string, ProviderHealthState>())
|
|
16
16
|
|
|
17
17
|
const cliCheckCache = new Map<string, { at: number; ok: boolean }>()
|
|
18
|
+
const delegateReadyCache = new Map<string, { at: number; ok: boolean }>()
|
|
18
19
|
const CLI_CHECK_TTL_MS = 30_000
|
|
19
20
|
|
|
20
21
|
function commandExists(binary: string): boolean {
|
|
@@ -70,6 +71,35 @@ function delegateBinary(delegateTool: DelegateTool): string {
|
|
|
70
71
|
return 'opencode'
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
function delegateToolReady(delegateTool: DelegateTool): boolean {
|
|
75
|
+
const now = Date.now()
|
|
76
|
+
const cached = delegateReadyCache.get(delegateTool)
|
|
77
|
+
if (cached && now - cached.at < CLI_CHECK_TTL_MS) return cached.ok
|
|
78
|
+
|
|
79
|
+
const binary = delegateBinary(delegateTool)
|
|
80
|
+
let ok = commandExists(binary)
|
|
81
|
+
if (ok && delegateTool === 'delegate_to_claude_code') {
|
|
82
|
+
const probe = spawnSync(binary, ['auth', 'status'], { encoding: 'utf-8', timeout: 8000 })
|
|
83
|
+
if ((probe.status ?? 1) !== 0) {
|
|
84
|
+
let loggedIn = false
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(probe.stdout || '{}') as { loggedIn?: boolean }
|
|
87
|
+
loggedIn = parsed.loggedIn === true
|
|
88
|
+
} catch {
|
|
89
|
+
loggedIn = false
|
|
90
|
+
}
|
|
91
|
+
ok = loggedIn
|
|
92
|
+
}
|
|
93
|
+
} else if (ok && delegateTool === 'delegate_to_codex_cli') {
|
|
94
|
+
const probe = spawnSync(binary, ['login', 'status'], { encoding: 'utf-8', timeout: 8000 })
|
|
95
|
+
const probeText = `${probe.stdout || ''}\n${probe.stderr || ''}`.toLowerCase()
|
|
96
|
+
ok = (probe.status ?? 1) === 0 && probeText.includes('logged in')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
delegateReadyCache.set(delegateTool, { at: now, ok })
|
|
100
|
+
return ok
|
|
101
|
+
}
|
|
102
|
+
|
|
73
103
|
function delegateProviderId(delegateTool: DelegateTool): string {
|
|
74
104
|
if (delegateTool === 'delegate_to_claude_code') return 'claude-cli'
|
|
75
105
|
if (delegateTool === 'delegate_to_codex_cli') return 'codex-cli'
|
|
@@ -85,9 +115,9 @@ export function rankDelegatesByHealth(order: DelegateTool[]): DelegateTool[] {
|
|
|
85
115
|
return true
|
|
86
116
|
})
|
|
87
117
|
return deduped.sort((a, b) => {
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
118
|
+
const aReady = delegateToolReady(a)
|
|
119
|
+
const bReady = delegateToolReady(b)
|
|
120
|
+
if (aReady !== bReady) return aReady ? -1 : 1
|
|
91
121
|
|
|
92
122
|
const aCool = isProviderCoolingDown(delegateProviderId(a))
|
|
93
123
|
const bCool = isProviderCoolingDown(delegateProviderId(b))
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { getProviderList } from '@/lib/providers'
|
|
3
|
+
import { OPENAI_COMPATIBLE_DEFAULTS } from '@/lib/server/provider-health'
|
|
4
|
+
import { decryptKey, loadCredentials } from '@/lib/server/storage'
|
|
5
|
+
import type { ProviderInfo, ProviderModelDiscoveryResult } from '@/types'
|
|
6
|
+
|
|
7
|
+
type DiscoveryStrategy = 'openai-compatible' | 'anthropic' | 'google' | 'ollama' | 'openclaw'
|
|
8
|
+
|
|
9
|
+
interface DiscoveryDescriptor {
|
|
10
|
+
providerId: string
|
|
11
|
+
providerName: string
|
|
12
|
+
strategy: DiscoveryStrategy
|
|
13
|
+
endpoint?: string
|
|
14
|
+
requiresApiKey: boolean
|
|
15
|
+
optionalApiKey: boolean
|
|
16
|
+
supportsDiscovery: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface DiscoverProviderModelsInput {
|
|
20
|
+
providerId: string
|
|
21
|
+
credentialId?: string | null
|
|
22
|
+
endpoint?: string | null
|
|
23
|
+
force?: boolean
|
|
24
|
+
requiresApiKey?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface DiscoveryCacheEntry {
|
|
28
|
+
expiresAt: number
|
|
29
|
+
value: ProviderModelDiscoveryResult
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const CLOUD_CACHE_TTL_MS = 15 * 60_000
|
|
33
|
+
const LOCAL_CACHE_TTL_MS = 60_000
|
|
34
|
+
const ERROR_CACHE_TTL_MS = 30_000
|
|
35
|
+
const DISCOVERY_TIMEOUT_MS = 10_000
|
|
36
|
+
const gk = '__swarmclaw_provider_model_discovery__' as const
|
|
37
|
+
|
|
38
|
+
type DiscoveryGlobals = typeof globalThis & {
|
|
39
|
+
[gk]?: {
|
|
40
|
+
cache: Map<string, DiscoveryCacheEntry>
|
|
41
|
+
pending: Map<string, Promise<ProviderModelDiscoveryResult>>
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const discoveryGlobals = globalThis as DiscoveryGlobals
|
|
46
|
+
const discoveryState = discoveryGlobals[gk] ?? (discoveryGlobals[gk] = {
|
|
47
|
+
cache: new Map<string, DiscoveryCacheEntry>(),
|
|
48
|
+
pending: new Map<string, Promise<ProviderModelDiscoveryResult>>(),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
function clean(value: string | null | undefined): string {
|
|
52
|
+
return typeof value === 'string' ? value.trim() : ''
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeEndpoint(raw: string | null | undefined, fallback = ''): string {
|
|
56
|
+
return (clean(raw) || fallback).replace(/\/+$/, '')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function supportsBuiltInModelDiscovery(providerId: string): boolean {
|
|
60
|
+
return !['claude-cli', 'codex-cli', 'opencode-cli', 'fireworks'].includes(providerId)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeGoogleModelsEndpoint(raw: string | null | undefined): string {
|
|
64
|
+
const fallback = 'https://generativelanguage.googleapis.com/v1beta'
|
|
65
|
+
const normalized = normalizeEndpoint(raw, fallback)
|
|
66
|
+
.replace(/\/openai$/i, '')
|
|
67
|
+
.replace(/\/models$/i, '')
|
|
68
|
+
return `${normalized}/models`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function resolveProviderInfo(providerId: string): ProviderInfo | null {
|
|
72
|
+
return getProviderList().find((provider) => provider.id === providerId) || null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resolveDescriptor(input: DiscoverProviderModelsInput): DiscoveryDescriptor | null {
|
|
76
|
+
const providerId = clean(input.providerId)
|
|
77
|
+
const provider = resolveProviderInfo(providerId)
|
|
78
|
+
const requiresApiKeyOverride = typeof input.requiresApiKey === 'boolean' ? input.requiresApiKey : undefined
|
|
79
|
+
|
|
80
|
+
if (providerId === 'custom') {
|
|
81
|
+
const endpoint = normalizeEndpoint(input.endpoint)
|
|
82
|
+
if (!endpoint) return null
|
|
83
|
+
return {
|
|
84
|
+
providerId,
|
|
85
|
+
providerName: 'Custom Provider',
|
|
86
|
+
strategy: 'openai-compatible',
|
|
87
|
+
endpoint,
|
|
88
|
+
requiresApiKey: requiresApiKeyOverride ?? true,
|
|
89
|
+
optionalApiKey: false,
|
|
90
|
+
supportsDiscovery: true,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (providerId === 'openclaw') {
|
|
95
|
+
return {
|
|
96
|
+
providerId,
|
|
97
|
+
providerName: 'OpenClaw',
|
|
98
|
+
strategy: 'openclaw',
|
|
99
|
+
endpoint: normalizeEndpoint(input.endpoint, 'http://localhost:18789'),
|
|
100
|
+
requiresApiKey: requiresApiKeyOverride ?? false,
|
|
101
|
+
optionalApiKey: true,
|
|
102
|
+
supportsDiscovery: true,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!provider) return null
|
|
107
|
+
const supportsDiscovery = provider.supportsModelDiscovery ?? supportsBuiltInModelDiscovery(providerId)
|
|
108
|
+
if (!supportsDiscovery) {
|
|
109
|
+
return {
|
|
110
|
+
providerId,
|
|
111
|
+
providerName: provider.name,
|
|
112
|
+
strategy: 'openai-compatible',
|
|
113
|
+
endpoint: undefined,
|
|
114
|
+
requiresApiKey: provider.requiresApiKey,
|
|
115
|
+
optionalApiKey: Boolean(provider.optionalApiKey),
|
|
116
|
+
supportsDiscovery: false,
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (providerId === 'anthropic') {
|
|
121
|
+
return {
|
|
122
|
+
providerId,
|
|
123
|
+
providerName: provider.name,
|
|
124
|
+
strategy: 'anthropic',
|
|
125
|
+
requiresApiKey: requiresApiKeyOverride ?? provider.requiresApiKey,
|
|
126
|
+
optionalApiKey: Boolean(provider.optionalApiKey),
|
|
127
|
+
supportsDiscovery,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (providerId === 'google') {
|
|
132
|
+
return {
|
|
133
|
+
providerId,
|
|
134
|
+
providerName: provider.name,
|
|
135
|
+
strategy: 'google',
|
|
136
|
+
endpoint: normalizeGoogleModelsEndpoint(input.endpoint || provider.defaultEndpoint || ''),
|
|
137
|
+
requiresApiKey: requiresApiKeyOverride ?? provider.requiresApiKey,
|
|
138
|
+
optionalApiKey: Boolean(provider.optionalApiKey),
|
|
139
|
+
supportsDiscovery,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (providerId === 'ollama') {
|
|
144
|
+
return {
|
|
145
|
+
providerId,
|
|
146
|
+
providerName: provider.name,
|
|
147
|
+
strategy: 'ollama',
|
|
148
|
+
endpoint: normalizeEndpoint(input.endpoint, provider.defaultEndpoint || 'http://localhost:11434'),
|
|
149
|
+
requiresApiKey: requiresApiKeyOverride ?? provider.requiresApiKey,
|
|
150
|
+
optionalApiKey: Boolean(provider.optionalApiKey),
|
|
151
|
+
supportsDiscovery,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const openAiDefault = OPENAI_COMPATIBLE_DEFAULTS[providerId as keyof typeof OPENAI_COMPATIBLE_DEFAULTS]?.defaultEndpoint
|
|
156
|
+
const endpoint = normalizeEndpoint(input.endpoint, provider.defaultEndpoint || openAiDefault || '')
|
|
157
|
+
return {
|
|
158
|
+
providerId,
|
|
159
|
+
providerName: provider.name,
|
|
160
|
+
strategy: 'openai-compatible',
|
|
161
|
+
endpoint,
|
|
162
|
+
requiresApiKey: requiresApiKeyOverride ?? provider.requiresApiKey,
|
|
163
|
+
optionalApiKey: Boolean(provider.optionalApiKey),
|
|
164
|
+
supportsDiscovery,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function parseErrorMessage(text: string, fallback: string): string {
|
|
169
|
+
const body = text.trim()
|
|
170
|
+
if (!body) return fallback
|
|
171
|
+
try {
|
|
172
|
+
const parsed = JSON.parse(body)
|
|
173
|
+
if (typeof parsed?.error?.message === 'string' && parsed.error.message.trim()) return parsed.error.message.trim()
|
|
174
|
+
if (typeof parsed?.error === 'string' && parsed.error.trim()) return parsed.error.trim()
|
|
175
|
+
if (typeof parsed?.message === 'string' && parsed.message.trim()) return parsed.message.trim()
|
|
176
|
+
if (typeof parsed?.detail === 'string' && parsed.detail.trim()) return parsed.detail.trim()
|
|
177
|
+
} catch {
|
|
178
|
+
// Ignore invalid JSON and fall back to the raw text.
|
|
179
|
+
}
|
|
180
|
+
return body.slice(0, 300) || fallback
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function resolveCredentialApiKey(credentialId: string | null | undefined): string | null {
|
|
184
|
+
const id = clean(credentialId)
|
|
185
|
+
if (!id) return null
|
|
186
|
+
try {
|
|
187
|
+
const credentials = loadCredentials()
|
|
188
|
+
const credential = credentials[id]
|
|
189
|
+
if (!credential?.encryptedKey) return null
|
|
190
|
+
return decryptKey(credential.encryptedKey)
|
|
191
|
+
} catch {
|
|
192
|
+
return null
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function hashApiKey(apiKey: string | null): string {
|
|
197
|
+
if (!apiKey) return 'anon'
|
|
198
|
+
return crypto.createHash('sha1').update(apiKey).digest('hex').slice(0, 12)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function dedupeModels(models: string[]): string[] {
|
|
202
|
+
const seen = new Set<string>()
|
|
203
|
+
const result: string[] = []
|
|
204
|
+
for (const model of models) {
|
|
205
|
+
const trimmed = model.trim()
|
|
206
|
+
if (!trimmed) continue
|
|
207
|
+
if (seen.has(trimmed)) continue
|
|
208
|
+
seen.add(trimmed)
|
|
209
|
+
result.push(trimmed)
|
|
210
|
+
}
|
|
211
|
+
return result
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function normalizeModelId(modelId: string, strategy: DiscoveryStrategy): string {
|
|
215
|
+
const trimmed = modelId.trim()
|
|
216
|
+
if (!trimmed) return ''
|
|
217
|
+
if (strategy === 'ollama') return trimmed.replace(/:latest$/i, '')
|
|
218
|
+
if (strategy === 'google' && trimmed.startsWith('models/')) return trimmed.slice('models/'.length)
|
|
219
|
+
return trimmed
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function looksLikeChatModel(providerId: string, modelId: string): boolean {
|
|
223
|
+
const normalized = modelId.toLowerCase()
|
|
224
|
+
if (!normalized) return false
|
|
225
|
+
|
|
226
|
+
const universalExclusions = [
|
|
227
|
+
'embedding',
|
|
228
|
+
'rerank',
|
|
229
|
+
'moderation',
|
|
230
|
+
'whisper',
|
|
231
|
+
'transcribe',
|
|
232
|
+
'transcription',
|
|
233
|
+
'tts',
|
|
234
|
+
'speech',
|
|
235
|
+
'text-to-speech',
|
|
236
|
+
'stable-diffusion',
|
|
237
|
+
'sdxl',
|
|
238
|
+
'flux',
|
|
239
|
+
'playground-v2',
|
|
240
|
+
'pix2pix',
|
|
241
|
+
'clip',
|
|
242
|
+
]
|
|
243
|
+
if (universalExclusions.some((token) => normalized.includes(token))) return false
|
|
244
|
+
|
|
245
|
+
if (providerId === 'openai') return /^(gpt-|o1($|-)|o3($|-)|o4($|-)|chatgpt-)/.test(normalized)
|
|
246
|
+
if (providerId === 'anthropic') return normalized.startsWith('claude-')
|
|
247
|
+
if (providerId === 'google') return normalized.startsWith('gemini-')
|
|
248
|
+
if (providerId === 'deepseek') return normalized.startsWith('deepseek-')
|
|
249
|
+
if (providerId === 'xai') return normalized.startsWith('grok-')
|
|
250
|
+
|
|
251
|
+
return true
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function extractCandidateModelIds(payload: unknown, strategy: DiscoveryStrategy): string[] {
|
|
255
|
+
const source = payload as {
|
|
256
|
+
data?: unknown[]
|
|
257
|
+
models?: unknown[]
|
|
258
|
+
}
|
|
259
|
+
const candidates: string[] = []
|
|
260
|
+
const append = (value: unknown) => {
|
|
261
|
+
if (typeof value === 'string' && value.trim()) candidates.push(value.trim())
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const readCollection = (items: unknown[] | undefined) => {
|
|
265
|
+
if (!Array.isArray(items)) return
|
|
266
|
+
for (const item of items) {
|
|
267
|
+
if (typeof item === 'string') {
|
|
268
|
+
append(item)
|
|
269
|
+
continue
|
|
270
|
+
}
|
|
271
|
+
if (!item || typeof item !== 'object') continue
|
|
272
|
+
const record = item as { id?: unknown; name?: unknown; model?: unknown; baseModelId?: unknown }
|
|
273
|
+
append(record.id)
|
|
274
|
+
append(record.name)
|
|
275
|
+
append(record.model)
|
|
276
|
+
append(record.baseModelId)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (Array.isArray(payload)) readCollection(payload)
|
|
281
|
+
readCollection(source.data)
|
|
282
|
+
readCollection(source.models)
|
|
283
|
+
|
|
284
|
+
const normalized = candidates
|
|
285
|
+
.map((candidate) => normalizeModelId(candidate, strategy))
|
|
286
|
+
.filter(Boolean)
|
|
287
|
+
return dedupeModels(normalized)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function extractDiscoveredModels(
|
|
291
|
+
providerId: string,
|
|
292
|
+
strategy: DiscoveryStrategy,
|
|
293
|
+
payload: unknown,
|
|
294
|
+
): { models: string[]; rawCount: number } {
|
|
295
|
+
const candidates = extractCandidateModelIds(payload, strategy)
|
|
296
|
+
const filtered = strategy === 'ollama'
|
|
297
|
+
? candidates
|
|
298
|
+
: candidates.filter((candidate) => looksLikeChatModel(providerId, candidate))
|
|
299
|
+
return {
|
|
300
|
+
models: dedupeModels(filtered),
|
|
301
|
+
rawCount: candidates.length,
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function ttlForDescriptor(descriptor: DiscoveryDescriptor, ok: boolean): number {
|
|
306
|
+
if (!ok) return ERROR_CACHE_TTL_MS
|
|
307
|
+
if (descriptor.strategy === 'ollama' || descriptor.strategy === 'openclaw') return LOCAL_CACHE_TTL_MS
|
|
308
|
+
return CLOUD_CACHE_TTL_MS
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function buildCacheKey(
|
|
312
|
+
descriptor: DiscoveryDescriptor,
|
|
313
|
+
credentialId: string | null | undefined,
|
|
314
|
+
apiKey: string | null,
|
|
315
|
+
): string {
|
|
316
|
+
return [
|
|
317
|
+
descriptor.providerId,
|
|
318
|
+
descriptor.strategy,
|
|
319
|
+
descriptor.endpoint || '',
|
|
320
|
+
clean(credentialId),
|
|
321
|
+
hashApiKey(apiKey),
|
|
322
|
+
].join('::')
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function fetchModelsFromProvider(
|
|
326
|
+
descriptor: DiscoveryDescriptor,
|
|
327
|
+
apiKey: string | null,
|
|
328
|
+
): Promise<{ ok: boolean; models: string[]; message: string }> {
|
|
329
|
+
const headers: Record<string, string> = {}
|
|
330
|
+
let url = descriptor.endpoint || ''
|
|
331
|
+
|
|
332
|
+
if (descriptor.strategy === 'anthropic') {
|
|
333
|
+
url = 'https://api.anthropic.com/v1/models'
|
|
334
|
+
if (apiKey) headers['x-api-key'] = apiKey
|
|
335
|
+
headers['anthropic-version'] = '2023-06-01'
|
|
336
|
+
} else if (descriptor.strategy === 'google') {
|
|
337
|
+
url = descriptor.endpoint || normalizeGoogleModelsEndpoint('')
|
|
338
|
+
if (apiKey) {
|
|
339
|
+
const searchParams = new URLSearchParams({ key: apiKey })
|
|
340
|
+
url = `${url}?${searchParams.toString()}`
|
|
341
|
+
}
|
|
342
|
+
} else if (descriptor.strategy === 'ollama') {
|
|
343
|
+
url = `${descriptor.endpoint}/api/tags`
|
|
344
|
+
} else {
|
|
345
|
+
url = `${descriptor.endpoint}/models`
|
|
346
|
+
if (apiKey) headers.authorization = `Bearer ${apiKey}`
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const res = await fetch(url, {
|
|
350
|
+
headers,
|
|
351
|
+
signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS),
|
|
352
|
+
cache: 'no-store',
|
|
353
|
+
})
|
|
354
|
+
if (!res.ok) {
|
|
355
|
+
const text = await res.text().catch(() => '')
|
|
356
|
+
return {
|
|
357
|
+
ok: false,
|
|
358
|
+
models: [],
|
|
359
|
+
message: parseErrorMessage(text, `${descriptor.providerName} returned ${res.status}.`),
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const payload = await res.json().catch(() => ({}))
|
|
364
|
+
const { models, rawCount } = extractDiscoveredModels(descriptor.providerId, descriptor.strategy, payload)
|
|
365
|
+
if (models.length === 0) {
|
|
366
|
+
return {
|
|
367
|
+
ok: true,
|
|
368
|
+
models: [],
|
|
369
|
+
message: rawCount > 0
|
|
370
|
+
? `${descriptor.providerName} returned ${rawCount} model(s), but none looked chat-capable.`
|
|
371
|
+
: `${descriptor.providerName} did not report any models.`,
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const message = rawCount > models.length
|
|
376
|
+
? `${descriptor.providerName} returned ${rawCount} model(s); showing ${models.length} likely chat models.`
|
|
377
|
+
: `${descriptor.providerName} returned ${models.length} live model(s).`
|
|
378
|
+
return { ok: true, models, message }
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function buildResult(
|
|
382
|
+
descriptor: DiscoveryDescriptor,
|
|
383
|
+
data: Partial<ProviderModelDiscoveryResult> & Pick<ProviderModelDiscoveryResult, 'ok' | 'models'>,
|
|
384
|
+
): ProviderModelDiscoveryResult {
|
|
385
|
+
return {
|
|
386
|
+
ok: data.ok,
|
|
387
|
+
providerId: descriptor.providerId,
|
|
388
|
+
providerName: descriptor.providerName,
|
|
389
|
+
models: data.models,
|
|
390
|
+
cached: Boolean(data.cached),
|
|
391
|
+
fetchedAt: data.fetchedAt ?? Date.now(),
|
|
392
|
+
cacheTtlMs: data.cacheTtlMs ?? ttlForDescriptor(descriptor, data.ok),
|
|
393
|
+
supportsDiscovery: descriptor.supportsDiscovery,
|
|
394
|
+
missingCredential: data.missingCredential,
|
|
395
|
+
message: data.message,
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export async function discoverProviderModels(
|
|
400
|
+
input: DiscoverProviderModelsInput,
|
|
401
|
+
): Promise<ProviderModelDiscoveryResult> {
|
|
402
|
+
const descriptor = resolveDescriptor(input)
|
|
403
|
+
if (!descriptor) {
|
|
404
|
+
return {
|
|
405
|
+
ok: false,
|
|
406
|
+
providerId: clean(input.providerId) || 'unknown',
|
|
407
|
+
providerName: undefined,
|
|
408
|
+
models: [],
|
|
409
|
+
cached: false,
|
|
410
|
+
fetchedAt: Date.now(),
|
|
411
|
+
cacheTtlMs: ERROR_CACHE_TTL_MS,
|
|
412
|
+
supportsDiscovery: false,
|
|
413
|
+
message: 'Live model discovery is not available for this provider configuration.',
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (!descriptor.supportsDiscovery) {
|
|
418
|
+
return buildResult(descriptor, {
|
|
419
|
+
ok: false,
|
|
420
|
+
models: [],
|
|
421
|
+
message: 'This provider does not expose a live model catalog here. You can still type a model name manually.',
|
|
422
|
+
})
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const apiKey = resolveCredentialApiKey(input.credentialId)
|
|
426
|
+
if (descriptor.requiresApiKey && !apiKey) {
|
|
427
|
+
return buildResult(descriptor, {
|
|
428
|
+
ok: false,
|
|
429
|
+
models: [],
|
|
430
|
+
missingCredential: true,
|
|
431
|
+
message: 'Add an API key to fetch the live model list. Manual model entry still works.',
|
|
432
|
+
})
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const cacheKey = buildCacheKey(descriptor, input.credentialId, apiKey)
|
|
436
|
+
const now = Date.now()
|
|
437
|
+
if (!input.force) {
|
|
438
|
+
const cached = discoveryState.cache.get(cacheKey)
|
|
439
|
+
if (cached && cached.expiresAt > now) {
|
|
440
|
+
return { ...cached.value, cached: true }
|
|
441
|
+
}
|
|
442
|
+
const pending = discoveryState.pending.get(cacheKey)
|
|
443
|
+
if (pending) return pending
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const promise = (async () => {
|
|
447
|
+
const fetchedAt = Date.now()
|
|
448
|
+
try {
|
|
449
|
+
const result = await fetchModelsFromProvider(descriptor, apiKey)
|
|
450
|
+
const built = buildResult(descriptor, {
|
|
451
|
+
ok: result.ok,
|
|
452
|
+
models: result.models,
|
|
453
|
+
message: result.message,
|
|
454
|
+
fetchedAt,
|
|
455
|
+
})
|
|
456
|
+
discoveryState.cache.set(cacheKey, {
|
|
457
|
+
expiresAt: fetchedAt + ttlForDescriptor(descriptor, result.ok),
|
|
458
|
+
value: built,
|
|
459
|
+
})
|
|
460
|
+
return built
|
|
461
|
+
} catch (error) {
|
|
462
|
+
const message = error instanceof Error ? error.message : 'Failed to fetch live models.'
|
|
463
|
+
const built = buildResult(descriptor, {
|
|
464
|
+
ok: false,
|
|
465
|
+
models: [],
|
|
466
|
+
message,
|
|
467
|
+
fetchedAt,
|
|
468
|
+
})
|
|
469
|
+
discoveryState.cache.set(cacheKey, {
|
|
470
|
+
expiresAt: fetchedAt + ERROR_CACHE_TTL_MS,
|
|
471
|
+
value: built,
|
|
472
|
+
})
|
|
473
|
+
return built
|
|
474
|
+
} finally {
|
|
475
|
+
discoveryState.pending.delete(cacheKey)
|
|
476
|
+
}
|
|
477
|
+
})()
|
|
478
|
+
|
|
479
|
+
discoveryState.pending.set(cacheKey, promise)
|
|
480
|
+
return promise
|
|
481
|
+
}
|
package/src/lib/server/queue.ts
CHANGED
|
@@ -11,7 +11,6 @@ import { pushMainLoopEventToMainSessions } from './main-agent-loop'
|
|
|
11
11
|
import { executeSessionChatTurn } from './chat-execution'
|
|
12
12
|
import { extractTaskResult, formatResultBody } from './task-result'
|
|
13
13
|
import { getCheckpointSaver } from './langgraph-checkpoint'
|
|
14
|
-
import { isMainLoopSession } from './main-session'
|
|
15
14
|
import { cascadeUnblock } from './dag-validation'
|
|
16
15
|
import { performGuardianRollback } from './guardian'
|
|
17
16
|
import type { Agent, BoardTask, Connector, Message } from '@/types'
|
|
@@ -282,10 +281,6 @@ function pushQueueUnique(queue: string[], id: string): void {
|
|
|
282
281
|
if (!queueContains(queue, id)) queue.push(id)
|
|
283
282
|
}
|
|
284
283
|
|
|
285
|
-
function isMainSession(session: SessionLike | null | undefined): boolean {
|
|
286
|
-
return isMainLoopSession(session)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
284
|
function resolveTaskOwnerUser(task: ScheduleTaskMeta, sessions: Record<string, SessionLike>): string | null {
|
|
290
285
|
const direct = typeof task.user === 'string' ? task.user.trim() : ''
|
|
291
286
|
if (direct) return direct
|
|
@@ -555,7 +550,7 @@ function notifyMainChatScheduleResult(task: BoardTask): void {
|
|
|
555
550
|
if (task.status !== 'completed' && task.status !== 'failed') return
|
|
556
551
|
|
|
557
552
|
const sessions = loadSessions()
|
|
558
|
-
|
|
553
|
+
void resolveTaskOwnerUser(scheduleTask, sessions as Record<string, SessionLike>)
|
|
559
554
|
const scheduleNameRaw = typeof scheduleTask.sourceScheduleName === 'string'
|
|
560
555
|
? scheduleTask.sourceScheduleName.trim()
|
|
561
556
|
: ''
|
|
@@ -594,18 +589,7 @@ function notifyMainChatScheduleResult(task: BoardTask): void {
|
|
|
594
589
|
return msg
|
|
595
590
|
}
|
|
596
591
|
|
|
597
|
-
|
|
598
|
-
if (!isMainSession(session)) continue
|
|
599
|
-
if (ownerUser && session?.user && session.user !== ownerUser) continue
|
|
600
|
-
const last = Array.isArray(session.messages) ? session.messages.at(-1) : null
|
|
601
|
-
if (last?.role === 'assistant' && last?.text === body && typeof last?.time === 'number' && now - last.time < 30_000) continue
|
|
602
|
-
if (!Array.isArray(session.messages)) session.messages = []
|
|
603
|
-
session.messages.push(buildMsg())
|
|
604
|
-
session.lastActiveAt = now
|
|
605
|
-
changed = true
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// Also push to the agent's persistent thread session
|
|
592
|
+
// Push to the agent's shortcut chat session.
|
|
609
593
|
try {
|
|
610
594
|
const agents = loadAgents()
|
|
611
595
|
const agent = agents[task.agentId]
|
|
@@ -830,13 +814,12 @@ function notifyAgentThreadTaskResult(task: BoardTask): void {
|
|
|
830
814
|
}
|
|
831
815
|
|
|
832
816
|
// Push to delegating agent's active user-facing chat sessions
|
|
833
|
-
// so the result is visible in the chat the user is looking at
|
|
817
|
+
// so the result is visible in the chat the user is looking at.
|
|
834
818
|
if (delegator) {
|
|
835
819
|
for (const session of Object.values(sessions)) {
|
|
836
820
|
if (!session || session.agentId !== delegatedBy) continue
|
|
837
|
-
// Skip
|
|
821
|
+
// Skip the agent shortcut session itself.
|
|
838
822
|
if (session.id === delegator.threadSessionId) continue
|
|
839
|
-
if (session.sessionType === 'orchestrated') continue
|
|
840
823
|
// Only push to recently-active sessions (within last 30 minutes)
|
|
841
824
|
const lastActive = typeof session.lastActiveAt === 'number' ? session.lastActiveAt : 0
|
|
842
825
|
if (now - lastActive > 30 * 60_000) continue
|