@swarmclawai/swarmclaw 0.7.3 → 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 +47 -40
- 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 +17 -0
- package/src/app/api/agents/[id]/thread/route.ts +3 -1
- package/src/app/api/agents/route.ts +23 -1
- package/src/app/api/auth/route.ts +1 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +16 -5
- 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]/route.ts +12 -0
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +7 -1
- 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/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/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +1 -1
- 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 +6 -10
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +2 -1
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/page.tsx +126 -15
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +34 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +20 -4
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-sheet.tsx +249 -7
- package/src/components/agents/inspector-panel.tsx +3 -2
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +41 -14
- package/src/components/chat/chat-card.tsx +2 -1
- package/src/components/chat/chat-header.tsx +8 -13
- package/src/components/chat/chat-list.tsx +58 -20
- package/src/components/chat/message-list.tsx +142 -18
- 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 +157 -86
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +2 -0
- 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/projects/project-detail.tsx +7 -2
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/section-heartbeat.tsx +11 -6
- package/src/components/shared/settings/section-orchestrator.tsx +3 -0
- package/src/components/shared/settings/settings-page.tsx +5 -3
- package/src/components/tasks/approvals-panel.tsx +7 -1
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -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-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approvals-auto-approve.test.ts +59 -0
- package/src/lib/server/build-llm.test.ts +13 -5
- package/src/lib/server/chat-execution-tool-events.test.ts +87 -2
- package/src/lib/server/chat-execution.ts +159 -71
- package/src/lib/server/chatroom-helpers.test.ts +7 -0
- package/src/lib/server/chatroom-helpers.ts +99 -6
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/manager.ts +89 -61
- package/src/lib/server/connectors/slack.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -2
- 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 +10 -4
- package/src/lib/server/main-agent-loop.ts +13 -6
- package/src/lib/server/openclaw-exec-config.ts +4 -2
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/orchestrator-lg.ts +1 -2
- package/src/lib/server/orchestrator.ts +3 -2
- package/src/lib/server/plugins.test.ts +9 -1
- package/src/lib/server/plugins.ts +12 -2
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +1 -1
- 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/session-tools/autonomy-tools.test.ts +23 -0
- package/src/lib/server/session-tools/crud.ts +27 -3
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +18 -8
- package/src/lib/server/session-tools/file-normalize.test.ts +5 -0
- package/src/lib/server/session-tools/file.ts +8 -2
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/index.ts +31 -1
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/monitor.ts +14 -7
- 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.ts +1 -1
- package/src/lib/server/session-tools/plugin-creator.ts +9 -2
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/session-info.ts +22 -1
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +23 -0
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +3 -1
- package/src/lib/server/session-tools/web.ts +73 -30
- package/src/lib/server/storage.ts +29 -3
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +139 -4
- package/src/lib/server/structured-extract.ts +1 -1
- package/src/lib/server/task-mention.ts +0 -1
- package/src/lib/server/tool-aliases.ts +37 -6
- package/src/lib/server/tool-capability-policy.ts +1 -1
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.ts +55 -1
- package/src/stores/use-app-store.ts +43 -1
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +189 -6
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -13
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
1
3
|
import { genId } from '@/lib/id'
|
|
2
4
|
import type { EvalScenario, EvalRun, EvalSuiteResult } from './types'
|
|
3
5
|
import { getScenario, EVAL_SCENARIOS } from './scenarios'
|
|
@@ -5,8 +7,15 @@ import { scoreCriteria } from './scorer'
|
|
|
5
7
|
import { saveEvalRun } from './store'
|
|
6
8
|
import { loadSessions, saveSessions, loadAgents, loadCredentials, decryptKey } from '../storage'
|
|
7
9
|
import { executeSessionChatTurn } from '../chat-execution'
|
|
10
|
+
import { WORKSPACE_DIR } from '../data-dir'
|
|
8
11
|
import type { Session } from '@/types'
|
|
9
12
|
|
|
13
|
+
export function resolveEvalSessionCwd(runId: string): string {
|
|
14
|
+
const dir = path.join(WORKSPACE_DIR, 'evals', runId)
|
|
15
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
16
|
+
return dir
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
export async function runEvalScenario(scenarioId: string, agentId: string): Promise<EvalRun> {
|
|
11
20
|
const scenario = getScenario(scenarioId)
|
|
12
21
|
if (!scenario) throw new Error(`Unknown eval scenario: ${scenarioId}`)
|
|
@@ -18,6 +27,7 @@ export async function runEvalScenario(scenarioId: string, agentId: string): Prom
|
|
|
18
27
|
const runId = genId()
|
|
19
28
|
const sessionId = `eval-${runId}`
|
|
20
29
|
const now = Date.now()
|
|
30
|
+
const sessionCwd = resolveEvalSessionCwd(runId)
|
|
21
31
|
|
|
22
32
|
const run: EvalRun = {
|
|
23
33
|
id: runId,
|
|
@@ -36,7 +46,7 @@ export async function runEvalScenario(scenarioId: string, agentId: string): Prom
|
|
|
36
46
|
const evalSession: Session = {
|
|
37
47
|
id: sessionId,
|
|
38
48
|
name: `Eval: ${scenario.name}`,
|
|
39
|
-
cwd:
|
|
49
|
+
cwd: sessionCwd,
|
|
40
50
|
user: 'eval-runner',
|
|
41
51
|
provider: (agent.provider as Session['provider']) ?? 'anthropic',
|
|
42
52
|
model: (agent.model as string) ?? '',
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import Database from 'better-sqlite3'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import type { EvalRun } from './types'
|
|
4
|
+
import { DATA_DIR } from '../data-dir'
|
|
4
5
|
|
|
5
|
-
const DB_PATH = path.join(
|
|
6
|
+
const DB_PATH = path.join(DATA_DIR, 'eval-runs.db')
|
|
6
7
|
|
|
7
8
|
let db: Database.Database | null = null
|
|
8
9
|
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_HEARTBEAT_ACK_MAX_CHARS,
|
|
5
|
+
DEFAULT_HEARTBEAT_INTERVAL_SEC,
|
|
6
|
+
DEFAULT_HEARTBEAT_SHOW_ALERTS,
|
|
7
|
+
DEFAULT_HEARTBEAT_SHOW_OK,
|
|
8
|
+
} from '@/lib/heartbeat-defaults'
|
|
3
9
|
import { loadAgents, loadSessions, loadSettings } from './storage'
|
|
4
10
|
import { enqueueSessionRun, getSessionRunState } from './session-run-manager'
|
|
5
11
|
import { log } from './logger'
|
|
@@ -275,7 +281,7 @@ function resolveNum(obj: Record<string, any>, key: string, current: number): num
|
|
|
275
281
|
|
|
276
282
|
function heartbeatConfigForSession(session: any, settings: Record<string, any>, agents: Record<string, any>): HeartbeatConfig {
|
|
277
283
|
// Global defaults — 30 min interval (was 120s)
|
|
278
|
-
let intervalSec = resolveInterval(settings,
|
|
284
|
+
let intervalSec = resolveInterval(settings, DEFAULT_HEARTBEAT_INTERVAL_SEC)
|
|
279
285
|
const globalPrompt = (typeof settings.heartbeatPrompt === 'string' && settings.heartbeatPrompt.trim())
|
|
280
286
|
? settings.heartbeatPrompt.trim()
|
|
281
287
|
: DEFAULT_HEARTBEAT_PROMPT
|
|
@@ -283,9 +289,9 @@ function heartbeatConfigForSession(session: any, settings: Record<string, any>,
|
|
|
283
289
|
let enabled = intervalSec > 0
|
|
284
290
|
let prompt = globalPrompt
|
|
285
291
|
let model: string | null = resolveStr(settings, 'heartbeatModel', null)
|
|
286
|
-
let ackMaxChars = resolveNum(settings, 'heartbeatAckMaxChars',
|
|
287
|
-
let showOk = resolveBool(settings, 'heartbeatShowOk',
|
|
288
|
-
let showAlerts = resolveBool(settings, 'heartbeatShowAlerts',
|
|
292
|
+
let ackMaxChars = resolveNum(settings, 'heartbeatAckMaxChars', DEFAULT_HEARTBEAT_ACK_MAX_CHARS)
|
|
293
|
+
let showOk = resolveBool(settings, 'heartbeatShowOk', DEFAULT_HEARTBEAT_SHOW_OK)
|
|
294
|
+
let showAlerts = resolveBool(settings, 'heartbeatShowAlerts', DEFAULT_HEARTBEAT_SHOW_ALERTS)
|
|
289
295
|
let target: string | null = resolveStr(settings, 'heartbeatTarget', null)
|
|
290
296
|
|
|
291
297
|
// Agent layer overrides
|
|
@@ -66,11 +66,13 @@ export interface HandleMainLoopRunResultInput {
|
|
|
66
66
|
estimatedCost?: number
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
export function isMainSession(
|
|
69
|
+
export function isMainSession(session: unknown): boolean {
|
|
70
|
+
void session
|
|
70
71
|
return false
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
export function buildMainLoopHeartbeatPrompt(
|
|
74
|
+
export function buildMainLoopHeartbeatPrompt(session: unknown, fallbackPrompt: string): string {
|
|
75
|
+
void session
|
|
74
76
|
return fallbackPrompt
|
|
75
77
|
}
|
|
76
78
|
|
|
@@ -82,18 +84,23 @@ export function stripMainLoopMetaForPersistence(text: string): string {
|
|
|
82
84
|
.trim()
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
export function getMainLoopStateForSession(
|
|
87
|
+
export function getMainLoopStateForSession(sessionId: string): MainLoopState | null {
|
|
88
|
+
void sessionId
|
|
86
89
|
return null
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
export function setMainLoopStateForSession(
|
|
92
|
+
export function setMainLoopStateForSession(sessionId: string, patch: Partial<MainLoopState>): MainLoopState | null {
|
|
93
|
+
void sessionId
|
|
94
|
+
void patch
|
|
90
95
|
return null
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
export function pushMainLoopEventToMainSessions(
|
|
98
|
+
export function pushMainLoopEventToMainSessions(input: PushMainLoopEventInput): number {
|
|
99
|
+
void input
|
|
94
100
|
return 0
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
export function handleMainLoopRunResult(
|
|
103
|
+
export function handleMainLoopRunResult(input: HandleMainLoopRunResultInput): MainLoopFollowupRequest | null {
|
|
104
|
+
void input
|
|
98
105
|
return null
|
|
99
106
|
}
|
|
@@ -8,7 +8,8 @@ const DEFAULT_CONFIG: ExecApprovalConfig = {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/** Fetch the gateway's global exec approval config. */
|
|
11
|
-
export async function getExecConfig(
|
|
11
|
+
export async function getExecConfig(agentId?: string): Promise<ExecApprovalSnapshot> {
|
|
12
|
+
void agentId
|
|
12
13
|
const gw = await ensureGatewayConnected()
|
|
13
14
|
if (!gw) throw new Error('Gateway not connected')
|
|
14
15
|
|
|
@@ -21,10 +22,11 @@ export async function getExecConfig(_agentId?: string): Promise<ExecApprovalSnap
|
|
|
21
22
|
|
|
22
23
|
/** Save exec approval config with hash-based conflict retry (up to 3 attempts) */
|
|
23
24
|
export async function setExecConfig(
|
|
24
|
-
|
|
25
|
+
agentId: string,
|
|
25
26
|
config: ExecApprovalConfig,
|
|
26
27
|
baseHash: string,
|
|
27
28
|
): Promise<{ ok: boolean; hash: string }> {
|
|
29
|
+
void agentId
|
|
28
30
|
const gw = await ensureGatewayConnected()
|
|
29
31
|
if (!gw) throw new Error('Gateway not connected')
|
|
30
32
|
|
|
@@ -3,6 +3,7 @@ import { randomUUID } from 'crypto'
|
|
|
3
3
|
import { wsConnect, buildOpenClawConnectParams } from '../providers/openclaw'
|
|
4
4
|
import { loadAgents, loadCredentials, decryptKey } from './storage'
|
|
5
5
|
import { notify, notifyWithPayload } from './ws-hub'
|
|
6
|
+
import { getGatewayProfile, getGatewayProfiles, resolvePrimaryAgentRoute } from './agent-runtime-config'
|
|
6
7
|
|
|
7
8
|
// --- Types ---
|
|
8
9
|
|
|
@@ -19,19 +20,22 @@ type EventHandler = (payload: unknown) => void
|
|
|
19
20
|
const GK = '__swarmclaw_ocgateway__' as const
|
|
20
21
|
|
|
21
22
|
interface GatewayState {
|
|
22
|
-
|
|
23
|
+
instances: Map<string, OpenClawGateway>
|
|
24
|
+
activeKey: string | null
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
function getState(): GatewayState {
|
|
26
28
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
29
|
const g = globalThis as any
|
|
28
|
-
if (!g[GK]) g[GK] = {
|
|
30
|
+
if (!g[GK]) g[GK] = { instances: new Map<string, OpenClawGateway>(), activeKey: null }
|
|
29
31
|
return g[GK] as GatewayState
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
// --- Helper: resolve gateway config from first OpenClaw agent ---
|
|
33
35
|
|
|
34
36
|
interface GatewayConfig {
|
|
37
|
+
key: string
|
|
38
|
+
profileId?: string | null
|
|
35
39
|
wsUrl: string
|
|
36
40
|
token: string | undefined
|
|
37
41
|
}
|
|
@@ -43,27 +47,79 @@ function normalizeWsUrl(raw: string): string {
|
|
|
43
47
|
return url.replace(/^http:/i, 'ws:').replace(/^https:/i, 'wss:')
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
const
|
|
50
|
+
function resolveTokenForCredential(credentialId?: string | null): string | undefined {
|
|
51
|
+
const id = typeof credentialId === 'string' && credentialId.trim() ? credentialId.trim() : ''
|
|
52
|
+
if (!id) return undefined
|
|
48
53
|
const creds = loadCredentials()
|
|
54
|
+
const cred = creds[id]
|
|
55
|
+
if (!cred?.encryptedKey) return undefined
|
|
56
|
+
try {
|
|
57
|
+
return decryptKey(cred.encryptedKey)
|
|
58
|
+
} catch {
|
|
59
|
+
return undefined
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function resolveGatewayConfig(target?: {
|
|
64
|
+
profileId?: string | null
|
|
65
|
+
agentId?: string | null
|
|
66
|
+
}): GatewayConfig | null {
|
|
67
|
+
const profileId = typeof target?.profileId === 'string' ? target.profileId.trim() : ''
|
|
68
|
+
if (profileId) {
|
|
69
|
+
const profile = getGatewayProfile(profileId)
|
|
70
|
+
if (!profile) return null
|
|
71
|
+
return {
|
|
72
|
+
key: `profile:${profile.id}`,
|
|
73
|
+
profileId: profile.id,
|
|
74
|
+
wsUrl: profile.wsUrl ? normalizeWsUrl(profile.wsUrl) : normalizeWsUrl(profile.endpoint),
|
|
75
|
+
token: resolveTokenForCredential(profile.credentialId),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const agentId = typeof target?.agentId === 'string' ? target.agentId.trim() : ''
|
|
80
|
+
if (agentId) {
|
|
81
|
+
const agents = loadAgents({ includeTrashed: true })
|
|
82
|
+
const agent = agents[agentId]
|
|
83
|
+
const route = resolvePrimaryAgentRoute(agent)
|
|
84
|
+
if (route?.provider === 'openclaw') {
|
|
85
|
+
return {
|
|
86
|
+
key: route.gatewayProfileId ? `profile:${route.gatewayProfileId}` : `agent:${agentId}`,
|
|
87
|
+
profileId: route.gatewayProfileId ?? null,
|
|
88
|
+
wsUrl: normalizeWsUrl(route.apiEndpoint || 'ws://127.0.0.1:18789'),
|
|
89
|
+
token: resolveTokenForCredential(route.credentialId),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const gatewayProfiles = getGatewayProfiles('openclaw')
|
|
95
|
+
if (gatewayProfiles[0]) {
|
|
96
|
+
const profile = gatewayProfiles[0]
|
|
97
|
+
return {
|
|
98
|
+
key: `profile:${profile.id}`,
|
|
99
|
+
profileId: profile.id,
|
|
100
|
+
wsUrl: profile.wsUrl ? normalizeWsUrl(profile.wsUrl) : normalizeWsUrl(profile.endpoint),
|
|
101
|
+
token: resolveTokenForCredential(profile.credentialId),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const agents = loadAgents({ includeTrashed: true })
|
|
49
106
|
for (const agent of Object.values(agents)) {
|
|
50
107
|
if (agent?.provider !== 'openclaw') continue
|
|
51
108
|
const wsUrl = agent.apiEndpoint
|
|
52
109
|
? normalizeWsUrl(agent.apiEndpoint)
|
|
53
110
|
: 'ws://127.0.0.1:18789'
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
111
|
+
return {
|
|
112
|
+
key: `agent:${agent.id}`,
|
|
113
|
+
profileId: agent.gatewayProfileId ?? null,
|
|
114
|
+
wsUrl,
|
|
115
|
+
token: resolveTokenForCredential(agent.credentialId),
|
|
60
116
|
}
|
|
61
|
-
return { wsUrl, token }
|
|
62
117
|
}
|
|
63
118
|
return null
|
|
64
119
|
}
|
|
65
120
|
|
|
66
121
|
export function hasOpenClawAgents(): boolean {
|
|
122
|
+
if (getGatewayProfiles('openclaw').length > 0) return true
|
|
67
123
|
const agents = loadAgents({ includeTrashed: true })
|
|
68
124
|
return Object.values(agents).some((a) => a?.provider === 'openclaw' && !a.trashedAt)
|
|
69
125
|
}
|
|
@@ -253,47 +309,78 @@ export class OpenClawGateway {
|
|
|
253
309
|
|
|
254
310
|
// --- Singleton access ---
|
|
255
311
|
|
|
256
|
-
export function getGateway(): OpenClawGateway | null {
|
|
257
|
-
|
|
312
|
+
export function getGateway(profileId?: string | null): OpenClawGateway | null {
|
|
313
|
+
const state = getState()
|
|
314
|
+
const key = typeof profileId === 'string' && profileId.trim() ? `profile:${profileId.trim()}` : null
|
|
315
|
+
if (key) {
|
|
316
|
+
return state.instances.get(key) || null
|
|
317
|
+
}
|
|
318
|
+
if (state.activeKey) {
|
|
319
|
+
return state.instances.get(state.activeKey) || null
|
|
320
|
+
}
|
|
321
|
+
for (const instance of state.instances.values()) {
|
|
322
|
+
if (instance.connected) return instance
|
|
323
|
+
}
|
|
324
|
+
return null
|
|
258
325
|
}
|
|
259
326
|
|
|
260
|
-
export async function ensureGatewayConnected(
|
|
327
|
+
export async function ensureGatewayConnected(target?: {
|
|
328
|
+
profileId?: string | null
|
|
329
|
+
agentId?: string | null
|
|
330
|
+
}): Promise<OpenClawGateway | null> {
|
|
261
331
|
const state = getState()
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const config = resolveGatewayConfig()
|
|
332
|
+
const config = resolveGatewayConfig(target)
|
|
265
333
|
if (!config) return null
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
state.
|
|
334
|
+
const existing = state.instances.get(config.key)
|
|
335
|
+
if (existing?.connected) {
|
|
336
|
+
state.activeKey = config.key
|
|
337
|
+
return existing
|
|
269
338
|
}
|
|
270
339
|
|
|
271
|
-
const
|
|
272
|
-
|
|
340
|
+
const instance = existing || new OpenClawGateway()
|
|
341
|
+
state.instances.set(config.key, instance)
|
|
342
|
+
const ok = await instance.connect(config.wsUrl, config.token)
|
|
343
|
+
if (ok) {
|
|
344
|
+
state.activeKey = config.key
|
|
345
|
+
return instance
|
|
346
|
+
}
|
|
347
|
+
return null
|
|
273
348
|
}
|
|
274
349
|
|
|
275
|
-
export function disconnectGateway() {
|
|
350
|
+
export function disconnectGateway(profileId?: string | null) {
|
|
276
351
|
const state = getState()
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
352
|
+
const key = typeof profileId === 'string' && profileId.trim() ? `profile:${profileId.trim()}` : null
|
|
353
|
+
if (key) {
|
|
354
|
+
const instance = state.instances.get(key)
|
|
355
|
+
if (instance) {
|
|
356
|
+
instance.disconnect()
|
|
357
|
+
state.instances.delete(key)
|
|
358
|
+
if (state.activeKey === key) state.activeKey = null
|
|
359
|
+
}
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
for (const [instanceKey, instance] of state.instances.entries()) {
|
|
363
|
+
instance.disconnect()
|
|
364
|
+
state.instances.delete(instanceKey)
|
|
280
365
|
}
|
|
366
|
+
state.activeKey = null
|
|
281
367
|
}
|
|
282
368
|
|
|
283
369
|
/** Manual connect with explicit URL/token (used by gateway connection panel) */
|
|
284
|
-
export async function manualConnect(url?: string, token?: string): Promise<boolean> {
|
|
370
|
+
export async function manualConnect(url?: string, token?: string, profileId?: string | null): Promise<boolean> {
|
|
285
371
|
const state = getState()
|
|
286
|
-
|
|
287
|
-
|
|
372
|
+
const config = resolveGatewayConfig({ profileId: profileId || null })
|
|
373
|
+
const key = profileId ? `profile:${profileId}` : '__manual__'
|
|
374
|
+
const instance = state.instances.get(key) || new OpenClawGateway()
|
|
375
|
+
if (instance.connected) {
|
|
376
|
+
instance.disconnect()
|
|
288
377
|
}
|
|
289
|
-
|
|
290
|
-
const config = resolveGatewayConfig()
|
|
378
|
+
state.instances.set(key, instance)
|
|
291
379
|
const wsUrl = url ? normalizeWsUrl(url) : config?.wsUrl ?? 'ws://127.0.0.1:18789'
|
|
292
380
|
const resolvedToken = token ?? config?.token
|
|
293
|
-
|
|
294
|
-
if (
|
|
295
|
-
state.
|
|
381
|
+
const ok = await instance.connect(wsUrl, resolvedToken)
|
|
382
|
+
if (ok) {
|
|
383
|
+
state.activeKey = key
|
|
296
384
|
}
|
|
297
|
-
|
|
298
|
-
return state.instance.connect(wsUrl, resolvedToken)
|
|
385
|
+
return ok
|
|
299
386
|
}
|
|
@@ -612,8 +612,7 @@ export async function executeLangGraphOrchestrator(
|
|
|
612
612
|
const completeMatch = finalResult.match(/ORCHESTRATION_COMPLETE:\s*([\s\S]+)/)
|
|
613
613
|
const summary = completeMatch ? completeMatch[1].trim() : finalResult
|
|
614
614
|
|
|
615
|
-
|
|
616
|
-
return `${summary}\n\n[MAIN_LOOP_META] {"status":"ok", "follow_up":false, "summary":${JSON.stringify(summary.slice(0, 300))}, "mission_task_id":${JSON.stringify(taskId || null)}}`
|
|
615
|
+
return summary
|
|
617
616
|
}
|
|
618
617
|
|
|
619
618
|
/**
|
|
@@ -86,6 +86,7 @@ async function executeOrchestratorLegacy(
|
|
|
86
86
|
sessionId: string,
|
|
87
87
|
taskId?: string,
|
|
88
88
|
): Promise<string> {
|
|
89
|
+
void taskId
|
|
89
90
|
const allAgents = loadAgents()
|
|
90
91
|
const sessions = loadSessions()
|
|
91
92
|
const session = sessions[sessionId]
|
|
@@ -241,7 +242,7 @@ async function executeOrchestratorLegacy(
|
|
|
241
242
|
|
|
242
243
|
if (cmd.done) {
|
|
243
244
|
result = cmd.summary || fullText
|
|
244
|
-
return
|
|
245
|
+
return result
|
|
245
246
|
}
|
|
246
247
|
}
|
|
247
248
|
|
|
@@ -256,7 +257,7 @@ async function executeOrchestratorLegacy(
|
|
|
256
257
|
result = `Loop stopped after reaching max turns (${maxTurns}).`
|
|
257
258
|
}
|
|
258
259
|
|
|
259
|
-
return
|
|
260
|
+
return result
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
async function executeSubTask(
|
|
@@ -3,7 +3,7 @@ import { describe, it } from 'node:test'
|
|
|
3
3
|
import fs from 'node:fs'
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { getPluginManager, normalizeMarketplacePluginUrl, sanitizePluginFilename } from './plugins'
|
|
6
|
-
import { canonicalizePluginId, expandPluginIds } from './tool-aliases'
|
|
6
|
+
import { canonicalizePluginId, expandPluginIds, pluginIdMatches } from './tool-aliases'
|
|
7
7
|
import { DATA_DIR } from './data-dir'
|
|
8
8
|
|
|
9
9
|
let testPluginSeq = 0
|
|
@@ -33,6 +33,14 @@ describe('plugin id canonicalization', () => {
|
|
|
33
33
|
assert.equal(expanded.includes('ask_human'), true)
|
|
34
34
|
assert.equal(expanded.includes('human_loop'), true)
|
|
35
35
|
})
|
|
36
|
+
|
|
37
|
+
it('does not expand a specific platform tool back into manage_platform', () => {
|
|
38
|
+
const expanded = expandPluginIds(['manage_schedules'])
|
|
39
|
+
assert.equal(expanded.includes('manage_schedules'), true)
|
|
40
|
+
assert.equal(expanded.includes('manage_platform'), false)
|
|
41
|
+
assert.equal(pluginIdMatches(['manage_platform'], 'manage_schedules'), true)
|
|
42
|
+
assert.equal(pluginIdMatches(['manage_schedules'], 'manage_platform'), false)
|
|
43
|
+
})
|
|
36
44
|
})
|
|
37
45
|
|
|
38
46
|
describe('plugin install helpers', () => {
|
|
@@ -510,12 +510,22 @@ class PluginManager {
|
|
|
510
510
|
if (this.watcher) return
|
|
511
511
|
try {
|
|
512
512
|
this.ensurePluginDirs()
|
|
513
|
-
|
|
513
|
+
const watcher = fs.watch(PLUGINS_DIR, (_eventType, filename) => {
|
|
514
514
|
if (!filename || (!filename.endsWith('.js') && !filename.endsWith('.mjs'))) return
|
|
515
515
|
this.loaded = false
|
|
516
516
|
notify('plugins')
|
|
517
517
|
})
|
|
518
|
-
|
|
518
|
+
watcher.on('error', (err: unknown) => {
|
|
519
|
+
log.warn('plugins', 'Plugin watcher disabled after runtime watch failure', {
|
|
520
|
+
error: err instanceof Error ? err.message : String(err),
|
|
521
|
+
})
|
|
522
|
+
if (this.watcher === watcher) {
|
|
523
|
+
try { watcher.close() } catch { /* ignore */ }
|
|
524
|
+
this.watcher = null
|
|
525
|
+
}
|
|
526
|
+
})
|
|
527
|
+
watcher.unref?.()
|
|
528
|
+
this.watcher = watcher
|
|
519
529
|
} catch (err: unknown) {
|
|
520
530
|
log.warn('plugins', 'Failed to watch plugins directory', {
|
|
521
531
|
error: err instanceof Error ? err.message : String(err),
|