@swarmclawai/swarmclaw 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -150
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +26 -0
- package/src/app/api/agents/[id]/thread/route.ts +37 -9
- package/src/app/api/agents/route.ts +13 -2
- package/src/app/api/auth/route.ts +76 -7
- package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
- package/src/app/api/{sessions → chats}/[id]/browser/route.ts +5 -1
- package/src/app/api/{sessions → chats}/[id]/chat/route.ts +7 -3
- package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
- package/src/app/api/chats/[id]/main-loop/route.ts +13 -0
- package/src/app/api/{sessions → chats}/[id]/messages/route.ts +19 -13
- package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/route.ts +22 -52
- package/src/app/api/{sessions → chats}/[id]/stop/route.ts +6 -1
- package/src/app/api/{sessions → chats}/route.ts +21 -7
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/skills/route.ts +11 -3
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +6 -26
- package/src/app/api/plugins/settings/route.ts +40 -0
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/settings/route.ts +49 -7
- package/src/app/api/tasks/[id]/route.ts +15 -6
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/usage/route.ts +30 -0
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +9 -2
- package/src/cli/index.js +39 -33
- package/src/cli/index.ts +43 -49
- package/src/cli/spec.js +29 -27
- package/src/components/agents/agent-card.tsx +16 -13
- package/src/components/agents/agent-chat-list.tsx +104 -4
- package/src/components/agents/agent-list.tsx +54 -22
- package/src/components/agents/agent-sheet.tsx +209 -18
- package/src/components/agents/cron-job-form.tsx +3 -3
- package/src/components/agents/inspector-panel.tsx +110 -50
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +5 -38
- package/src/components/chat/chat-area.tsx +39 -27
- package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +7 -23
- package/src/components/chat/chat-header.tsx +299 -314
- package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +11 -14
- package/src/components/chat/chat-tool-toggles.tsx +26 -17
- package/src/components/chat/checkpoint-timeline.tsx +4 -4
- package/src/components/chat/message-bubble.tsx +4 -1
- package/src/components/chat/message-list.tsx +5 -3
- package/src/components/chat/session-debug-panel.tsx +1 -1
- package/src/components/chat/tool-request-banner.tsx +3 -3
- package/src/components/chatrooms/agent-hover-card.tsx +3 -3
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
- package/src/components/chatrooms/chatroom-view.tsx +347 -205
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +218 -1
- package/src/components/home/home-view.tsx +129 -5
- package/src/components/layout/app-layout.tsx +392 -182
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/plugins/plugin-list.tsx +487 -254
- package/src/components/plugins/plugin-sheet.tsx +236 -13
- package/src/components/projects/project-detail.tsx +183 -0
- package/src/components/settings/gateway-connection-panel.tsx +1 -1
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/command-palette.tsx +111 -25
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +78 -1
- package/src/components/shared/settings/section-orchestrator.tsx +3 -3
- package/src/components/shared/settings/section-providers.tsx +1 -1
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +244 -56
- package/src/components/tasks/approvals-panel.tsx +205 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/usage/metrics-dashboard.tsx +147 -1
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +8 -8
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/chat.ts +1 -1
- package/src/lib/{sessions.ts → chats.ts} +28 -18
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/providers/claude-cli.ts +1 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +205 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +36 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/capability-router.ts +10 -8
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
- package/src/lib/server/chat-execution.ts +285 -165
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +67 -2
- package/src/lib/server/chatroom-helpers.ts +48 -8
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +948 -112
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +188 -9
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/cost.ts +34 -1
- package/src/lib/server/daemon-state.ts +61 -3
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/heartbeat-service.ts +14 -40
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +28 -1103
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +5 -6
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +20 -9
- package/src/lib/server/orchestrator.ts +7 -7
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +207 -0
- package/src/lib/server/plugins.ts +927 -66
- package/src/lib/server/provider-health.ts +38 -6
- package/src/lib/server/queue.ts +13 -28
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -82
- package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
- package/src/lib/server/session-tools/calendar.ts +366 -0
- package/src/lib/server/session-tools/canvas.ts +1 -1
- package/src/lib/server/session-tools/chatroom.ts +4 -2
- package/src/lib/server/session-tools/connector.ts +114 -10
- package/src/lib/server/session-tools/context.ts +21 -5
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +74 -28
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +497 -24
- package/src/lib/server/session-tools/discovery.ts +24 -6
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/edit_file.ts +4 -2
- package/src/lib/server/session-tools/email.ts +320 -0
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +241 -25
- package/src/lib/server/session-tools/git.ts +1 -1
- package/src/lib/server/session-tools/http.ts +1 -1
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +380 -0
- package/src/lib/server/session-tools/index.ts +130 -50
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/memory.ts +172 -3
- package/src/lib/server/session-tools/monitor.ts +151 -8
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
- package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +148 -7
- package/src/lib/server/session-tools/plugin-creator.ts +89 -26
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +301 -0
- package/src/lib/server/session-tools/sample-ui.ts +1 -1
- package/src/lib/server/session-tools/sandbox.ts +4 -2
- package/src/lib/server/session-tools/schedule.ts +24 -12
- package/src/lib/server/session-tools/session-info.ts +43 -7
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
- package/src/lib/server/session-tools/shell.ts +5 -2
- package/src/lib/server/session-tools/subagent.ts +194 -28
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +42 -12
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +926 -91
- package/src/lib/server/storage.ts +255 -16
- package/src/lib/server/stream-agent-chat.ts +116 -268
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -10
- package/src/lib/server/tool-aliases.ts +66 -18
- package/src/lib/server/tool-capability-policy.test.ts +9 -9
- package/src/lib/server/tool-capability-policy.ts +38 -27
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/tool-definitions.ts +4 -0
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +10 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +5 -11
- package/src/stores/use-chat-store.ts +38 -9
- package/src/types/index.ts +352 -47
- package/src/app/api/sessions/[id]/main-loop/route.ts +0 -94
- package/src/components/sessions/new-session-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -24
- package/src/lib/server/session-run-manager.test.ts +0 -23
- /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { URL } from 'url'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
5
|
+
import * as cheerio from 'cheerio'
|
|
6
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
7
|
+
import { getPluginManager } from '../plugins'
|
|
8
|
+
import { runStructuredExtraction } from '../structured-extract'
|
|
9
|
+
import type { ToolBuildContext } from './context'
|
|
10
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
11
|
+
|
|
12
|
+
interface CrawledPage {
|
|
13
|
+
url: string
|
|
14
|
+
status: number
|
|
15
|
+
title: string | null
|
|
16
|
+
depth: number
|
|
17
|
+
textPreview: string
|
|
18
|
+
headings: string[]
|
|
19
|
+
links: string[]
|
|
20
|
+
hash: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function cleanText(value: string, max = 1200): string {
|
|
24
|
+
const normalized = value.replace(/\s+/g, ' ').trim()
|
|
25
|
+
return normalized.length <= max ? normalized : `${normalized.slice(0, max)}...`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeUrl(input: string, base?: string): string {
|
|
29
|
+
const resolved = base ? new URL(input, base) : new URL(input)
|
|
30
|
+
resolved.hash = ''
|
|
31
|
+
if (resolved.pathname.endsWith('/') && resolved.pathname !== '/') {
|
|
32
|
+
resolved.pathname = resolved.pathname.replace(/\/+$/, '')
|
|
33
|
+
}
|
|
34
|
+
return resolved.toString()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function shouldIncludeUrl(url: string, params: { includePattern?: string | null; excludePattern?: string | null }) {
|
|
38
|
+
if (params.includePattern) {
|
|
39
|
+
try {
|
|
40
|
+
if (!new RegExp(params.includePattern, 'i').test(url)) return false
|
|
41
|
+
} catch {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (params.excludePattern) {
|
|
46
|
+
try {
|
|
47
|
+
if (new RegExp(params.excludePattern, 'i').test(url)) return false
|
|
48
|
+
} catch {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function pageHash(text: string): string {
|
|
56
|
+
return crypto.createHash('sha1').update(text).digest('hex')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function fetchCrawlPage(url: string, depth: number): Promise<CrawledPage> {
|
|
60
|
+
const res = await fetch(url, {
|
|
61
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; SwarmClaw/1.0)' },
|
|
62
|
+
signal: AbortSignal.timeout(15_000),
|
|
63
|
+
})
|
|
64
|
+
const html = await res.text()
|
|
65
|
+
const $ = cheerio.load(html)
|
|
66
|
+
$('script, style, noscript').remove()
|
|
67
|
+
|
|
68
|
+
const title = cleanText($('title').first().text(), 200) || null
|
|
69
|
+
const headings = $('h1, h2, h3')
|
|
70
|
+
.toArray()
|
|
71
|
+
.map((node) => cleanText($(node).text(), 200))
|
|
72
|
+
.filter(Boolean)
|
|
73
|
+
.slice(0, 12)
|
|
74
|
+
const textPreview = cleanText($('body').text() || $.text(), 1600)
|
|
75
|
+
const links = $('a[href]')
|
|
76
|
+
.toArray()
|
|
77
|
+
.map((node) => $(node).attr('href') || '')
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
.map((href) => {
|
|
80
|
+
try {
|
|
81
|
+
return normalizeUrl(href, url)
|
|
82
|
+
} catch {
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
.filter((href): href is string => !!href)
|
|
87
|
+
.slice(0, 200)
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
url,
|
|
91
|
+
status: res.status,
|
|
92
|
+
title,
|
|
93
|
+
depth,
|
|
94
|
+
textPreview,
|
|
95
|
+
headings,
|
|
96
|
+
links: Array.from(new Set(links)),
|
|
97
|
+
hash: pageHash(`${title || ''}\n${textPreview}`),
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function crawlSite(params: {
|
|
102
|
+
startUrl: string
|
|
103
|
+
limit: number
|
|
104
|
+
maxDepth: number
|
|
105
|
+
sameOrigin: boolean
|
|
106
|
+
includePattern?: string | null
|
|
107
|
+
excludePattern?: string | null
|
|
108
|
+
}): Promise<CrawledPage[]> {
|
|
109
|
+
const startUrl = normalizeUrl(params.startUrl)
|
|
110
|
+
const startOrigin = new URL(startUrl).origin
|
|
111
|
+
const queue: Array<{ url: string; depth: number }> = [{ url: startUrl, depth: 0 }]
|
|
112
|
+
const visited = new Set<string>()
|
|
113
|
+
const pages: CrawledPage[] = []
|
|
114
|
+
|
|
115
|
+
while (queue.length > 0 && pages.length < params.limit) {
|
|
116
|
+
const current = queue.shift()!
|
|
117
|
+
if (visited.has(current.url)) continue
|
|
118
|
+
visited.add(current.url)
|
|
119
|
+
if (!shouldIncludeUrl(current.url, params)) continue
|
|
120
|
+
if (params.sameOrigin && new URL(current.url).origin !== startOrigin) continue
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const page = await fetchCrawlPage(current.url, current.depth)
|
|
124
|
+
pages.push(page)
|
|
125
|
+
if (current.depth >= params.maxDepth) continue
|
|
126
|
+
for (const link of page.links) {
|
|
127
|
+
if (visited.has(link)) continue
|
|
128
|
+
if (params.sameOrigin && new URL(link).origin !== startOrigin) continue
|
|
129
|
+
queue.push({ url: link, depth: current.depth + 1 })
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
// skip failed pages and continue crawling
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return pages
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function followPagination(params: {
|
|
140
|
+
startUrl: string
|
|
141
|
+
limit: number
|
|
142
|
+
}): Promise<CrawledPage[]> {
|
|
143
|
+
const pages: CrawledPage[] = []
|
|
144
|
+
const visited = new Set<string>()
|
|
145
|
+
let currentUrl = normalizeUrl(params.startUrl)
|
|
146
|
+
let depth = 0
|
|
147
|
+
|
|
148
|
+
while (currentUrl && pages.length < params.limit && !visited.has(currentUrl)) {
|
|
149
|
+
visited.add(currentUrl)
|
|
150
|
+
const page = await fetchCrawlPage(currentUrl, depth)
|
|
151
|
+
pages.push(page)
|
|
152
|
+
|
|
153
|
+
const res = await fetch(currentUrl, {
|
|
154
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; SwarmClaw/1.0)' },
|
|
155
|
+
signal: AbortSignal.timeout(15_000),
|
|
156
|
+
})
|
|
157
|
+
const html = await res.text()
|
|
158
|
+
const $ = cheerio.load(html)
|
|
159
|
+
const nextHref = $('link[rel="next"]').attr('href')
|
|
160
|
+
|| $('a[rel="next"]').attr('href')
|
|
161
|
+
|| $('a').toArray().map((node) => ({
|
|
162
|
+
href: $(node).attr('href') || '',
|
|
163
|
+
text: cleanText($(node).text(), 80).toLowerCase(),
|
|
164
|
+
})).find((candidate) => /^(next|next page|older|more|continue)/i.test(candidate.text))?.href
|
|
165
|
+
|
|
166
|
+
if (!nextHref) break
|
|
167
|
+
try {
|
|
168
|
+
currentUrl = normalizeUrl(nextHref, currentUrl)
|
|
169
|
+
} catch {
|
|
170
|
+
break
|
|
171
|
+
}
|
|
172
|
+
depth += 1
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return pages
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function dedupePages(input: CrawledPage[]): CrawledPage[] {
|
|
179
|
+
const seen = new Set<string>()
|
|
180
|
+
const out: CrawledPage[] = []
|
|
181
|
+
for (const page of input) {
|
|
182
|
+
const key = `${page.url}|${page.hash}`
|
|
183
|
+
if (seen.has(key)) continue
|
|
184
|
+
seen.add(key)
|
|
185
|
+
out.push(page)
|
|
186
|
+
}
|
|
187
|
+
return out
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function fetchSitemapUrls(sitemapUrl: string): Promise<string[]> {
|
|
191
|
+
const res = await fetch(sitemapUrl, {
|
|
192
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; SwarmClaw/1.0)' },
|
|
193
|
+
signal: AbortSignal.timeout(15_000),
|
|
194
|
+
})
|
|
195
|
+
const xml = await res.text()
|
|
196
|
+
const matches = Array.from(xml.matchAll(/<loc>\s*([^<]+)\s*<\/loc>/gi))
|
|
197
|
+
return Array.from(new Set(matches.map((match) => match[1]?.trim()).filter((value): value is string => !!value)))
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function normalizeSelectorMap(value: unknown): Record<string, string> {
|
|
201
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
|
|
202
|
+
const entries: Array<readonly [string, string]> = []
|
|
203
|
+
for (const [key, selector] of Object.entries(value as Record<string, unknown>)) {
|
|
204
|
+
if (typeof selector !== 'string') continue
|
|
205
|
+
const trimmed = selector.trim()
|
|
206
|
+
if (!trimmed) continue
|
|
207
|
+
entries.push([key, trimmed] as const)
|
|
208
|
+
}
|
|
209
|
+
return Object.fromEntries(entries)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function extractSelectorRows(urls: string[], selectors: Record<string, string>) {
|
|
213
|
+
const rows: Array<Record<string, unknown>> = []
|
|
214
|
+
for (const url of urls) {
|
|
215
|
+
const res = await fetch(url, {
|
|
216
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; SwarmClaw/1.0)' },
|
|
217
|
+
signal: AbortSignal.timeout(15_000),
|
|
218
|
+
})
|
|
219
|
+
const html = await res.text()
|
|
220
|
+
const $ = cheerio.load(html)
|
|
221
|
+
$('script, style, noscript').remove()
|
|
222
|
+
const row: Record<string, unknown> = { url }
|
|
223
|
+
for (const [key, selector] of Object.entries(selectors)) {
|
|
224
|
+
row[key] = cleanText($(selector).first().text(), 800)
|
|
225
|
+
}
|
|
226
|
+
rows.push(row)
|
|
227
|
+
}
|
|
228
|
+
return rows
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function normalizePagesInput(value: unknown): CrawledPage[] {
|
|
232
|
+
if (typeof value === 'string' && value.trim()) {
|
|
233
|
+
try {
|
|
234
|
+
return JSON.parse(value) as CrawledPage[]
|
|
235
|
+
} catch {
|
|
236
|
+
return []
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (Array.isArray(value)) return value as CrawledPage[]
|
|
240
|
+
return []
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function resolveExtractionSession(bctx: ToolBuildContext) {
|
|
244
|
+
const session = bctx.resolveCurrentSession?.()
|
|
245
|
+
if (!session) throw new Error('crawl batch_extract requires an active session context.')
|
|
246
|
+
return session
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function executeCrawlAction(args: Record<string, unknown>, bctx: ToolBuildContext) {
|
|
250
|
+
const normalized = normalizeToolInputArgs(args)
|
|
251
|
+
const action = String(normalized.action || 'crawl_site').trim().toLowerCase()
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
if (action === 'status') {
|
|
255
|
+
return JSON.stringify({
|
|
256
|
+
supports: ['crawl_site', 'follow_pagination', 'extract_sitemap', 'dedupe_pages', 'batch_extract'],
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (action === 'dedupe_pages') {
|
|
261
|
+
const pages = dedupePages(normalizePagesInput(normalized.pages))
|
|
262
|
+
return JSON.stringify({ count: pages.length, pages })
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const startUrl = typeof normalized.url === 'string'
|
|
266
|
+
? normalized.url
|
|
267
|
+
: typeof normalized.startUrl === 'string'
|
|
268
|
+
? normalized.startUrl
|
|
269
|
+
: ''
|
|
270
|
+
|
|
271
|
+
const limit = typeof normalized.limit === 'number' ? Math.max(1, Math.min(normalized.limit, 100)) : 20
|
|
272
|
+
const maxDepth = typeof normalized.maxDepth === 'number' ? Math.max(0, Math.min(normalized.maxDepth, 5)) : 2
|
|
273
|
+
const sameOrigin = normalized.sameOrigin !== false
|
|
274
|
+
|
|
275
|
+
if (action === 'crawl_site' || action === 'extract_sitemap') {
|
|
276
|
+
const sitemapUrl = typeof normalized.sitemapUrl === 'string' && normalized.sitemapUrl.trim()
|
|
277
|
+
? normalized.sitemapUrl.trim()
|
|
278
|
+
: null
|
|
279
|
+
const pages = action === 'extract_sitemap' && sitemapUrl
|
|
280
|
+
? dedupePages(await Promise.all(
|
|
281
|
+
(await fetchSitemapUrls(sitemapUrl))
|
|
282
|
+
.slice(0, limit)
|
|
283
|
+
.map((url) => fetchCrawlPage(normalizeUrl(url), 0)),
|
|
284
|
+
))
|
|
285
|
+
: dedupePages(await crawlSite({
|
|
286
|
+
startUrl,
|
|
287
|
+
limit,
|
|
288
|
+
maxDepth,
|
|
289
|
+
sameOrigin,
|
|
290
|
+
includePattern: typeof normalized.includePattern === 'string' ? normalized.includePattern : null,
|
|
291
|
+
excludePattern: typeof normalized.excludePattern === 'string' ? normalized.excludePattern : null,
|
|
292
|
+
}))
|
|
293
|
+
if (action === 'extract_sitemap') {
|
|
294
|
+
return JSON.stringify({
|
|
295
|
+
startUrl: normalizeUrl(startUrl),
|
|
296
|
+
count: pages.length,
|
|
297
|
+
urlCount: pages.length,
|
|
298
|
+
urls: pages.map((page) => page.url),
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
return JSON.stringify({
|
|
302
|
+
startUrl: normalizeUrl(startUrl),
|
|
303
|
+
count: pages.length,
|
|
304
|
+
pageCount: pages.length,
|
|
305
|
+
pages,
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (action === 'follow_pagination') {
|
|
310
|
+
const pages = dedupePages(await followPagination({ startUrl, limit }))
|
|
311
|
+
return JSON.stringify({
|
|
312
|
+
startUrl: normalizeUrl(startUrl),
|
|
313
|
+
count: pages.length,
|
|
314
|
+
pageCount: pages.length,
|
|
315
|
+
pages,
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (action === 'batch_extract') {
|
|
320
|
+
const seededPages = normalizePagesInput(normalized.pages)
|
|
321
|
+
if (seededPages.length === 0 && !startUrl) return 'Error: url/startUrl or pages is required.'
|
|
322
|
+
const pages = seededPages.length > 0
|
|
323
|
+
? dedupePages(seededPages)
|
|
324
|
+
: dedupePages(await crawlSite({
|
|
325
|
+
startUrl,
|
|
326
|
+
limit,
|
|
327
|
+
maxDepth,
|
|
328
|
+
sameOrigin,
|
|
329
|
+
includePattern: typeof normalized.includePattern === 'string' ? normalized.includePattern : null,
|
|
330
|
+
excludePattern: typeof normalized.excludePattern === 'string' ? normalized.excludePattern : null,
|
|
331
|
+
}))
|
|
332
|
+
const selectors = normalizeSelectorMap(normalized.selectors)
|
|
333
|
+
if (Object.keys(selectors).length > 0) {
|
|
334
|
+
const rows = await extractSelectorRows(pages.map((page) => page.url), selectors)
|
|
335
|
+
return JSON.stringify({
|
|
336
|
+
count: pages.length,
|
|
337
|
+
pageCount: pages.length,
|
|
338
|
+
rowCount: rows.length,
|
|
339
|
+
urls: pages.map((page) => page.url),
|
|
340
|
+
rows,
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
const session = resolveExtractionSession(bctx)
|
|
344
|
+
const sourceText = pages
|
|
345
|
+
.map((page) => `URL: ${page.url}\nTitle: ${page.title || ''}\nHeadings: ${page.headings.join(' | ')}\nText: ${page.textPreview}`)
|
|
346
|
+
.join('\n\n---\n\n')
|
|
347
|
+
const extracted = await runStructuredExtraction({
|
|
348
|
+
session,
|
|
349
|
+
text: sourceText,
|
|
350
|
+
schema: normalized.schema,
|
|
351
|
+
instruction: typeof normalized.instruction === 'string'
|
|
352
|
+
? normalized.instruction
|
|
353
|
+
: 'Aggregate the crawled pages and extract the requested structured information.',
|
|
354
|
+
maxChars: typeof normalized.maxChars === 'number' ? Math.max(10_000, normalized.maxChars) : 120_000,
|
|
355
|
+
})
|
|
356
|
+
return JSON.stringify({
|
|
357
|
+
count: pages.length,
|
|
358
|
+
pageCount: pages.length,
|
|
359
|
+
urls: pages.map((page) => page.url),
|
|
360
|
+
object: extracted.object,
|
|
361
|
+
validationErrors: extracted.validationErrors,
|
|
362
|
+
provider: extracted.provider,
|
|
363
|
+
model: extracted.model,
|
|
364
|
+
raw: normalized.includeRaw === true ? extracted.raw : undefined,
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (!startUrl) return 'Error: url or startUrl is required.'
|
|
369
|
+
|
|
370
|
+
return `Error: Unknown action "${action}".`
|
|
371
|
+
} catch (err: unknown) {
|
|
372
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const CrawlPlugin: Plugin = {
|
|
377
|
+
name: 'Crawl',
|
|
378
|
+
enabledByDefault: false,
|
|
379
|
+
description: 'Research whole sites by crawling pages, following pagination, deduping results, and batch-extracting structure.',
|
|
380
|
+
hooks: {
|
|
381
|
+
getCapabilityDescription: () =>
|
|
382
|
+
'I can crawl websites with `crawl`, including sitemap extraction, pagination following, page deduping, and batch structured extraction over many pages.',
|
|
383
|
+
} as PluginHooks,
|
|
384
|
+
tools: [
|
|
385
|
+
{
|
|
386
|
+
name: 'crawl',
|
|
387
|
+
description: 'Site crawler. Actions: crawl_site, follow_pagination, extract_sitemap, dedupe_pages, batch_extract, status.',
|
|
388
|
+
parameters: {
|
|
389
|
+
type: 'object',
|
|
390
|
+
properties: {
|
|
391
|
+
action: {
|
|
392
|
+
type: 'string',
|
|
393
|
+
enum: ['crawl_site', 'follow_pagination', 'extract_sitemap', 'dedupe_pages', 'batch_extract', 'status'],
|
|
394
|
+
},
|
|
395
|
+
url: { type: 'string' },
|
|
396
|
+
startUrl: { type: 'string' },
|
|
397
|
+
sitemapUrl: { type: 'string' },
|
|
398
|
+
pages: {},
|
|
399
|
+
limit: { type: 'number' },
|
|
400
|
+
maxDepth: { type: 'number' },
|
|
401
|
+
sameOrigin: { type: 'boolean' },
|
|
402
|
+
includePattern: { type: 'string' },
|
|
403
|
+
excludePattern: { type: 'string' },
|
|
404
|
+
selectors: {},
|
|
405
|
+
schema: {},
|
|
406
|
+
instruction: { type: 'string' },
|
|
407
|
+
maxChars: { type: 'number' },
|
|
408
|
+
includeRaw: { type: 'boolean' },
|
|
409
|
+
},
|
|
410
|
+
required: ['action'],
|
|
411
|
+
},
|
|
412
|
+
execute: async (args, context) => {
|
|
413
|
+
const syntheticBuildContext = {
|
|
414
|
+
cwd: context.session.cwd || process.cwd(),
|
|
415
|
+
ctx: { sessionId: context.session.id, agentId: context.session.agentId || null },
|
|
416
|
+
hasPlugin: () => true,
|
|
417
|
+
hasTool: () => true,
|
|
418
|
+
cleanupFns: [],
|
|
419
|
+
commandTimeoutMs: 0,
|
|
420
|
+
claudeTimeoutMs: 0,
|
|
421
|
+
cliProcessTimeoutMs: 0,
|
|
422
|
+
persistDelegateResumeId: () => undefined,
|
|
423
|
+
readStoredDelegateResumeId: () => null,
|
|
424
|
+
resolveCurrentSession: () => context.session,
|
|
425
|
+
activePlugins: context.session.plugins || [],
|
|
426
|
+
} as ToolBuildContext
|
|
427
|
+
return executeCrawlAction(args, syntheticBuildContext)
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
getPluginManager().registerBuiltin('crawl', CrawlPlugin)
|
|
434
|
+
|
|
435
|
+
export function buildCrawlTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
436
|
+
if (!bctx.hasPlugin('crawl')) return []
|
|
437
|
+
return [
|
|
438
|
+
tool(
|
|
439
|
+
async (args) => executeCrawlAction(args, bctx),
|
|
440
|
+
{
|
|
441
|
+
name: 'crawl',
|
|
442
|
+
description: CrawlPlugin.tools![0].description,
|
|
443
|
+
schema: z.object({}).passthrough(),
|
|
444
|
+
},
|
|
445
|
+
),
|
|
446
|
+
]
|
|
447
|
+
}
|
|
@@ -22,7 +22,13 @@ import {
|
|
|
22
22
|
import { resolveScheduleName } from '@/lib/schedule-name'
|
|
23
23
|
import { findDuplicateSchedule, type ScheduleLike } from '@/lib/schedule-dedupe'
|
|
24
24
|
import { computeTaskFingerprint, findDuplicateTask } from '@/lib/task-dedupe'
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
hasManagedAgentAssignmentInput,
|
|
27
|
+
isDelegationTaskPayload,
|
|
28
|
+
resolveDelegatorAgentId,
|
|
29
|
+
resolveManagedAgentAssignment,
|
|
30
|
+
validateManagedAgentAssignment,
|
|
31
|
+
} from '@/lib/server/agent-assignment'
|
|
26
32
|
import { normalizeTaskQualityGate } from '@/lib/server/task-quality-gate'
|
|
27
33
|
import type { ToolBuildContext } from './context'
|
|
28
34
|
import { safePath, findBinaryOnPath } from './context'
|
|
@@ -137,7 +143,8 @@ const RESOURCE_DEFAULTS: Record<string, (parsed: any) => any> = {
|
|
|
137
143
|
soul: p.soul || '',
|
|
138
144
|
provider: p.provider || 'claude-cli',
|
|
139
145
|
model: p.model || '',
|
|
140
|
-
|
|
146
|
+
platformAssignScope: p.platformAssignScope === 'all' ? 'all' : 'self',
|
|
147
|
+
isOrchestrator: p.platformAssignScope === 'all',
|
|
141
148
|
tools: p.tools || [],
|
|
142
149
|
skills: p.skills || [],
|
|
143
150
|
skillIds: p.skillIds || [],
|
|
@@ -233,12 +240,12 @@ const PLATFORM_RESOURCES: Record<string, {
|
|
|
233
240
|
|
|
234
241
|
export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
235
242
|
const tools: StructuredToolInterface[] = []
|
|
236
|
-
const { cwd, ctx,
|
|
243
|
+
const { cwd, ctx, hasPlugin } = bctx
|
|
237
244
|
|
|
238
245
|
// Build dynamic agent summary for tools that need agent awareness
|
|
239
246
|
const assignScope = ctx?.platformAssignScope || 'self'
|
|
240
247
|
let agentSummary = ''
|
|
241
|
-
if (
|
|
248
|
+
if (hasPlugin('manage_tasks') || hasPlugin('manage_schedules')) {
|
|
242
249
|
if (assignScope === 'all') {
|
|
243
250
|
try {
|
|
244
251
|
const agents = loadAgents()
|
|
@@ -251,17 +258,17 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
for (const [toolKey, res] of Object.entries(PLATFORM_RESOURCES)) {
|
|
254
|
-
if (!
|
|
261
|
+
if (!hasPlugin(toolKey)) continue
|
|
255
262
|
|
|
256
263
|
let description = `Manage SwarmClaw ${res.label}. ${res.readOnly ? 'List and get only.' : 'List, get, create, update, or delete.'} Returns JSON.`
|
|
257
264
|
if (toolKey === 'manage_tasks') {
|
|
258
265
|
if (assignScope === 'self') {
|
|
259
|
-
description += `\n\
|
|
266
|
+
description += `\n\nYou may create tasks for yourself ("${ctx?.agentId || 'unknown'}") or leave them unassigned to track multi-step work. You cannot assign tasks to other agents unless a user enables "Assign to Other Agents" in your agent settings. Valid manual statuses: backlog, queued, completed, failed, archived. "running" is runtime-only and set automatically when execution starts.`
|
|
260
267
|
} else {
|
|
261
|
-
description += `\n\
|
|
268
|
+
description += `\n\nYou may create tasks for yourself, leave them unassigned, or delegate them to other agents. Your agent ID is "${ctx?.agentId || 'unknown'}". When delegating, set a target agent using "agentId", "assignee", "agent", "assignedAgentId", or "assigned_agent_id". Use the target agent's exact ID when possible. Valid manual statuses: backlog, queued, completed, failed, archived. "running" is runtime-only and set automatically when execution starts.` + agentSummary
|
|
262
269
|
}
|
|
263
270
|
} else if (toolKey === 'manage_agents') {
|
|
264
|
-
description += `\n\nAgents may self-edit their own soul. To update your soul, use action="update", id="${ctx?.agentId || 'your-agent-id'}", and include data with the "soul" field.`
|
|
271
|
+
description += `\n\nAgents may self-edit their own soul. To update your soul, use action="update", id="${ctx?.agentId || 'your-agent-id'}", and include data with the "soul" field. Set "platformAssignScope":"all" to let an agent delegate work across the fleet; use "self" for solo execution.`
|
|
265
272
|
} else if (toolKey === 'manage_schedules') {
|
|
266
273
|
if (assignScope === 'self') {
|
|
267
274
|
description += `\n\nSet "agentId" to assign a schedule to yourself ("${ctx?.agentId || 'unknown'}") or leave it null. You can only assign schedules to yourself. Schedule types: interval (set intervalMs), cron (set cron), once (set runAt). Set taskPrompt for what the agent should do. Before create, call list/get to avoid duplicate schedules. If an equivalent active/paused schedule already exists, create returns that existing schedule (deduplicated=true).`
|
|
@@ -337,13 +344,30 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
337
344
|
if (parsed && typeof parsed === 'object' && 'id' in parsed) {
|
|
338
345
|
delete (parsed as Record<string, unknown>).id
|
|
339
346
|
}
|
|
340
|
-
// Enforce assignment scope for tasks and schedules
|
|
341
|
-
if (assignScope === 'self' && (toolKey === 'manage_tasks' || toolKey === 'manage_schedules')) {
|
|
342
|
-
if (parsed.agentId && parsed.agentId !== ctx?.agentId) {
|
|
343
|
-
return `Error: You can only assign ${res.label} to yourself ("${ctx?.agentId}"). To assign to other agents, ask a user to enable "Assign to Other Agents" in your agent settings.`
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
347
|
const now = Date.now()
|
|
348
|
+
if (toolKey === 'manage_tasks' || toolKey === 'manage_schedules') {
|
|
349
|
+
const agents = loadAgents()
|
|
350
|
+
const resolution = resolveManagedAgentAssignment(
|
|
351
|
+
parsed as Record<string, unknown>,
|
|
352
|
+
agents,
|
|
353
|
+
toolKey === 'manage_tasks' ? (parsed.agentId || ctx?.agentId || null) : null,
|
|
354
|
+
{ allowDescription: toolKey === 'manage_tasks' },
|
|
355
|
+
)
|
|
356
|
+
const assignmentError = validateManagedAgentAssignment({
|
|
357
|
+
resourceLabel: res.label,
|
|
358
|
+
agents,
|
|
359
|
+
assignScope,
|
|
360
|
+
currentAgentId: ctx?.agentId || null,
|
|
361
|
+
targetAgentId: resolution.agentId,
|
|
362
|
+
unresolvedReference: resolution.unresolvedReference,
|
|
363
|
+
isDelegation: toolKey === 'manage_tasks' ? isDelegationTaskPayload(parsed as Record<string, unknown>) : false,
|
|
364
|
+
delegatorAgentId: toolKey === 'manage_tasks'
|
|
365
|
+
? resolveDelegatorAgentId(parsed as Record<string, unknown>, agents, ctx?.agentId || null)
|
|
366
|
+
: null,
|
|
367
|
+
})
|
|
368
|
+
if (assignmentError) return assignmentError
|
|
369
|
+
parsed.agentId = resolution.agentId
|
|
370
|
+
}
|
|
347
371
|
if (toolKey === 'manage_schedules') {
|
|
348
372
|
const duplicate = findDuplicateSchedule(all as Record<string, ScheduleLike>, {
|
|
349
373
|
agentId: parsed.agentId || null,
|
|
@@ -387,15 +411,6 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
387
411
|
})
|
|
388
412
|
}
|
|
389
413
|
}
|
|
390
|
-
// @mention agent resolution for tasks
|
|
391
|
-
if (toolKey === 'manage_tasks' && parsed.description) {
|
|
392
|
-
const agents = loadAgents()
|
|
393
|
-
parsed.agentId = resolveTaskAgentFromDescription(
|
|
394
|
-
parsed.description,
|
|
395
|
-
parsed.agentId || ctx?.agentId || '',
|
|
396
|
-
agents,
|
|
397
|
-
)
|
|
398
|
-
}
|
|
399
414
|
if (toolKey === 'manage_tasks') {
|
|
400
415
|
parsed.title = deriveTaskTitle(parsed)
|
|
401
416
|
if (!parsed.title || /^untitled task$/i.test(parsed.title)) {
|
|
@@ -499,10 +514,41 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
499
514
|
? normalizeTaskQualityGate(parsed.qualityGate, settings)
|
|
500
515
|
: null
|
|
501
516
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
517
|
+
if (toolKey === 'manage_tasks' || toolKey === 'manage_schedules') {
|
|
518
|
+
const agents = loadAgents()
|
|
519
|
+
const requestedClear = Object.prototype.hasOwnProperty.call(parsed, 'agentId') && parsed.agentId == null
|
|
520
|
+
const shouldResolveAssignment = requestedClear
|
|
521
|
+
|| hasManagedAgentAssignmentInput(parsed as Record<string, unknown>)
|
|
522
|
+
if (shouldResolveAssignment) {
|
|
523
|
+
const resolution = resolveManagedAgentAssignment(
|
|
524
|
+
parsed as Record<string, unknown>,
|
|
525
|
+
agents,
|
|
526
|
+
null,
|
|
527
|
+
{ allowDescription: false },
|
|
528
|
+
)
|
|
529
|
+
const assignmentError = validateManagedAgentAssignment({
|
|
530
|
+
resourceLabel: res.label,
|
|
531
|
+
agents,
|
|
532
|
+
assignScope,
|
|
533
|
+
currentAgentId: ctx?.agentId || null,
|
|
534
|
+
targetAgentId: requestedClear ? null : resolution.agentId,
|
|
535
|
+
unresolvedReference: requestedClear ? null : resolution.unresolvedReference,
|
|
536
|
+
isDelegation: toolKey === 'manage_tasks'
|
|
537
|
+
? isDelegationTaskPayload({
|
|
538
|
+
...all[id],
|
|
539
|
+
...parsed,
|
|
540
|
+
agentId: requestedClear ? null : resolution.agentId,
|
|
541
|
+
} as Record<string, unknown>)
|
|
542
|
+
: false,
|
|
543
|
+
delegatorAgentId: toolKey === 'manage_tasks'
|
|
544
|
+
? resolveDelegatorAgentId({
|
|
545
|
+
...all[id],
|
|
546
|
+
...parsed,
|
|
547
|
+
} as Record<string, unknown>, agents, ctx?.agentId || null)
|
|
548
|
+
: null,
|
|
549
|
+
})
|
|
550
|
+
if (assignmentError) return assignmentError
|
|
551
|
+
if (!requestedClear) parsed.agentId = resolution.agentId
|
|
506
552
|
}
|
|
507
553
|
}
|
|
508
554
|
all[id] = { ...all[id], ...parsed, updatedAt: Date.now() }
|
|
@@ -598,7 +644,7 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
598
644
|
)
|
|
599
645
|
}
|
|
600
646
|
|
|
601
|
-
if (
|
|
647
|
+
if (hasPlugin('manage_documents')) {
|
|
602
648
|
tools.push(
|
|
603
649
|
tool(
|
|
604
650
|
async (rawArgs) => {
|