@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
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
traverseLinkedMemoryGraph,
|
|
15
15
|
type MemoryLookupLimits,
|
|
16
16
|
} from './memory-graph'
|
|
17
|
+
import { isWorkingMemoryCategory } from './memory-tiers'
|
|
17
18
|
|
|
18
19
|
import { DATA_DIR } from './data-dir'
|
|
19
20
|
|
|
@@ -1203,8 +1204,7 @@ function initDb() {
|
|
|
1203
1204
|
if (seenCanonical.has(keyCanonical)) canonicalDuplicateCandidates++
|
|
1204
1205
|
else seenCanonical.add(keyCanonical)
|
|
1205
1206
|
|
|
1206
|
-
const
|
|
1207
|
-
const isWorkingLike = category === 'execution' || category === 'working' || category === 'scratch'
|
|
1207
|
+
const isWorkingLike = isWorkingMemoryCategory(row.category)
|
|
1208
1208
|
if (isWorkingLike && (row.updatedAt || row.createdAt || 0) < cutoff) staleWorkingCandidates++
|
|
1209
1209
|
}
|
|
1210
1210
|
|
|
@@ -1303,8 +1303,7 @@ function initDb() {
|
|
|
1303
1303
|
if (pruneWorking && toDelete.size < deleteBudget) {
|
|
1304
1304
|
for (const row of rows) {
|
|
1305
1305
|
if (toDelete.has(row.id)) continue
|
|
1306
|
-
const
|
|
1307
|
-
const isWorkingLike = category === 'execution' || category === 'working' || category === 'scratch'
|
|
1306
|
+
const isWorkingLike = isWorkingMemoryCategory(row.category)
|
|
1308
1307
|
const updatedAt = row.updatedAt || row.createdAt || 0
|
|
1309
1308
|
if (isWorkingLike && updatedAt < cutoff) toDelete.add(row.id)
|
|
1310
1309
|
if (toDelete.size >= deleteBudget) break
|
|
@@ -1323,8 +1322,7 @@ function initDb() {
|
|
|
1323
1322
|
const deletedSet = new Set(deleteIds)
|
|
1324
1323
|
for (const row of rows) {
|
|
1325
1324
|
if (!deletedSet.has(row.id)) continue
|
|
1326
|
-
const
|
|
1327
|
-
const isWorkingLike = category === 'execution' || category === 'working' || category === 'scratch'
|
|
1325
|
+
const isWorkingLike = isWorkingMemoryCategory(row.category)
|
|
1328
1326
|
if (isWorkingLike) pruned++
|
|
1329
1327
|
else deduped++
|
|
1330
1328
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { MemoryEntry } from '@/types'
|
|
2
|
+
|
|
3
|
+
export type MemoryTier = 'working' | 'durable' | 'archive'
|
|
4
|
+
|
|
5
|
+
const WORKING_CATEGORIES = new Set(['execution', 'working', 'scratch', 'breadcrumb'])
|
|
6
|
+
const ARCHIVE_CATEGORIES = new Set(['session_archive'])
|
|
7
|
+
|
|
8
|
+
export function getMemoryTierForCategory(category: unknown): MemoryTier {
|
|
9
|
+
const normalized = typeof category === 'string' ? category.trim().toLowerCase() : ''
|
|
10
|
+
if (ARCHIVE_CATEGORIES.has(normalized)) return 'archive'
|
|
11
|
+
if (WORKING_CATEGORIES.has(normalized)) return 'working'
|
|
12
|
+
return 'durable'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getMemoryTier(entry: Pick<MemoryEntry, 'category' | 'metadata'>): MemoryTier {
|
|
16
|
+
const metadataTier = typeof entry.metadata?.tier === 'string' ? entry.metadata.tier.trim().toLowerCase() : ''
|
|
17
|
+
if (metadataTier === 'archive' || metadataTier === 'session_archive') return 'archive'
|
|
18
|
+
if (metadataTier === 'working') return 'working'
|
|
19
|
+
if (metadataTier === 'durable') return 'durable'
|
|
20
|
+
return getMemoryTierForCategory(entry.category)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function partitionMemoriesByTier<T extends Pick<MemoryEntry, 'category' | 'metadata'>>(entries: T[]) {
|
|
24
|
+
const working: T[] = []
|
|
25
|
+
const durable: T[] = []
|
|
26
|
+
const archive: T[] = []
|
|
27
|
+
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
const tier = getMemoryTier(entry)
|
|
30
|
+
if (tier === 'working') working.push(entry)
|
|
31
|
+
else if (tier === 'archive') archive.push(entry)
|
|
32
|
+
else durable.push(entry)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { working, durable, archive }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isWorkingMemoryCategory(category: unknown): boolean {
|
|
39
|
+
return getMemoryTierForCategory(category) === 'working'
|
|
40
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import type { Agent } from '@/types'
|
|
4
|
+
import {
|
|
5
|
+
resolveOpenClawGatewayAgentIdFromList,
|
|
6
|
+
type OpenClawGatewayAgentSummary,
|
|
7
|
+
} from './openclaw-agent-resolver'
|
|
8
|
+
|
|
9
|
+
function makeOpenClawAgent(overrides: Partial<Agent> = {}): Agent {
|
|
10
|
+
const now = Date.now()
|
|
11
|
+
return {
|
|
12
|
+
id: 'f4535f26',
|
|
13
|
+
name: 'OpenClaw Ops',
|
|
14
|
+
description: '',
|
|
15
|
+
systemPrompt: '',
|
|
16
|
+
provider: 'openclaw',
|
|
17
|
+
model: 'openclaw-default',
|
|
18
|
+
createdAt: now,
|
|
19
|
+
updatedAt: now,
|
|
20
|
+
...overrides,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test('resolveOpenClawGatewayAgentIdFromList matches a local OpenClaw agent by normalized name', () => {
|
|
25
|
+
const gatewayAgents: OpenClawGatewayAgentSummary[] = [
|
|
26
|
+
{ id: 'main', name: 'Main' },
|
|
27
|
+
{ id: 'openclaw-ops', name: 'OpenClaw Ops' },
|
|
28
|
+
]
|
|
29
|
+
const resolved = resolveOpenClawGatewayAgentIdFromList({
|
|
30
|
+
agentRef: 'f4535f26',
|
|
31
|
+
gatewayAgents,
|
|
32
|
+
localAgent: makeOpenClawAgent(),
|
|
33
|
+
})
|
|
34
|
+
assert.equal(resolved, 'openclaw-ops')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('resolveOpenClawGatewayAgentIdFromList preserves direct gateway ids', () => {
|
|
38
|
+
const gatewayAgents: OpenClawGatewayAgentSummary[] = [
|
|
39
|
+
{ id: 'main', name: 'Main' },
|
|
40
|
+
]
|
|
41
|
+
const resolved = resolveOpenClawGatewayAgentIdFromList({
|
|
42
|
+
agentRef: 'main',
|
|
43
|
+
gatewayAgents,
|
|
44
|
+
})
|
|
45
|
+
assert.equal(resolved, 'main')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('resolveOpenClawGatewayAgentIdFromList can match identity names when display names differ', () => {
|
|
49
|
+
const gatewayAgents: OpenClawGatewayAgentSummary[] = [
|
|
50
|
+
{ id: 'research-ops', identity: { name: 'Research Ops' } },
|
|
51
|
+
]
|
|
52
|
+
const resolved = resolveOpenClawGatewayAgentIdFromList({
|
|
53
|
+
agentRef: 'agent-123',
|
|
54
|
+
gatewayAgents,
|
|
55
|
+
localAgent: makeOpenClawAgent({ id: 'agent-123', name: 'Research Ops' }),
|
|
56
|
+
})
|
|
57
|
+
assert.equal(resolved, 'research-ops')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('single-agent gateway can back a local OpenClaw provider agent without an explicit name match', async () => {
|
|
61
|
+
const gatewayAgents: OpenClawGatewayAgentSummary[] = [
|
|
62
|
+
{ id: 'main', name: 'Main' },
|
|
63
|
+
]
|
|
64
|
+
const resolved = resolveOpenClawGatewayAgentIdFromList({
|
|
65
|
+
agentRef: 'f4535f26',
|
|
66
|
+
gatewayAgents,
|
|
67
|
+
localAgent: makeOpenClawAgent({ name: 'OpenClaw-2' }),
|
|
68
|
+
})
|
|
69
|
+
assert.equal(resolved, 'main')
|
|
70
|
+
})
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { Agent } from '@/types'
|
|
2
|
+
import { normalizeOpenClawAgentId } from '@/lib/openclaw-agent-id'
|
|
3
|
+
import { ensureGatewayConnected, type OpenClawGateway } from './openclaw-gateway'
|
|
4
|
+
import { loadAgents } from './storage'
|
|
5
|
+
|
|
6
|
+
export interface OpenClawGatewayAgentSummary {
|
|
7
|
+
id: string
|
|
8
|
+
name?: string
|
|
9
|
+
identity?: {
|
|
10
|
+
name?: string
|
|
11
|
+
} | null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface OpenClawGatewayAgentsList {
|
|
15
|
+
defaultId?: string
|
|
16
|
+
agents?: OpenClawGatewayAgentSummary[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function addTextCandidate(target: Set<string>, value: string | undefined | null) {
|
|
20
|
+
const trimmed = (value ?? '').trim()
|
|
21
|
+
if (trimmed) {
|
|
22
|
+
target.add(trimmed.toLowerCase())
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function addNormalizedCandidate(target: Set<string>, value: string | undefined | null) {
|
|
27
|
+
const trimmed = (value ?? '').trim()
|
|
28
|
+
if (trimmed) {
|
|
29
|
+
target.add(normalizeOpenClawAgentId(trimmed))
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resolveOpenClawGatewayAgentIdFromList(params: {
|
|
34
|
+
agentRef: string
|
|
35
|
+
gatewayAgents: OpenClawGatewayAgentSummary[]
|
|
36
|
+
localAgent?: Agent | null
|
|
37
|
+
}): string | null {
|
|
38
|
+
const rawRef = params.agentRef.trim()
|
|
39
|
+
if (!rawRef) {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const exactTextCandidates = new Set<string>()
|
|
44
|
+
const normalizedCandidates = new Set<string>()
|
|
45
|
+
|
|
46
|
+
addTextCandidate(exactTextCandidates, rawRef)
|
|
47
|
+
addNormalizedCandidate(normalizedCandidates, rawRef)
|
|
48
|
+
|
|
49
|
+
if (params.localAgent) {
|
|
50
|
+
addTextCandidate(exactTextCandidates, params.localAgent.id)
|
|
51
|
+
addTextCandidate(exactTextCandidates, params.localAgent.name)
|
|
52
|
+
addNormalizedCandidate(normalizedCandidates, params.localAgent.id)
|
|
53
|
+
addNormalizedCandidate(normalizedCandidates, params.localAgent.name)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const gatewayAgent of params.gatewayAgents) {
|
|
57
|
+
if (exactTextCandidates.has(gatewayAgent.id.trim().toLowerCase())) {
|
|
58
|
+
return gatewayAgent.id
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const gatewayAgent of params.gatewayAgents) {
|
|
63
|
+
if (normalizedCandidates.has(normalizeOpenClawAgentId(gatewayAgent.id))) {
|
|
64
|
+
return gatewayAgent.id
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const gatewayAgent of params.gatewayAgents) {
|
|
69
|
+
const labels = [gatewayAgent.name, gatewayAgent.identity?.name]
|
|
70
|
+
for (const label of labels) {
|
|
71
|
+
if (!label?.trim()) continue
|
|
72
|
+
if (exactTextCandidates.has(label.trim().toLowerCase())) {
|
|
73
|
+
return gatewayAgent.id
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const gatewayAgent of params.gatewayAgents) {
|
|
79
|
+
const labels = [gatewayAgent.name, gatewayAgent.identity?.name]
|
|
80
|
+
for (const label of labels) {
|
|
81
|
+
if (!label?.trim()) continue
|
|
82
|
+
if (normalizedCandidates.has(normalizeOpenClawAgentId(label))) {
|
|
83
|
+
return gatewayAgent.id
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (params.localAgent && params.gatewayAgents.length === 1) {
|
|
89
|
+
return params.gatewayAgents[0].id
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function resolveOpenClawGatewayAgentId(
|
|
96
|
+
agentRef: string,
|
|
97
|
+
gatewayArg?: OpenClawGateway | null,
|
|
98
|
+
): Promise<string> {
|
|
99
|
+
const trimmedRef = agentRef.trim()
|
|
100
|
+
if (!trimmedRef) {
|
|
101
|
+
throw new Error('Missing agentId')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const localAgents = loadAgents({ includeTrashed: true }) as Record<string, Agent>
|
|
105
|
+
const localAgent = localAgents[trimmedRef] || null
|
|
106
|
+
if (localAgent && localAgent.provider !== 'openclaw') {
|
|
107
|
+
throw new Error(`Agent "${localAgent.name}" is not an OpenClaw agent`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const gateway = gatewayArg ?? await ensureGatewayConnected()
|
|
111
|
+
if (!gateway) {
|
|
112
|
+
throw new Error('OpenClaw gateway not connected')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const result = await gateway.rpc('agents.list', {}) as OpenClawGatewayAgentsList | undefined
|
|
116
|
+
const gatewayAgents = Array.isArray(result?.agents) ? result.agents : []
|
|
117
|
+
const resolved = resolveOpenClawGatewayAgentIdFromList({
|
|
118
|
+
agentRef: trimmedRef,
|
|
119
|
+
gatewayAgents,
|
|
120
|
+
localAgent,
|
|
121
|
+
})
|
|
122
|
+
if (resolved) {
|
|
123
|
+
return resolved
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const label = localAgent?.name?.trim() || trimmedRef
|
|
127
|
+
throw new Error(`OpenClaw gateway agent not found for "${label}"`)
|
|
128
|
+
}
|
|
@@ -7,12 +7,12 @@ const DEFAULT_CONFIG: ExecApprovalConfig = {
|
|
|
7
7
|
patterns: [],
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
/** Fetch
|
|
11
|
-
export async function getExecConfig(
|
|
10
|
+
/** Fetch the gateway's global exec approval config. */
|
|
11
|
+
export async function getExecConfig(_agentId?: string): Promise<ExecApprovalSnapshot> {
|
|
12
12
|
const gw = await ensureGatewayConnected()
|
|
13
13
|
if (!gw) throw new Error('Gateway not connected')
|
|
14
14
|
|
|
15
|
-
const result = await gw.rpc('exec.approvals.get', {
|
|
15
|
+
const result = await gw.rpc('exec.approvals.get', {}) as ExecApprovalSnapshot | undefined
|
|
16
16
|
if (!result) {
|
|
17
17
|
return { path: '', exists: false, hash: '', file: { ...DEFAULT_CONFIG } }
|
|
18
18
|
}
|
|
@@ -21,7 +21,7 @@ export async function getExecConfig(agentId: string): Promise<ExecApprovalSnapsh
|
|
|
21
21
|
|
|
22
22
|
/** Save exec approval config with hash-based conflict retry (up to 3 attempts) */
|
|
23
23
|
export async function setExecConfig(
|
|
24
|
-
|
|
24
|
+
_agentId: string,
|
|
25
25
|
config: ExecApprovalConfig,
|
|
26
26
|
baseHash: string,
|
|
27
27
|
): Promise<{ ok: boolean; hash: string }> {
|
|
@@ -32,7 +32,6 @@ export async function setExecConfig(
|
|
|
32
32
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
33
33
|
try {
|
|
34
34
|
const result = await gw.rpc('exec.approvals.set', {
|
|
35
|
-
agentId,
|
|
36
35
|
file: config,
|
|
37
36
|
baseHash: currentHash,
|
|
38
37
|
}) as { hash?: string } | undefined
|
|
@@ -41,7 +40,7 @@ export async function setExecConfig(
|
|
|
41
40
|
const msg = err instanceof Error ? err.message : String(err)
|
|
42
41
|
if (msg.includes('conflict') && attempt < 2) {
|
|
43
42
|
// Re-fetch to get fresh hash
|
|
44
|
-
const fresh = await getExecConfig(
|
|
43
|
+
const fresh = await getExecConfig()
|
|
45
44
|
currentHash = fresh.hash
|
|
46
45
|
continue
|
|
47
46
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import { normalizeOpenClawSkillsPayload } from './openclaw-skills-normalize'
|
|
4
|
+
|
|
5
|
+
test('normalizeOpenClawSkillsPayload maps gateway skill reports into UI entries', () => {
|
|
6
|
+
const normalized = normalizeOpenClawSkillsPayload({
|
|
7
|
+
workspaceDir: '/tmp/workspace',
|
|
8
|
+
skills: [
|
|
9
|
+
{
|
|
10
|
+
name: 'github',
|
|
11
|
+
description: 'GitHub operations',
|
|
12
|
+
source: 'openclaw-bundled',
|
|
13
|
+
eligible: true,
|
|
14
|
+
requirements: {
|
|
15
|
+
bins: ['gh'],
|
|
16
|
+
anyBins: [['git', 'jj']],
|
|
17
|
+
env: ['GH_TOKEN'],
|
|
18
|
+
},
|
|
19
|
+
missing: {
|
|
20
|
+
config: ['channels.github'],
|
|
21
|
+
},
|
|
22
|
+
install: [
|
|
23
|
+
{ kind: 'brew', label: 'Install GitHub CLI', bins: ['gh'] },
|
|
24
|
+
],
|
|
25
|
+
configChecks: [
|
|
26
|
+
{ path: 'channels.github', satisfied: false },
|
|
27
|
+
],
|
|
28
|
+
skillKey: 'github',
|
|
29
|
+
baseDir: '/tmp/github',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
assert.equal(normalized.length, 1)
|
|
35
|
+
assert.deepEqual(normalized[0], {
|
|
36
|
+
name: 'github',
|
|
37
|
+
description: 'GitHub operations',
|
|
38
|
+
source: 'bundled',
|
|
39
|
+
eligible: true,
|
|
40
|
+
missing: ['config channels.github'],
|
|
41
|
+
disabled: false,
|
|
42
|
+
installOptions: [
|
|
43
|
+
{ kind: 'brew', label: 'Install GitHub CLI', bins: ['gh'] },
|
|
44
|
+
],
|
|
45
|
+
skillRequirements: {
|
|
46
|
+
bins: ['gh'],
|
|
47
|
+
anyBins: [['git', 'jj']],
|
|
48
|
+
env: ['GH_TOKEN'],
|
|
49
|
+
config: undefined,
|
|
50
|
+
os: undefined,
|
|
51
|
+
},
|
|
52
|
+
configChecks: [{ key: 'channels.github', ok: false }],
|
|
53
|
+
skillKey: 'github',
|
|
54
|
+
baseDir: '/tmp/github',
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { OpenClawSkillEntry, SkillInstallOption, SkillRequirements } from '@/types'
|
|
2
|
+
|
|
3
|
+
interface GatewayConfigCheck {
|
|
4
|
+
path?: string
|
|
5
|
+
satisfied?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface GatewayInstallOption {
|
|
9
|
+
kind?: string
|
|
10
|
+
label?: string
|
|
11
|
+
bins?: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface GatewaySkillRequirements {
|
|
15
|
+
bins?: string[]
|
|
16
|
+
anyBins?: string[][]
|
|
17
|
+
env?: string[]
|
|
18
|
+
config?: string[]
|
|
19
|
+
os?: string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface GatewaySkillEntry {
|
|
23
|
+
name?: string
|
|
24
|
+
description?: string
|
|
25
|
+
source?: string
|
|
26
|
+
eligible?: boolean
|
|
27
|
+
requirements?: GatewaySkillRequirements
|
|
28
|
+
missing?: GatewaySkillRequirements
|
|
29
|
+
disabled?: boolean
|
|
30
|
+
install?: GatewayInstallOption[]
|
|
31
|
+
configChecks?: GatewayConfigCheck[]
|
|
32
|
+
skillKey?: string
|
|
33
|
+
baseDir?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface GatewaySkillsStatusPayload {
|
|
37
|
+
skills?: GatewaySkillEntry[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function uniq(values: Array<string | undefined | null>): string[] {
|
|
41
|
+
return [...new Set(values.map((value) => (value ?? '').trim()).filter(Boolean))]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeSource(source: string | undefined): OpenClawSkillEntry['source'] {
|
|
45
|
+
switch ((source ?? '').trim()) {
|
|
46
|
+
case 'openclaw-bundled':
|
|
47
|
+
case 'bundled':
|
|
48
|
+
return 'bundled'
|
|
49
|
+
case 'managed':
|
|
50
|
+
return 'managed'
|
|
51
|
+
case 'personal':
|
|
52
|
+
return 'personal'
|
|
53
|
+
case 'workspace':
|
|
54
|
+
return 'workspace'
|
|
55
|
+
default:
|
|
56
|
+
return 'workspace'
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeInstallOptions(install: GatewayInstallOption[] | undefined): SkillInstallOption[] | undefined {
|
|
61
|
+
if (!Array.isArray(install) || !install.length) return undefined
|
|
62
|
+
const normalized = install
|
|
63
|
+
.map((entry) => {
|
|
64
|
+
const kind = (entry.kind ?? '').trim()
|
|
65
|
+
if (!kind || !entry.label?.trim()) return null
|
|
66
|
+
if (!['brew', 'node', 'go', 'uv', 'download'].includes(kind)) return null
|
|
67
|
+
return {
|
|
68
|
+
kind: kind as SkillInstallOption['kind'],
|
|
69
|
+
label: entry.label.trim(),
|
|
70
|
+
bins: Array.isArray(entry.bins) ? uniq(entry.bins) : undefined,
|
|
71
|
+
} satisfies SkillInstallOption
|
|
72
|
+
})
|
|
73
|
+
.filter((value): value is NonNullable<typeof value> => value !== null)
|
|
74
|
+
return normalized.length ? normalized : undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeRequirements(input: GatewaySkillRequirements | undefined): SkillRequirements | undefined {
|
|
78
|
+
if (!input || typeof input !== 'object') return undefined
|
|
79
|
+
const bins = Array.isArray(input.bins) ? uniq(input.bins) : undefined
|
|
80
|
+
const anyBins = Array.isArray(input.anyBins)
|
|
81
|
+
? input.anyBins
|
|
82
|
+
.map((group) => Array.isArray(group) ? uniq(group) : [])
|
|
83
|
+
.filter((group) => group.length > 0)
|
|
84
|
+
: undefined
|
|
85
|
+
const env = Array.isArray(input.env) ? uniq(input.env) : undefined
|
|
86
|
+
const config = Array.isArray(input.config) ? uniq(input.config) : undefined
|
|
87
|
+
const os = Array.isArray(input.os) ? uniq(input.os) : undefined
|
|
88
|
+
if (!bins && !anyBins && !env && !config && !os) return undefined
|
|
89
|
+
return { bins, anyBins, env, config, os }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function flattenMissing(input: GatewaySkillRequirements | undefined): string[] | undefined {
|
|
93
|
+
if (!input || typeof input !== 'object') return undefined
|
|
94
|
+
const out: string[] = []
|
|
95
|
+
for (const value of Array.isArray(input.bins) ? uniq(input.bins) : []) out.push(value)
|
|
96
|
+
for (const group of Array.isArray(input.anyBins) ? input.anyBins : []) {
|
|
97
|
+
const normalized = Array.isArray(group) ? uniq(group) : []
|
|
98
|
+
if (normalized.length) out.push(`one of: ${normalized.join(' | ')}`)
|
|
99
|
+
}
|
|
100
|
+
for (const value of Array.isArray(input.env) ? uniq(input.env) : []) out.push(`env ${value}`)
|
|
101
|
+
for (const value of Array.isArray(input.config) ? uniq(input.config) : []) out.push(`config ${value}`)
|
|
102
|
+
for (const value of Array.isArray(input.os) ? uniq(input.os) : []) out.push(`os ${value}`)
|
|
103
|
+
return out.length ? out : undefined
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function normalizeOpenClawSkillsPayload(payload: unknown): OpenClawSkillEntry[] {
|
|
107
|
+
const rawSkills = Array.isArray(payload)
|
|
108
|
+
? payload as GatewaySkillEntry[]
|
|
109
|
+
: Array.isArray((payload as GatewaySkillsStatusPayload | null | undefined)?.skills)
|
|
110
|
+
? (payload as GatewaySkillsStatusPayload).skills!
|
|
111
|
+
: []
|
|
112
|
+
|
|
113
|
+
return rawSkills
|
|
114
|
+
.map((skill) => {
|
|
115
|
+
const name = skill.name?.trim()
|
|
116
|
+
if (!name) return null
|
|
117
|
+
return {
|
|
118
|
+
name,
|
|
119
|
+
description: skill.description?.trim() || undefined,
|
|
120
|
+
source: normalizeSource(skill.source),
|
|
121
|
+
eligible: skill.eligible === true,
|
|
122
|
+
missing: flattenMissing(skill.missing),
|
|
123
|
+
disabled: skill.disabled === true,
|
|
124
|
+
installOptions: normalizeInstallOptions(skill.install),
|
|
125
|
+
skillRequirements: normalizeRequirements(skill.requirements),
|
|
126
|
+
configChecks: Array.isArray(skill.configChecks)
|
|
127
|
+
? skill.configChecks
|
|
128
|
+
.filter((check) => check.path?.trim())
|
|
129
|
+
.map((check) => ({ key: check.path!.trim(), ok: check.satisfied === true }))
|
|
130
|
+
: undefined,
|
|
131
|
+
skillKey: skill.skillKey?.trim() || undefined,
|
|
132
|
+
baseDir: skill.baseDir?.trim() || undefined,
|
|
133
|
+
} satisfies OpenClawSkillEntry
|
|
134
|
+
})
|
|
135
|
+
.filter((skill): skill is NonNullable<typeof skill> => skill !== null)
|
|
136
|
+
}
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import crypto from 'node:crypto'
|
|
4
4
|
import { DATA_DIR } from './data-dir'
|
|
5
|
+
import { normalizeOpenClawAgentId } from '@/lib/openclaw-agent-id'
|
|
5
6
|
import { loadSettings, loadAgents, saveAgents, loadSchedules, saveSchedules, loadCredentials, decryptKey, encryptKey } from './storage'
|
|
6
7
|
import { getMemoryDb } from './memory-db'
|
|
7
8
|
import type { AppSettings, MemoryEntry, Schedule } from '@/types'
|
|
@@ -182,7 +183,7 @@ export function pushAgentToOpenClaw(agentId: string): { written: string[] } {
|
|
|
182
183
|
const agent = agents[agentId]
|
|
183
184
|
if (!agent) throw new Error(`Agent not found: ${agentId}`)
|
|
184
185
|
|
|
185
|
-
const agentDir = path.join(config.workspacePath, 'agents', agent.name
|
|
186
|
+
const agentDir = path.join(config.workspacePath, 'agents', normalizeOpenClawAgentId(agent.name))
|
|
186
187
|
ensureDir(agentDir)
|
|
187
188
|
|
|
188
189
|
const written: string[] = []
|
|
@@ -214,7 +215,7 @@ export function pullAgentFromOpenClaw(agentId: string): { updated: string[] } {
|
|
|
214
215
|
const agent = agents[agentId]
|
|
215
216
|
if (!agent) throw new Error(`Agent not found: ${agentId}`)
|
|
216
217
|
|
|
217
|
-
const agentDir = path.join(config.workspacePath, 'agents', agent.name
|
|
218
|
+
const agentDir = path.join(config.workspacePath, 'agents', normalizeOpenClawAgentId(agent.name))
|
|
218
219
|
const updated: string[] = []
|
|
219
220
|
|
|
220
221
|
const soulPath = path.join(agentDir, 'SOUL.md')
|
|
@@ -13,6 +13,7 @@ import { notify } from './ws-hub'
|
|
|
13
13
|
import { pushMainLoopEventToMainSessions } from './main-agent-loop'
|
|
14
14
|
import { buildCurrentDateTimePromptContext } from './prompt-runtime-context'
|
|
15
15
|
import { getPluginManager } from './plugins'
|
|
16
|
+
import './builtin-plugins'
|
|
16
17
|
import { genId } from '@/lib/id'
|
|
17
18
|
import { NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
18
19
|
import type { Agent, TaskComment, MessageToolEvent } from '@/types'
|
|
@@ -118,10 +119,10 @@ async function executeSubTaskViaCli(agent: Agent, task: string, parentSessionId:
|
|
|
118
119
|
messages: [],
|
|
119
120
|
createdAt: Date.now(),
|
|
120
121
|
lastActiveAt: Date.now(),
|
|
121
|
-
sessionType: '
|
|
122
|
+
sessionType: 'human' as const,
|
|
122
123
|
agentId: agent.id,
|
|
123
124
|
parentSessionId,
|
|
124
|
-
|
|
125
|
+
plugins: agent.plugins || agent.tools || [],
|
|
125
126
|
}
|
|
126
127
|
ss(sessions)
|
|
127
128
|
|
|
@@ -156,9 +157,9 @@ export async function executeLangGraphOrchestrator(
|
|
|
156
157
|
const agents = agentIds.map((id) => allAgents[id]).filter(Boolean) as Agent[]
|
|
157
158
|
const agentListContext = agents.length
|
|
158
159
|
? '\n\nAvailable agents:\n' + agents.map((a) => {
|
|
159
|
-
const
|
|
160
|
+
const plugins = (a.plugins || a.tools)?.length ? ` [plugins: ${(a.plugins || a.tools)!.join(', ')}]` : ''
|
|
160
161
|
const skills = a.skills?.length ? ` [skills: ${a.skills.join(', ')}]` : ''
|
|
161
|
-
return `- ${a.name}: ${a.description}${
|
|
162
|
+
return `- ${a.name}: ${a.description}${plugins}${skills}`
|
|
162
163
|
}).join('\n')
|
|
163
164
|
: '\n\n(No agents available for delegation.)'
|
|
164
165
|
|
|
@@ -177,7 +178,11 @@ export async function executeLangGraphOrchestrator(
|
|
|
177
178
|
return `Agent "${agentName}" not found. Available: ${agents.map((a) => a.name).join(', ')}`
|
|
178
179
|
}
|
|
179
180
|
console.log(`[orchestrator-lg] Delegating to ${agent.name}: ${agentTask.slice(0, 80)}`)
|
|
180
|
-
getPluginManager().runHook(
|
|
181
|
+
getPluginManager().runHook(
|
|
182
|
+
'onAgentDelegation',
|
|
183
|
+
{ sourceAgentId: orchestrator.id, targetAgentId: agent.id, task: agentTask },
|
|
184
|
+
{ enabledIds: orchestrator.plugins || [] },
|
|
185
|
+
)
|
|
181
186
|
const result = await executeSubTaskViaCli(agent, agentTask, sessionId)
|
|
182
187
|
saveMessage(sessionId, 'assistant', `Delegated to ${agent.name}: ${agentTask.slice(0, 100)}`, [{
|
|
183
188
|
name: 'delegate_to_agent',
|
|
@@ -393,6 +398,7 @@ export async function executeLangGraphOrchestrator(
|
|
|
393
398
|
|
|
394
399
|
const checkpointSaver = getCheckpointSaver()
|
|
395
400
|
const isStrictMode = settings.capabilityPolicyMode === 'strict'
|
|
401
|
+
const approvalInterruptsEnabled = isStrictMode && settings.approvalsEnabled !== false
|
|
396
402
|
const allTools = [delegateTool, storeMemoryTool, searchMemoryTool, getSecretTool, commentOnTaskTool, createTaskTool, markCompleteTool]
|
|
397
403
|
const llmWithTools = llm.bindTools(allTools)
|
|
398
404
|
const toolNode = new ToolNode(allTools)
|
|
@@ -472,7 +478,7 @@ export async function executeLangGraphOrchestrator(
|
|
|
472
478
|
|
|
473
479
|
const compiledGraph = graph.compile({
|
|
474
480
|
checkpointer: checkpointSaver,
|
|
475
|
-
...(
|
|
481
|
+
...(approvalInterruptsEnabled ? { interruptBefore: ['tools'] } : {}),
|
|
476
482
|
})
|
|
477
483
|
|
|
478
484
|
// Export graph structure for introspection
|
|
@@ -534,7 +540,7 @@ export async function executeLangGraphOrchestrator(
|
|
|
534
540
|
}
|
|
535
541
|
|
|
536
542
|
// Check for interrupt (paused before tool execution in strict mode)
|
|
537
|
-
if (
|
|
543
|
+
if (approvalInterruptsEnabled && taskId) {
|
|
538
544
|
const state = await compiledGraph.getState({ configurable: { thread_id: threadId } })
|
|
539
545
|
const nextNodes = state?.next || []
|
|
540
546
|
if (nextNodes.includes('tools')) {
|
|
@@ -628,7 +634,11 @@ export async function resumeLangGraphOrchestrator(
|
|
|
628
634
|
async ({ agentName, task: agentTask }) => {
|
|
629
635
|
const agent = agents.find((a) => a.name.toLowerCase() === agentName.toLowerCase())
|
|
630
636
|
if (!agent) return `Agent "${agentName}" not found. Available: ${agents.map((a) => a.name).join(', ')}`
|
|
631
|
-
getPluginManager().runHook(
|
|
637
|
+
getPluginManager().runHook(
|
|
638
|
+
'onAgentDelegation',
|
|
639
|
+
{ sourceAgentId: orchestrator.id, targetAgentId: agent.id, task: agentTask },
|
|
640
|
+
{ enabledIds: orchestrator.plugins || [] },
|
|
641
|
+
)
|
|
632
642
|
const result = await executeSubTaskViaCli(agent, agentTask, sessionId)
|
|
633
643
|
saveMessage(sessionId, 'assistant', `Delegated to ${agent.name}: ${agentTask.slice(0, 100)}`, [{
|
|
634
644
|
name: 'delegate_to_agent',
|
|
@@ -753,6 +763,7 @@ export async function resumeLangGraphOrchestrator(
|
|
|
753
763
|
const checkpointSaver = getCheckpointSaver()
|
|
754
764
|
const settings = loadSettings()
|
|
755
765
|
const isStrictMode = settings.capabilityPolicyMode === 'strict'
|
|
766
|
+
const approvalInterruptsEnabled = isStrictMode && settings.approvalsEnabled !== false
|
|
756
767
|
|
|
757
768
|
const allTools = [delegateTool, storeMemoryTool, searchMemoryTool, getSecretTool, commentOnTaskTool, createTaskTool, markCompleteTool]
|
|
758
769
|
const llmWithTools = llm.bindTools(allTools)
|
|
@@ -782,7 +793,7 @@ export async function resumeLangGraphOrchestrator(
|
|
|
782
793
|
.addEdge('router', 'agent')
|
|
783
794
|
.compile({
|
|
784
795
|
checkpointer: checkpointSaver,
|
|
785
|
-
...(
|
|
796
|
+
...(approvalInterruptsEnabled ? { interruptBefore: ['tools'] } : {}),
|
|
786
797
|
})
|
|
787
798
|
|
|
788
799
|
let finalResult = ''
|