@swarmclawai/swarmclaw 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -50
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +43 -0
- package/src/app/api/agents/[id]/thread/route.ts +39 -8
- package/src/app/api/agents/route.ts +35 -2
- package/src/app/api/auth/route.ts +77 -8
- package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +30 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +23 -1
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +12 -4
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +55 -17
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +16 -6
- package/src/app/api/tasks/bulk/route.ts +3 -3
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +135 -17
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +38 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +21 -12
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +456 -23
- package/src/components/agents/inspector-panel.tsx +110 -49
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +70 -27
- package/src/components/chat/chat-card.tsx +6 -21
- package/src/components/chat/chat-header.tsx +263 -366
- package/src/components/chat/chat-list.tsx +62 -26
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +145 -19
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +422 -209
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +385 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +189 -1
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +88 -6
- package/src/components/shared/settings/section-orchestrator.tsx +6 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +248 -47
- package/src/components/tasks/approvals-panel.tsx +211 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +264 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +44 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
- package/src/lib/server/chat-execution.ts +402 -125
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +74 -2
- package/src/lib/server/chatroom-helpers.ts +144 -11
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +994 -130
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +189 -10
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/daemon-state.ts +62 -3
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +23 -43
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +31 -964
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +6 -5
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +18 -8
- package/src/lib/server/orchestrator.ts +5 -4
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +215 -0
- package/src/lib/server/plugins.ts +832 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +4 -21
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +96 -34
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +40 -12
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +243 -24
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +87 -2
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +162 -12
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +95 -25
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +58 -4
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +195 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +947 -108
- package/src/lib/server/storage.ts +255 -10
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +185 -25
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -11
- package/src/lib/server/tool-aliases.ts +80 -12
- package/src/lib/server/tool-capability-policy.ts +7 -1
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +62 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +43 -7
- package/src/stores/use-chat-store.ts +31 -2
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +470 -44
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- package/src/lib/server/session-run-manager.test.ts +0 -26
|
@@ -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,13 @@ const DEFAULT_CONFIG: ExecApprovalConfig = {
|
|
|
7
7
|
patterns: [],
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
/** Fetch
|
|
11
|
-
export async function getExecConfig(agentId
|
|
10
|
+
/** Fetch the gateway's global exec approval config. */
|
|
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
|
|
|
15
|
-
const result = await gw.rpc('exec.approvals.get', {
|
|
16
|
+
const result = await gw.rpc('exec.approvals.get', {}) as ExecApprovalSnapshot | undefined
|
|
16
17
|
if (!result) {
|
|
17
18
|
return { path: '', exists: false, hash: '', file: { ...DEFAULT_CONFIG } }
|
|
18
19
|
}
|
|
@@ -25,6 +26,7 @@ export async function setExecConfig(
|
|
|
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
|
|
|
@@ -32,7 +34,6 @@ export async function setExecConfig(
|
|
|
32
34
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
33
35
|
try {
|
|
34
36
|
const result = await gw.rpc('exec.approvals.set', {
|
|
35
|
-
agentId,
|
|
36
37
|
file: config,
|
|
37
38
|
baseHash: currentHash,
|
|
38
39
|
}) as { hash?: string } | undefined
|
|
@@ -41,7 +42,7 @@ export async function setExecConfig(
|
|
|
41
42
|
const msg = err instanceof Error ? err.message : String(err)
|
|
42
43
|
if (msg.includes('conflict') && attempt < 2) {
|
|
43
44
|
// Re-fetch to get fresh hash
|
|
44
|
-
const fresh = await getExecConfig(
|
|
45
|
+
const fresh = await getExecConfig()
|
|
45
46
|
currentHash = fresh.hash
|
|
46
47
|
continue
|
|
47
48
|
}
|
|
@@ -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
|
}
|
|
@@ -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
|
+
})
|