@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
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { MemoryEntry } from '@/types'
|
|
2
|
+
|
|
3
|
+
export type MemoryTier = 'working' | 'durable' | 'archive'
|
|
4
|
+
export type MemoryScopeBadge = 'global' | 'agent' | 'shared' | 'session' | 'project'
|
|
5
|
+
|
|
6
|
+
const WORKING_CATEGORIES = new Set(['execution', 'working', 'scratch', 'breadcrumb'])
|
|
7
|
+
const ARCHIVE_CATEGORIES = new Set(['session_archive'])
|
|
8
|
+
|
|
9
|
+
function hasProjectRoot(entry: Pick<MemoryEntry, 'metadata' | 'references' | 'filePaths'>): boolean {
|
|
10
|
+
const metadataRoot = typeof entry.metadata?.projectRoot === 'string' ? entry.metadata.projectRoot.trim() : ''
|
|
11
|
+
if (metadataRoot) return true
|
|
12
|
+
|
|
13
|
+
if (Array.isArray(entry.references)) {
|
|
14
|
+
for (const ref of entry.references) {
|
|
15
|
+
if (typeof ref.projectRoot === 'string' && ref.projectRoot.trim()) return true
|
|
16
|
+
if ((ref.type === 'project' || ref.type === 'folder' || ref.type === 'file') && typeof ref.path === 'string' && ref.path.trim()) {
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (Array.isArray(entry.filePaths)) {
|
|
23
|
+
for (const ref of entry.filePaths) {
|
|
24
|
+
if (typeof ref.projectRoot === 'string' && ref.projectRoot.trim()) return true
|
|
25
|
+
if (typeof ref.path === 'string' && ref.path.trim()) return true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getMemoryTierForCategory(category: unknown): MemoryTier {
|
|
33
|
+
const normalized = typeof category === 'string' ? category.trim().toLowerCase() : ''
|
|
34
|
+
if (ARCHIVE_CATEGORIES.has(normalized)) return 'archive'
|
|
35
|
+
if (WORKING_CATEGORIES.has(normalized)) return 'working'
|
|
36
|
+
return 'durable'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getMemoryTier(entry: Pick<MemoryEntry, 'category' | 'metadata'>): MemoryTier {
|
|
40
|
+
const metadataTier = typeof entry.metadata?.tier === 'string' ? entry.metadata.tier.trim().toLowerCase() : ''
|
|
41
|
+
if (metadataTier === 'working' || metadataTier === 'durable' || metadataTier === 'archive') {
|
|
42
|
+
return metadataTier
|
|
43
|
+
}
|
|
44
|
+
if (metadataTier === 'session_archive') return 'archive'
|
|
45
|
+
return getMemoryTierForCategory(entry.category)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function deriveMemoryScope(entry: Pick<MemoryEntry, 'agentId' | 'sessionId' | 'sharedWith' | 'metadata' | 'references' | 'filePaths'>): MemoryScopeBadge {
|
|
49
|
+
if (entry.sessionId) return 'session'
|
|
50
|
+
if (hasProjectRoot(entry)) return 'project'
|
|
51
|
+
if (entry.agentId && Array.isArray(entry.sharedWith) && entry.sharedWith.length > 0) return 'shared'
|
|
52
|
+
if (entry.agentId) return 'agent'
|
|
53
|
+
return 'global'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getMemoryScopeLabel(scope: MemoryScopeBadge): string {
|
|
57
|
+
if (scope === 'agent') return 'private'
|
|
58
|
+
return scope
|
|
59
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import { buildOpenClawMainSessionKey, normalizeOpenClawAgentId } from './openclaw-agent-id'
|
|
4
|
+
|
|
5
|
+
test('normalizeOpenClawAgentId mirrors gateway-style normalization', () => {
|
|
6
|
+
assert.equal(normalizeOpenClawAgentId('OpenClaw Ops'), 'openclaw-ops')
|
|
7
|
+
assert.equal(normalizeOpenClawAgentId(' Agent / Research '), 'agent-research')
|
|
8
|
+
assert.equal(normalizeOpenClawAgentId('main'), 'main')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('buildOpenClawMainSessionKey uses normalized OpenClaw agent ids', () => {
|
|
12
|
+
assert.equal(buildOpenClawMainSessionKey('OpenClaw Ops'), 'agent:openclaw-ops:main')
|
|
13
|
+
assert.equal(buildOpenClawMainSessionKey(' '), null)
|
|
14
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const VALID_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i
|
|
2
|
+
const INVALID_CHARS_RE = /[^a-z0-9_-]+/g
|
|
3
|
+
const LEADING_DASH_RE = /^-+/
|
|
4
|
+
const TRAILING_DASH_RE = /-+$/
|
|
5
|
+
|
|
6
|
+
export function normalizeOpenClawAgentId(value: string | undefined | null): string {
|
|
7
|
+
const trimmed = (value ?? '').trim()
|
|
8
|
+
if (!trimmed) {
|
|
9
|
+
return 'main'
|
|
10
|
+
}
|
|
11
|
+
if (VALID_ID_RE.test(trimmed)) {
|
|
12
|
+
return trimmed.toLowerCase()
|
|
13
|
+
}
|
|
14
|
+
return (
|
|
15
|
+
trimmed
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(INVALID_CHARS_RE, '-')
|
|
18
|
+
.replace(LEADING_DASH_RE, '')
|
|
19
|
+
.replace(TRAILING_DASH_RE, '')
|
|
20
|
+
.slice(0, 64)
|
|
21
|
+
|| 'main'
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function buildOpenClawMainSessionKey(agentNameOrId: string | undefined | null): string | null {
|
|
26
|
+
const trimmed = (agentNameOrId ?? '').trim()
|
|
27
|
+
if (!trimmed) {
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
return `agent:${normalizeOpenClawAgentId(trimmed)}:main`
|
|
31
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { api } from './api-client'
|
|
2
|
+
import type { ProviderModelDiscoveryResult } from '@/types'
|
|
3
|
+
|
|
4
|
+
export interface DiscoverProviderModelsParams {
|
|
5
|
+
providerId: string
|
|
6
|
+
credentialId?: string | null
|
|
7
|
+
endpoint?: string | null
|
|
8
|
+
force?: boolean
|
|
9
|
+
requiresApiKey?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function buildProviderModelDiscoveryPath(params: DiscoverProviderModelsParams): string {
|
|
13
|
+
const searchParams = new URLSearchParams()
|
|
14
|
+
if (params.credentialId) searchParams.set('credentialId', params.credentialId)
|
|
15
|
+
if (params.endpoint?.trim()) searchParams.set('endpoint', params.endpoint.trim())
|
|
16
|
+
if (params.force) searchParams.set('force', '1')
|
|
17
|
+
if (typeof params.requiresApiKey === 'boolean') {
|
|
18
|
+
searchParams.set('requiresApiKey', params.requiresApiKey ? '1' : '0')
|
|
19
|
+
}
|
|
20
|
+
const query = searchParams.toString()
|
|
21
|
+
const encodedProviderId = encodeURIComponent(params.providerId)
|
|
22
|
+
return `/providers/${encodedProviderId}/discover-models${query ? `?${query}` : ''}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function fetchProviderModelDiscovery(
|
|
26
|
+
params: DiscoverProviderModelsParams,
|
|
27
|
+
): Promise<ProviderModelDiscoveryResult> {
|
|
28
|
+
return api<ProviderModelDiscoveryResult>('GET', buildProviderModelDiscoveryPath(params))
|
|
29
|
+
}
|
|
@@ -256,11 +256,16 @@ export function getProviderList(): ProviderInfo[] {
|
|
|
256
256
|
const overrides = getModelOverrides()
|
|
257
257
|
const builtins = Object.values(PROVIDERS)
|
|
258
258
|
.filter(({ id }) => id !== 'openclaw')
|
|
259
|
-
.map((
|
|
260
|
-
...info
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
259
|
+
.map((provider) => {
|
|
260
|
+
const { handler, ...info } = provider
|
|
261
|
+
void handler
|
|
262
|
+
return {
|
|
263
|
+
...info,
|
|
264
|
+
models: overrides[info.id] || info.models,
|
|
265
|
+
defaultModels: info.models,
|
|
266
|
+
supportsModelDiscovery: !['claude-cli', 'codex-cli', 'opencode-cli', 'fireworks'].includes(info.id),
|
|
267
|
+
}
|
|
268
|
+
})
|
|
264
269
|
|
|
265
270
|
const customs: ProviderInfo[] = Object.values(getCustomProviders())
|
|
266
271
|
.filter((c) => c.isEnabled)
|
|
@@ -269,6 +274,7 @@ export function getProviderList(): ProviderInfo[] {
|
|
|
269
274
|
name: c.name,
|
|
270
275
|
models: c.models,
|
|
271
276
|
defaultModels: c.models,
|
|
277
|
+
supportsModelDiscovery: !!(c.baseUrl && c.baseUrl.trim()),
|
|
272
278
|
requiresApiKey: c.requiresApiKey,
|
|
273
279
|
requiresEndpoint: false as boolean,
|
|
274
280
|
defaultEndpoint: c.baseUrl,
|
|
@@ -283,6 +289,7 @@ export function getProviderList(): ProviderInfo[] {
|
|
|
283
289
|
name: String(p.name),
|
|
284
290
|
models: p.models as string[],
|
|
285
291
|
defaultModels: p.models as string[],
|
|
292
|
+
supportsModelDiscovery: Boolean(p.supportsModelDiscovery),
|
|
286
293
|
requiresApiKey: Boolean(p.requiresApiKey),
|
|
287
294
|
requiresEndpoint: Boolean(p.requiresEndpoint),
|
|
288
295
|
defaultEndpoint: p.defaultEndpoint as string | undefined,
|
package/src/lib/runtime-loop.ts
CHANGED
|
@@ -3,9 +3,28 @@ import type { LoopMode } from '@/types'
|
|
|
3
3
|
export const DEFAULT_LOOP_MODE: LoopMode = 'bounded'
|
|
4
4
|
|
|
5
5
|
// Loop limits
|
|
6
|
-
export const
|
|
7
|
-
export const
|
|
8
|
-
export const
|
|
6
|
+
export const AGENT_LOOP_RECURSION_LIMIT_MIN = 1
|
|
7
|
+
export const AGENT_LOOP_RECURSION_LIMIT_MAX = 200
|
|
8
|
+
export const ORCHESTRATOR_LOOP_RECURSION_LIMIT_MIN = 1
|
|
9
|
+
export const ORCHESTRATOR_LOOP_RECURSION_LIMIT_MAX = 300
|
|
10
|
+
export const LEGACY_ORCHESTRATOR_MAX_TURNS_MIN = 1
|
|
11
|
+
export const LEGACY_ORCHESTRATOR_MAX_TURNS_MAX = 300
|
|
12
|
+
export const ONGOING_LOOP_MAX_ITERATIONS_MIN = 10
|
|
13
|
+
export const ONGOING_LOOP_MAX_ITERATIONS_MAX = 5000
|
|
14
|
+
export const ONGOING_LOOP_MAX_RUNTIME_MINUTES_MIN = 0
|
|
15
|
+
export const ONGOING_LOOP_MAX_RUNTIME_MINUTES_MAX = 1440
|
|
16
|
+
export const DELEGATION_MAX_DEPTH_MIN = 1
|
|
17
|
+
export const DELEGATION_MAX_DEPTH_MAX = 12
|
|
18
|
+
export const SHELL_COMMAND_TIMEOUT_SEC_MIN = 1
|
|
19
|
+
export const SHELL_COMMAND_TIMEOUT_SEC_MAX = 600
|
|
20
|
+
export const CLAUDE_CODE_TIMEOUT_SEC_MIN = 5
|
|
21
|
+
export const CLAUDE_CODE_TIMEOUT_SEC_MAX = 7200
|
|
22
|
+
export const CLI_PROCESS_TIMEOUT_SEC_MIN = 10
|
|
23
|
+
export const CLI_PROCESS_TIMEOUT_SEC_MAX = 7200
|
|
24
|
+
|
|
25
|
+
export const DEFAULT_AGENT_LOOP_RECURSION_LIMIT = 60
|
|
26
|
+
export const DEFAULT_ORCHESTRATOR_LOOP_RECURSION_LIMIT = 80
|
|
27
|
+
export const DEFAULT_LEGACY_ORCHESTRATOR_MAX_TURNS = 16
|
|
9
28
|
export const DEFAULT_ONGOING_LOOP_MAX_ITERATIONS = 250
|
|
10
29
|
export const DEFAULT_ONGOING_LOOP_MAX_RUNTIME_MINUTES = 60
|
|
11
30
|
export const DEFAULT_DELEGATION_MAX_DEPTH = 3
|
|
@@ -14,3 +33,86 @@ export const DEFAULT_DELEGATION_MAX_DEPTH = 3
|
|
|
14
33
|
export const DEFAULT_SHELL_COMMAND_TIMEOUT_SEC = 30
|
|
15
34
|
export const DEFAULT_CLAUDE_CODE_TIMEOUT_SEC = 1800
|
|
16
35
|
export const DEFAULT_CLI_PROCESS_TIMEOUT_SEC = 1800
|
|
36
|
+
|
|
37
|
+
function parseIntSetting(value: unknown, fallback: number, min: number, max: number): number {
|
|
38
|
+
const parsed = typeof value === 'number'
|
|
39
|
+
? value
|
|
40
|
+
: typeof value === 'string'
|
|
41
|
+
? Number.parseInt(value, 10)
|
|
42
|
+
: Number.NaN
|
|
43
|
+
if (!Number.isFinite(parsed)) return fallback
|
|
44
|
+
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface NormalizedRuntimeSettingFields {
|
|
48
|
+
loopMode: LoopMode
|
|
49
|
+
agentLoopRecursionLimit: number
|
|
50
|
+
orchestratorLoopRecursionLimit: number
|
|
51
|
+
legacyOrchestratorMaxTurns: number
|
|
52
|
+
delegationMaxDepth: number
|
|
53
|
+
ongoingLoopMaxIterations: number
|
|
54
|
+
ongoingLoopMaxRuntimeMinutes: number
|
|
55
|
+
shellCommandTimeoutSec: number
|
|
56
|
+
claudeCodeTimeoutSec: number
|
|
57
|
+
cliProcessTimeoutSec: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function normalizeRuntimeSettingFields(settings: Record<string, unknown>): NormalizedRuntimeSettingFields {
|
|
61
|
+
return {
|
|
62
|
+
loopMode: settings.loopMode === 'ongoing' ? 'ongoing' : DEFAULT_LOOP_MODE,
|
|
63
|
+
agentLoopRecursionLimit: parseIntSetting(
|
|
64
|
+
settings.agentLoopRecursionLimit,
|
|
65
|
+
DEFAULT_AGENT_LOOP_RECURSION_LIMIT,
|
|
66
|
+
AGENT_LOOP_RECURSION_LIMIT_MIN,
|
|
67
|
+
AGENT_LOOP_RECURSION_LIMIT_MAX,
|
|
68
|
+
),
|
|
69
|
+
orchestratorLoopRecursionLimit: parseIntSetting(
|
|
70
|
+
settings.orchestratorLoopRecursionLimit,
|
|
71
|
+
DEFAULT_ORCHESTRATOR_LOOP_RECURSION_LIMIT,
|
|
72
|
+
ORCHESTRATOR_LOOP_RECURSION_LIMIT_MIN,
|
|
73
|
+
ORCHESTRATOR_LOOP_RECURSION_LIMIT_MAX,
|
|
74
|
+
),
|
|
75
|
+
legacyOrchestratorMaxTurns: parseIntSetting(
|
|
76
|
+
settings.legacyOrchestratorMaxTurns,
|
|
77
|
+
DEFAULT_LEGACY_ORCHESTRATOR_MAX_TURNS,
|
|
78
|
+
LEGACY_ORCHESTRATOR_MAX_TURNS_MIN,
|
|
79
|
+
LEGACY_ORCHESTRATOR_MAX_TURNS_MAX,
|
|
80
|
+
),
|
|
81
|
+
delegationMaxDepth: parseIntSetting(
|
|
82
|
+
settings.delegationMaxDepth,
|
|
83
|
+
DEFAULT_DELEGATION_MAX_DEPTH,
|
|
84
|
+
DELEGATION_MAX_DEPTH_MIN,
|
|
85
|
+
DELEGATION_MAX_DEPTH_MAX,
|
|
86
|
+
),
|
|
87
|
+
ongoingLoopMaxIterations: parseIntSetting(
|
|
88
|
+
settings.ongoingLoopMaxIterations,
|
|
89
|
+
DEFAULT_ONGOING_LOOP_MAX_ITERATIONS,
|
|
90
|
+
ONGOING_LOOP_MAX_ITERATIONS_MIN,
|
|
91
|
+
ONGOING_LOOP_MAX_ITERATIONS_MAX,
|
|
92
|
+
),
|
|
93
|
+
ongoingLoopMaxRuntimeMinutes: parseIntSetting(
|
|
94
|
+
settings.ongoingLoopMaxRuntimeMinutes,
|
|
95
|
+
DEFAULT_ONGOING_LOOP_MAX_RUNTIME_MINUTES,
|
|
96
|
+
ONGOING_LOOP_MAX_RUNTIME_MINUTES_MIN,
|
|
97
|
+
ONGOING_LOOP_MAX_RUNTIME_MINUTES_MAX,
|
|
98
|
+
),
|
|
99
|
+
shellCommandTimeoutSec: parseIntSetting(
|
|
100
|
+
settings.shellCommandTimeoutSec,
|
|
101
|
+
DEFAULT_SHELL_COMMAND_TIMEOUT_SEC,
|
|
102
|
+
SHELL_COMMAND_TIMEOUT_SEC_MIN,
|
|
103
|
+
SHELL_COMMAND_TIMEOUT_SEC_MAX,
|
|
104
|
+
),
|
|
105
|
+
claudeCodeTimeoutSec: parseIntSetting(
|
|
106
|
+
settings.claudeCodeTimeoutSec,
|
|
107
|
+
DEFAULT_CLAUDE_CODE_TIMEOUT_SEC,
|
|
108
|
+
CLAUDE_CODE_TIMEOUT_SEC_MIN,
|
|
109
|
+
CLAUDE_CODE_TIMEOUT_SEC_MAX,
|
|
110
|
+
),
|
|
111
|
+
cliProcessTimeoutSec: parseIntSetting(
|
|
112
|
+
settings.cliProcessTimeoutSec,
|
|
113
|
+
DEFAULT_CLI_PROCESS_TIMEOUT_SEC,
|
|
114
|
+
CLI_PROCESS_TIMEOUT_SEC_MIN,
|
|
115
|
+
CLI_PROCESS_TIMEOUT_SEC_MAX,
|
|
116
|
+
),
|
|
117
|
+
}
|
|
118
|
+
}
|
package/src/lib/safe-storage.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
function canUseLocalStorage(): boolean {
|
|
2
|
-
|
|
2
|
+
if (typeof window === 'undefined') return false
|
|
3
|
+
try {
|
|
4
|
+
return !!window.localStorage
|
|
5
|
+
} catch {
|
|
6
|
+
return false
|
|
7
|
+
}
|
|
3
8
|
}
|
|
4
9
|
|
|
5
10
|
export function safeStorageGet(key: string): string | null {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import type { Agent } from '@/types'
|
|
4
|
+
import {
|
|
5
|
+
isDelegationTaskPayload,
|
|
6
|
+
resolveDelegatorAgentId,
|
|
7
|
+
resolveManagedAgentAssignment,
|
|
8
|
+
validateManagedAgentAssignment,
|
|
9
|
+
} from './agent-assignment'
|
|
10
|
+
|
|
11
|
+
const now = Date.now()
|
|
12
|
+
const agents: Record<string, Agent> = {
|
|
13
|
+
molly: {
|
|
14
|
+
id: 'molly',
|
|
15
|
+
name: 'Molly',
|
|
16
|
+
description: '',
|
|
17
|
+
systemPrompt: '',
|
|
18
|
+
provider: 'openai',
|
|
19
|
+
model: 'gpt-4o',
|
|
20
|
+
createdAt: now,
|
|
21
|
+
updatedAt: now,
|
|
22
|
+
},
|
|
23
|
+
writer: {
|
|
24
|
+
id: 'writer',
|
|
25
|
+
name: 'Writer',
|
|
26
|
+
description: '',
|
|
27
|
+
systemPrompt: '',
|
|
28
|
+
provider: 'openai',
|
|
29
|
+
model: 'gpt-4o',
|
|
30
|
+
createdAt: now,
|
|
31
|
+
updatedAt: now,
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('resolveManagedAgentAssignment', () => {
|
|
36
|
+
it('resolves explicit aliases to concrete agent ids', () => {
|
|
37
|
+
const resolved = resolveManagedAgentAssignment({ assignee: 'Writer' }, agents, 'molly')
|
|
38
|
+
assert.equal(resolved.agentId, 'writer')
|
|
39
|
+
assert.equal(resolved.source, 'explicit')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('resolves description-based delegation before scope checks', () => {
|
|
43
|
+
const resolved = resolveManagedAgentAssignment(
|
|
44
|
+
{ description: 'Please delegate this to @Writer and let them handle the draft.' },
|
|
45
|
+
agents,
|
|
46
|
+
'molly',
|
|
47
|
+
)
|
|
48
|
+
assert.equal(resolved.agentId, 'writer')
|
|
49
|
+
assert.equal(resolved.source, 'description')
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('validateManagedAgentAssignment', () => {
|
|
54
|
+
it('blocks assigning another agent when scope is self', () => {
|
|
55
|
+
const resolved = resolveManagedAgentAssignment({ assignee: 'writer' }, agents, 'molly')
|
|
56
|
+
const error = validateManagedAgentAssignment({
|
|
57
|
+
resourceLabel: 'tasks',
|
|
58
|
+
agents,
|
|
59
|
+
assignScope: 'self',
|
|
60
|
+
currentAgentId: 'molly',
|
|
61
|
+
targetAgentId: resolved.agentId,
|
|
62
|
+
unresolvedReference: resolved.unresolvedReference,
|
|
63
|
+
})
|
|
64
|
+
assert.match(error || '', /only assign tasks to yourself/i)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('allows self-assignment in self scope', () => {
|
|
68
|
+
const resolved = resolveManagedAgentAssignment({ agentId: 'molly' }, agents, 'molly')
|
|
69
|
+
const error = validateManagedAgentAssignment({
|
|
70
|
+
resourceLabel: 'tasks',
|
|
71
|
+
agents,
|
|
72
|
+
assignScope: 'self',
|
|
73
|
+
currentAgentId: 'molly',
|
|
74
|
+
targetAgentId: resolved.agentId,
|
|
75
|
+
unresolvedReference: resolved.unresolvedReference,
|
|
76
|
+
})
|
|
77
|
+
assert.equal(error, null)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('rejects unknown explicit agent references', () => {
|
|
81
|
+
const resolved = resolveManagedAgentAssignment({ agentId: 'missing-agent' }, agents, 'molly')
|
|
82
|
+
const error = validateManagedAgentAssignment({
|
|
83
|
+
resourceLabel: 'tasks',
|
|
84
|
+
agents,
|
|
85
|
+
assignScope: 'all',
|
|
86
|
+
currentAgentId: 'molly',
|
|
87
|
+
targetAgentId: resolved.agentId,
|
|
88
|
+
unresolvedReference: resolved.unresolvedReference,
|
|
89
|
+
})
|
|
90
|
+
assert.match(error || '', /unknown agent "missing-agent"/i)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('rejects self-delegation using resolved agent ids', () => {
|
|
94
|
+
const payload = {
|
|
95
|
+
agentId: 'molly',
|
|
96
|
+
sourceType: 'delegation',
|
|
97
|
+
delegatedByAgentId: 'Molly',
|
|
98
|
+
}
|
|
99
|
+
const resolved = resolveManagedAgentAssignment(payload, agents, 'molly')
|
|
100
|
+
const error = validateManagedAgentAssignment({
|
|
101
|
+
resourceLabel: 'tasks',
|
|
102
|
+
agents,
|
|
103
|
+
assignScope: 'all',
|
|
104
|
+
currentAgentId: 'molly',
|
|
105
|
+
targetAgentId: resolved.agentId,
|
|
106
|
+
unresolvedReference: resolved.unresolvedReference,
|
|
107
|
+
isDelegation: isDelegationTaskPayload(payload),
|
|
108
|
+
delegatorAgentId: resolveDelegatorAgentId(payload, agents, 'molly'),
|
|
109
|
+
})
|
|
110
|
+
assert.match(error || '', /different agent id/i)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { Agent } from '@/types'
|
|
2
|
+
import { resolveAgentReference, resolveTaskAgentFromDescription } from './task-mention'
|
|
3
|
+
|
|
4
|
+
export const MANAGED_AGENT_REFERENCE_KEYS = [
|
|
5
|
+
'agentId',
|
|
6
|
+
'agent_id',
|
|
7
|
+
'assignedAgentId',
|
|
8
|
+
'assigned_agent_id',
|
|
9
|
+
'assignedToAgentId',
|
|
10
|
+
'assigned_to_agent_id',
|
|
11
|
+
'assigneeId',
|
|
12
|
+
'assignee_id',
|
|
13
|
+
'assignedAgent',
|
|
14
|
+
'assigned_agent',
|
|
15
|
+
'assignedTo',
|
|
16
|
+
'assigned_to',
|
|
17
|
+
'assignee',
|
|
18
|
+
'agent',
|
|
19
|
+
'owner',
|
|
20
|
+
] as const
|
|
21
|
+
|
|
22
|
+
type AssignmentSource = 'explicit' | 'description' | 'fallback' | 'none'
|
|
23
|
+
|
|
24
|
+
export interface ManagedAgentAssignmentResolution {
|
|
25
|
+
agentId: string | null
|
|
26
|
+
explicitReference: string | null
|
|
27
|
+
unresolvedReference: string | null
|
|
28
|
+
source: AssignmentSource
|
|
29
|
+
hadExplicitInput: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function firstNonEmptyString(
|
|
33
|
+
parsed: Record<string, unknown>,
|
|
34
|
+
keys: readonly string[],
|
|
35
|
+
): string | null {
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
const raw = parsed[key]
|
|
38
|
+
if (typeof raw !== 'string') continue
|
|
39
|
+
const trimmed = raw.trim()
|
|
40
|
+
if (trimmed) return trimmed
|
|
41
|
+
}
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function hasManagedAgentAssignmentInput(
|
|
46
|
+
parsed: Record<string, unknown>,
|
|
47
|
+
keys: readonly string[] = MANAGED_AGENT_REFERENCE_KEYS,
|
|
48
|
+
): boolean {
|
|
49
|
+
return keys.some((key) => Object.prototype.hasOwnProperty.call(parsed, key))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resolveManagedAgentAssignment(
|
|
53
|
+
parsed: Record<string, unknown>,
|
|
54
|
+
agents: Record<string, Agent>,
|
|
55
|
+
fallbackAgentId?: string | null,
|
|
56
|
+
opts?: {
|
|
57
|
+
allowDescription?: boolean
|
|
58
|
+
keys?: readonly string[]
|
|
59
|
+
},
|
|
60
|
+
): ManagedAgentAssignmentResolution {
|
|
61
|
+
const keys = opts?.keys ?? MANAGED_AGENT_REFERENCE_KEYS
|
|
62
|
+
const explicitReference = firstNonEmptyString(parsed, keys)
|
|
63
|
+
const hadExplicitInput = hasManagedAgentAssignmentInput(parsed, keys)
|
|
64
|
+
if (explicitReference) {
|
|
65
|
+
const resolved = resolveAgentReference(explicitReference, agents)
|
|
66
|
+
return {
|
|
67
|
+
agentId: resolved,
|
|
68
|
+
explicitReference,
|
|
69
|
+
unresolvedReference: resolved ? null : explicitReference,
|
|
70
|
+
source: 'explicit',
|
|
71
|
+
hadExplicitInput,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (opts?.allowDescription !== false) {
|
|
76
|
+
const description = typeof parsed.description === 'string' ? parsed.description.trim() : ''
|
|
77
|
+
if (description) {
|
|
78
|
+
const resolvedFromDescription = resolveTaskAgentFromDescription(description, '', agents).trim()
|
|
79
|
+
if (resolvedFromDescription) {
|
|
80
|
+
return {
|
|
81
|
+
agentId: resolvedFromDescription,
|
|
82
|
+
explicitReference: null,
|
|
83
|
+
unresolvedReference: null,
|
|
84
|
+
source: 'description',
|
|
85
|
+
hadExplicitInput,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const fallback = typeof fallbackAgentId === 'string' ? fallbackAgentId.trim() : ''
|
|
92
|
+
if (fallback) {
|
|
93
|
+
return {
|
|
94
|
+
agentId: fallback,
|
|
95
|
+
explicitReference: null,
|
|
96
|
+
unresolvedReference: null,
|
|
97
|
+
source: 'fallback',
|
|
98
|
+
hadExplicitInput,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
agentId: null,
|
|
104
|
+
explicitReference: null,
|
|
105
|
+
unresolvedReference: null,
|
|
106
|
+
source: 'none',
|
|
107
|
+
hadExplicitInput,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function resolveDelegatorAgentId(
|
|
112
|
+
parsed: Record<string, unknown>,
|
|
113
|
+
agents: Record<string, Agent>,
|
|
114
|
+
fallbackAgentId?: string | null,
|
|
115
|
+
): string | null {
|
|
116
|
+
const explicitDelegator = typeof parsed.delegatedByAgentId === 'string'
|
|
117
|
+
? parsed.delegatedByAgentId.trim()
|
|
118
|
+
: ''
|
|
119
|
+
if (explicitDelegator) {
|
|
120
|
+
return resolveAgentReference(explicitDelegator, agents) || explicitDelegator
|
|
121
|
+
}
|
|
122
|
+
const fallback = typeof fallbackAgentId === 'string' ? fallbackAgentId.trim() : ''
|
|
123
|
+
return fallback || null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function isDelegationTaskPayload(parsed: Record<string, unknown>): boolean {
|
|
127
|
+
const sourceType = typeof parsed.sourceType === 'string' ? parsed.sourceType.trim().toLowerCase() : ''
|
|
128
|
+
if (sourceType === 'delegation') return true
|
|
129
|
+
if (typeof parsed.delegatedFromTaskId === 'string' && parsed.delegatedFromTaskId.trim()) return true
|
|
130
|
+
if (typeof parsed.delegatedByAgentId === 'string' && parsed.delegatedByAgentId.trim()) return true
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function validateManagedAgentAssignment(params: {
|
|
135
|
+
resourceLabel: string
|
|
136
|
+
agents: Record<string, Agent>
|
|
137
|
+
assignScope: 'self' | 'all'
|
|
138
|
+
currentAgentId?: string | null
|
|
139
|
+
targetAgentId?: string | null
|
|
140
|
+
unresolvedReference?: string | null
|
|
141
|
+
isDelegation?: boolean
|
|
142
|
+
delegatorAgentId?: string | null
|
|
143
|
+
}): string | null {
|
|
144
|
+
const currentAgentId = typeof params.currentAgentId === 'string' ? params.currentAgentId.trim() : ''
|
|
145
|
+
const targetAgentId = typeof params.targetAgentId === 'string' ? params.targetAgentId.trim() : ''
|
|
146
|
+
const unresolvedReference = typeof params.unresolvedReference === 'string' ? params.unresolvedReference.trim() : ''
|
|
147
|
+
const delegatorAgentId = typeof params.delegatorAgentId === 'string' ? params.delegatorAgentId.trim() : ''
|
|
148
|
+
|
|
149
|
+
if (unresolvedReference) {
|
|
150
|
+
return `Error: Unknown agent "${unresolvedReference}". Use an existing agent ID or exact agent name.`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (targetAgentId && !params.agents[targetAgentId]) {
|
|
154
|
+
return `Error: Unknown agent "${targetAgentId}". Use an existing agent ID or exact agent name.`
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (params.assignScope === 'self' && currentAgentId && targetAgentId && targetAgentId !== currentAgentId) {
|
|
158
|
+
return `Error: You can only assign ${params.resourceLabel} to yourself ("${currentAgentId}"). To assign to other agents, ask a user to enable "Assign to Other Agents" in your agent settings.`
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (params.isDelegation && targetAgentId) {
|
|
162
|
+
const comparisonId = delegatorAgentId || currentAgentId
|
|
163
|
+
if (comparisonId && targetAgentId === comparisonId) {
|
|
164
|
+
return 'Error: Delegation target must be a different agent ID. Create a normal self-task instead of delegating to yourself.'
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return null
|
|
169
|
+
}
|