@swarmclawai/swarmclaw 0.2.0
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 +577 -0
- package/bin/server-cmd.js +359 -0
- package/bin/swarmclaw.js +29 -0
- package/bin/swarmclaw.mjs +1504 -0
- package/next.config.ts +33 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/branding/swarmclaw-org-avatar.png +0 -0
- package/public/branding/swarmclaw-org-avatar.svg +58 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/connectors.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/new-session-openclaw.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/schedules.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agents/[id]/route.ts +30 -0
- package/src/app/api/agents/[id]/thread/route.ts +66 -0
- package/src/app/api/agents/generate/route.ts +42 -0
- package/src/app/api/agents/route.ts +33 -0
- package/src/app/api/auth/route.ts +25 -0
- package/src/app/api/claude-skills/route.ts +42 -0
- package/src/app/api/clawhub/install/route.ts +39 -0
- package/src/app/api/clawhub/search/route.ts +11 -0
- package/src/app/api/connectors/[id]/route.ts +79 -0
- package/src/app/api/connectors/route.ts +60 -0
- package/src/app/api/credentials/[id]/route.ts +14 -0
- package/src/app/api/credentials/route.ts +31 -0
- package/src/app/api/daemon/health-check/route.ts +11 -0
- package/src/app/api/daemon/route.ts +22 -0
- package/src/app/api/dirs/pick/route.ts +60 -0
- package/src/app/api/dirs/route.ts +29 -0
- package/src/app/api/documents/[id]/route.ts +47 -0
- package/src/app/api/documents/route.ts +93 -0
- package/src/app/api/files/serve/route.ts +69 -0
- package/src/app/api/generate/info/route.ts +12 -0
- package/src/app/api/generate/route.ts +106 -0
- package/src/app/api/ip/route.ts +6 -0
- package/src/app/api/knowledge/[id]/route.ts +61 -0
- package/src/app/api/knowledge/route.ts +48 -0
- package/src/app/api/knowledge/upload/route.ts +86 -0
- package/src/app/api/logs/route.ts +65 -0
- package/src/app/api/mcp-servers/[id]/route.ts +32 -0
- package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
- package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
- package/src/app/api/mcp-servers/route.ts +27 -0
- package/src/app/api/memory/[id]/route.ts +126 -0
- package/src/app/api/memory/maintenance/route.ts +63 -0
- package/src/app/api/memory/route.ts +111 -0
- package/src/app/api/memory-images/[filename]/route.ts +36 -0
- package/src/app/api/orchestrator/run/route.ts +43 -0
- package/src/app/api/plugins/install/route.ts +58 -0
- package/src/app/api/plugins/marketplace/route.ts +33 -0
- package/src/app/api/plugins/route.ts +21 -0
- package/src/app/api/preview-server/route.ts +339 -0
- package/src/app/api/providers/[id]/models/route.ts +29 -0
- package/src/app/api/providers/[id]/route.ts +34 -0
- package/src/app/api/providers/configs/route.ts +7 -0
- package/src/app/api/providers/ollama/route.ts +30 -0
- package/src/app/api/providers/openclaw/health/route.ts +23 -0
- package/src/app/api/providers/route.ts +28 -0
- package/src/app/api/runs/[id]/route.ts +9 -0
- package/src/app/api/runs/route.ts +13 -0
- package/src/app/api/schedules/[id]/route.ts +28 -0
- package/src/app/api/schedules/[id]/run/route.ts +104 -0
- package/src/app/api/schedules/route.ts +78 -0
- package/src/app/api/secrets/[id]/route.ts +29 -0
- package/src/app/api/secrets/route.ts +42 -0
- package/src/app/api/sessions/[id]/browser/route.ts +13 -0
- package/src/app/api/sessions/[id]/chat/route.ts +96 -0
- package/src/app/api/sessions/[id]/clear/route.ts +19 -0
- package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
- package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
- package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
- package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
- package/src/app/api/sessions/[id]/messages/route.ts +9 -0
- package/src/app/api/sessions/[id]/retry/route.ts +28 -0
- package/src/app/api/sessions/[id]/route.ts +103 -0
- package/src/app/api/sessions/[id]/stop/route.ts +13 -0
- package/src/app/api/sessions/heartbeat/route.ts +26 -0
- package/src/app/api/sessions/route.ts +85 -0
- package/src/app/api/settings/route.ts +58 -0
- package/src/app/api/setup/check-provider/route.ts +326 -0
- package/src/app/api/setup/doctor/route.ts +250 -0
- package/src/app/api/skills/[id]/route.ts +40 -0
- package/src/app/api/skills/import/route.ts +69 -0
- package/src/app/api/skills/route.ts +28 -0
- package/src/app/api/tasks/[id]/route.ts +102 -0
- package/src/app/api/tasks/route.ts +115 -0
- package/src/app/api/tts/route.ts +40 -0
- package/src/app/api/upload/route.ts +18 -0
- package/src/app/api/uploads/[filename]/route.ts +59 -0
- package/src/app/api/usage/route.ts +35 -0
- package/src/app/api/version/route.ts +81 -0
- package/src/app/api/version/update/route.ts +95 -0
- package/src/app/api/webhooks/[id]/history/route.ts +13 -0
- package/src/app/api/webhooks/[id]/route.ts +204 -0
- package/src/app/api/webhooks/route.ts +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +370 -0
- package/src/app/layout.tsx +52 -0
- package/src/app/page.tsx +172 -0
- package/src/cli/index.js +1232 -0
- package/src/cli/index.test.js +281 -0
- package/src/cli/index.ts +1158 -0
- package/src/cli/spec.js +284 -0
- package/src/components/agents/agent-card.tsx +219 -0
- package/src/components/agents/agent-chat-list.tsx +165 -0
- package/src/components/agents/agent-list.tsx +110 -0
- package/src/components/agents/agent-sheet.tsx +1220 -0
- package/src/components/auth/access-key-gate.tsx +248 -0
- package/src/components/auth/setup-wizard.tsx +940 -0
- package/src/components/auth/user-picker.tsx +88 -0
- package/src/components/chat/chat-area.tsx +406 -0
- package/src/components/chat/chat-header.tsx +491 -0
- package/src/components/chat/chat-tool-toggles.tsx +161 -0
- package/src/components/chat/code-block.tsx +146 -0
- package/src/components/chat/dev-server-bar.tsx +39 -0
- package/src/components/chat/message-bubble.tsx +486 -0
- package/src/components/chat/message-list.tsx +299 -0
- package/src/components/chat/session-debug-panel.tsx +196 -0
- package/src/components/chat/streaming-bubble.tsx +85 -0
- package/src/components/chat/thinking-indicator.tsx +26 -0
- package/src/components/chat/tool-call-bubble.tsx +438 -0
- package/src/components/chat/tool-request-banner.tsx +103 -0
- package/src/components/connectors/connector-list.tsx +196 -0
- package/src/components/connectors/connector-sheet.tsx +804 -0
- package/src/components/input/chat-input.tsx +235 -0
- package/src/components/knowledge/knowledge-list.tsx +206 -0
- package/src/components/knowledge/knowledge-sheet.tsx +316 -0
- package/src/components/layout/app-layout.tsx +1016 -0
- package/src/components/layout/daemon-indicator.tsx +56 -0
- package/src/components/layout/mobile-header.tsx +31 -0
- package/src/components/layout/network-banner.tsx +17 -0
- package/src/components/layout/update-banner.tsx +130 -0
- package/src/components/logs/log-list.tsx +358 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
- package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
- package/src/components/memory/memory-card.tsx +63 -0
- package/src/components/memory/memory-detail.tsx +339 -0
- package/src/components/memory/memory-list.tsx +198 -0
- package/src/components/memory/memory-sheet.tsx +70 -0
- package/src/components/plugins/plugin-list.tsx +60 -0
- package/src/components/plugins/plugin-sheet.tsx +311 -0
- package/src/components/providers/provider-list.tsx +96 -0
- package/src/components/providers/provider-sheet.tsx +542 -0
- package/src/components/runs/run-list.tsx +231 -0
- package/src/components/schedules/schedule-card.tsx +63 -0
- package/src/components/schedules/schedule-list.tsx +76 -0
- package/src/components/schedules/schedule-sheet.tsx +336 -0
- package/src/components/secrets/secret-sheet.tsx +180 -0
- package/src/components/secrets/secrets-list.tsx +91 -0
- package/src/components/sessions/new-session-sheet.tsx +478 -0
- package/src/components/sessions/session-card.tsx +144 -0
- package/src/components/sessions/session-list.tsx +202 -0
- package/src/components/shared/ai-gen-block.tsx +77 -0
- package/src/components/shared/avatar.tsx +48 -0
- package/src/components/shared/bottom-sheet.tsx +30 -0
- package/src/components/shared/confirm-dialog.tsx +47 -0
- package/src/components/shared/connector-platform-icon.tsx +113 -0
- package/src/components/shared/dir-browser.tsx +285 -0
- package/src/components/shared/dropdown.tsx +55 -0
- package/src/components/shared/icon-button.tsx +25 -0
- package/src/components/shared/settings/plugin-manager.tsx +207 -0
- package/src/components/shared/settings/section-capability-policy.tsx +93 -0
- package/src/components/shared/settings/section-embedding.tsx +99 -0
- package/src/components/shared/settings/section-heartbeat.tsx +168 -0
- package/src/components/shared/settings/section-memory.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +108 -0
- package/src/components/shared/settings/section-providers.tsx +181 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
- package/src/components/shared/settings/section-secrets.tsx +132 -0
- package/src/components/shared/settings/section-user-preferences.tsx +24 -0
- package/src/components/shared/settings/section-voice.tsx +53 -0
- package/src/components/shared/settings/settings-sheet.tsx +88 -0
- package/src/components/shared/settings/types.ts +7 -0
- package/src/components/shared/settings/utils.ts +13 -0
- package/src/components/shared/settings-sheet.tsx +1 -0
- package/src/components/shared/skeleton.tsx +19 -0
- package/src/components/shared/usage-badge.tsx +28 -0
- package/src/components/skills/clawhub-browser.tsx +225 -0
- package/src/components/skills/skill-list.tsx +70 -0
- package/src/components/skills/skill-sheet.tsx +254 -0
- package/src/components/tasks/task-board.tsx +96 -0
- package/src/components/tasks/task-card.tsx +179 -0
- package/src/components/tasks/task-column.tsx +73 -0
- package/src/components/tasks/task-list.tsx +118 -0
- package/src/components/tasks/task-sheet.tsx +415 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sonner.tsx +22 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/usage/usage-list.tsx +105 -0
- package/src/components/webhooks/webhook-list.tsx +166 -0
- package/src/components/webhooks/webhook-sheet.tsx +402 -0
- package/src/hooks/use-auto-resize.ts +20 -0
- package/src/hooks/use-media-query.ts +21 -0
- package/src/hooks/use-speech-recognition.ts +83 -0
- package/src/instrumentation.ts +8 -0
- package/src/lib/agents.ts +13 -0
- package/src/lib/api-client.ts +100 -0
- package/src/lib/chat.ts +60 -0
- package/src/lib/memory.ts +42 -0
- package/src/lib/openclaw-endpoint.test.ts +48 -0
- package/src/lib/openclaw-endpoint.ts +67 -0
- package/src/lib/provider-config.ts +13 -0
- package/src/lib/providers/anthropic.ts +135 -0
- package/src/lib/providers/claude-cli.ts +202 -0
- package/src/lib/providers/codex-cli.ts +260 -0
- package/src/lib/providers/index.ts +351 -0
- package/src/lib/providers/ollama.ts +131 -0
- package/src/lib/providers/openai.ts +164 -0
- package/src/lib/providers/openclaw.ts +330 -0
- package/src/lib/providers/opencode-cli.ts +164 -0
- package/src/lib/runtime-loop.ts +15 -0
- package/src/lib/schedule-dedupe.test.ts +84 -0
- package/src/lib/schedule-dedupe.ts +174 -0
- package/src/lib/schedule-name.ts +62 -0
- package/src/lib/schedules.ts +16 -0
- package/src/lib/server/agent-registry.ts +70 -0
- package/src/lib/server/api-routes.test.ts +362 -0
- package/src/lib/server/autonomy-contract.ts +200 -0
- package/src/lib/server/build-llm.ts +155 -0
- package/src/lib/server/capability-router.test.ts +21 -0
- package/src/lib/server/capability-router.ts +172 -0
- package/src/lib/server/chat-execution.ts +894 -0
- package/src/lib/server/clawhub-client.test.ts +161 -0
- package/src/lib/server/clawhub-client.ts +26 -0
- package/src/lib/server/connectors/connector-routing.test.ts +243 -0
- package/src/lib/server/connectors/discord.ts +116 -0
- package/src/lib/server/connectors/googlechat.ts +66 -0
- package/src/lib/server/connectors/manager.ts +559 -0
- package/src/lib/server/connectors/matrix.ts +78 -0
- package/src/lib/server/connectors/media.ts +149 -0
- package/src/lib/server/connectors/openclaw.test.ts +375 -0
- package/src/lib/server/connectors/openclaw.ts +1132 -0
- package/src/lib/server/connectors/signal.ts +183 -0
- package/src/lib/server/connectors/slack.ts +258 -0
- package/src/lib/server/connectors/teams.ts +94 -0
- package/src/lib/server/connectors/telegram.ts +221 -0
- package/src/lib/server/connectors/types.ts +62 -0
- package/src/lib/server/connectors/whatsapp.ts +349 -0
- package/src/lib/server/context-manager.ts +232 -0
- package/src/lib/server/cost.ts +31 -0
- package/src/lib/server/daemon-state.ts +354 -0
- package/src/lib/server/data-dir.ts +3 -0
- package/src/lib/server/embeddings.ts +111 -0
- package/src/lib/server/execution-log.ts +257 -0
- package/src/lib/server/gateway/protocol.test.ts +54 -0
- package/src/lib/server/gateway/protocol.ts +114 -0
- package/src/lib/server/heartbeat-service.ts +366 -0
- package/src/lib/server/knowledge-db.test.ts +441 -0
- package/src/lib/server/logger.ts +47 -0
- package/src/lib/server/main-agent-loop.ts +1017 -0
- package/src/lib/server/mcp-client.test.ts +342 -0
- package/src/lib/server/mcp-client.ts +130 -0
- package/src/lib/server/memory-db.ts +1078 -0
- package/src/lib/server/memory-graph.test.ts +153 -0
- package/src/lib/server/memory-graph.ts +138 -0
- package/src/lib/server/openclaw-health.ts +245 -0
- package/src/lib/server/orchestrator-lg.ts +431 -0
- package/src/lib/server/orchestrator.ts +364 -0
- package/src/lib/server/playwright-proxy.mjs +70 -0
- package/src/lib/server/plugins.ts +229 -0
- package/src/lib/server/process-manager.ts +327 -0
- package/src/lib/server/provider-health.ts +113 -0
- package/src/lib/server/queue.ts +859 -0
- package/src/lib/server/runtime-settings.ts +119 -0
- package/src/lib/server/scheduler.ts +196 -0
- package/src/lib/server/session-mailbox.ts +129 -0
- package/src/lib/server/session-run-manager.ts +512 -0
- package/src/lib/server/session-tools/connector.ts +124 -0
- package/src/lib/server/session-tools/context-mgmt.ts +103 -0
- package/src/lib/server/session-tools/context.ts +114 -0
- package/src/lib/server/session-tools/crud.ts +673 -0
- package/src/lib/server/session-tools/delegate.ts +708 -0
- package/src/lib/server/session-tools/file.ts +264 -0
- package/src/lib/server/session-tools/index.ts +164 -0
- package/src/lib/server/session-tools/memory.ts +230 -0
- package/src/lib/server/session-tools/session-info.ts +422 -0
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
- package/src/lib/server/session-tools/shell.ts +171 -0
- package/src/lib/server/session-tools/web.ts +408 -0
- package/src/lib/server/session-tools.ts +9 -0
- package/src/lib/server/skills-normalize.ts +130 -0
- package/src/lib/server/storage-mcp.test.ts +161 -0
- package/src/lib/server/storage.ts +670 -0
- package/src/lib/server/stream-agent-chat.ts +571 -0
- package/src/lib/server/task-reports.ts +122 -0
- package/src/lib/server/task-result.ts +161 -0
- package/src/lib/server/task-validation.test.ts +27 -0
- package/src/lib/server/task-validation.ts +90 -0
- package/src/lib/server/tool-capability-policy.test.ts +58 -0
- package/src/lib/server/tool-capability-policy.ts +262 -0
- package/src/lib/sessions.ts +68 -0
- package/src/lib/tasks.ts +20 -0
- package/src/lib/tts.ts +42 -0
- package/src/lib/upload.ts +10 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +43 -0
- package/src/stores/use-app-store.ts +468 -0
- package/src/stores/use-chat-store.ts +323 -0
- package/src/types/index.ts +621 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert'
|
|
3
|
+
import {
|
|
4
|
+
normalizeLinkedMemoryIds,
|
|
5
|
+
normalizeMemoryLookupLimits,
|
|
6
|
+
resolveLookupRequest,
|
|
7
|
+
traverseLinkedMemoryGraph,
|
|
8
|
+
} from './memory-graph.ts'
|
|
9
|
+
import type { MemoryLookupLimits, LinkedMemoryNode } from './memory-graph.ts'
|
|
10
|
+
|
|
11
|
+
describe('normalizeLinkedMemoryIds', () => {
|
|
12
|
+
it('filters empty strings and self-references', () => {
|
|
13
|
+
assert.deepStrictEqual(
|
|
14
|
+
normalizeLinkedMemoryIds(['a', '', 'b', ' ', 'a', 'c'], 'self'),
|
|
15
|
+
['a', 'b', 'c']
|
|
16
|
+
)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('returns empty array for non-array input', () => {
|
|
20
|
+
assert.deepStrictEqual(normalizeLinkedMemoryIds(null), [])
|
|
21
|
+
assert.deepStrictEqual(normalizeLinkedMemoryIds(undefined), [])
|
|
22
|
+
assert.deepStrictEqual(normalizeLinkedMemoryIds('not-an-array'), [])
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('deduplicates ids', () => {
|
|
26
|
+
assert.deepStrictEqual(
|
|
27
|
+
normalizeLinkedMemoryIds(['a', 'b', 'a', 'a', 'c'], undefined),
|
|
28
|
+
['a', 'b', 'c']
|
|
29
|
+
)
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('normalizeMemoryLookupLimits', () => {
|
|
34
|
+
it('returns defaults for empty settings', () => {
|
|
35
|
+
const limits = normalizeMemoryLookupLimits({})
|
|
36
|
+
assert.strictEqual(limits.maxDepth, 3)
|
|
37
|
+
assert.strictEqual(limits.maxPerLookup, 20)
|
|
38
|
+
assert.strictEqual(limits.maxLinkedExpansion, 60)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('clamps to valid ranges', () => {
|
|
42
|
+
const limits = normalizeMemoryLookupLimits({
|
|
43
|
+
memoryReferenceDepth: 100,
|
|
44
|
+
maxMemoriesPerLookup: 1000,
|
|
45
|
+
maxLinkedMemoriesExpanded: 5000,
|
|
46
|
+
})
|
|
47
|
+
assert.strictEqual(limits.maxDepth, 12) // max
|
|
48
|
+
assert.strictEqual(limits.maxPerLookup, 200) // max
|
|
49
|
+
assert.strictEqual(limits.maxLinkedExpansion, 1000) // max
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('allows zeros for depth and linked expansion', () => {
|
|
53
|
+
const limits = normalizeMemoryLookupLimits({
|
|
54
|
+
memoryReferenceDepth: 0,
|
|
55
|
+
maxMemoriesPerLookup: 5,
|
|
56
|
+
maxLinkedMemoriesExpanded: 0,
|
|
57
|
+
})
|
|
58
|
+
assert.strictEqual(limits.maxDepth, 0)
|
|
59
|
+
assert.strictEqual(limits.maxPerLookup, 5)
|
|
60
|
+
assert.strictEqual(limits.maxLinkedExpansion, 0)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('resolveLookupRequest', () => {
|
|
65
|
+
const defaults: MemoryLookupLimits = {
|
|
66
|
+
maxDepth: 3,
|
|
67
|
+
maxPerLookup: 20,
|
|
68
|
+
maxLinkedExpansion: 60,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
it('uses defaults for empty request', () => {
|
|
72
|
+
assert.deepStrictEqual(resolveLookupRequest(defaults, {}), defaults)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('overrides with request values', () => {
|
|
76
|
+
const result = resolveLookupRequest(defaults, { depth: 2, limit: 10, linkedLimit: 30 })
|
|
77
|
+
assert.strictEqual(result.maxDepth, 2)
|
|
78
|
+
assert.strictEqual(result.maxPerLookup, 10)
|
|
79
|
+
assert.strictEqual(result.maxLinkedExpansion, 30)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('caps at defaults maxima', () => {
|
|
83
|
+
const result = resolveLookupRequest(defaults, { depth: 100, limit: 1000, linkedLimit: 5000 })
|
|
84
|
+
assert.strictEqual(result.maxDepth, 3)
|
|
85
|
+
assert.strictEqual(result.maxPerLookup, 20)
|
|
86
|
+
assert.strictEqual(result.maxLinkedExpansion, 60)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe('traverseLinkedMemoryGraph', () => {
|
|
91
|
+
const fetchByIds = (ids: string[]): LinkedMemoryNode[] => {
|
|
92
|
+
return ids.map((id) => ({
|
|
93
|
+
id,
|
|
94
|
+
linkedMemoryIds: id === 'a' ? ['b', 'c'] : id === 'b' ? ['d'] : [],
|
|
95
|
+
}))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
it('returns empty for empty seeds', () => {
|
|
99
|
+
const result = traverseLinkedMemoryGraph([], { maxDepth: 3, maxPerLookup: 20, maxLinkedExpansion: 60 }, fetchByIds)
|
|
100
|
+
assert.strictEqual(result.entries.length, 0)
|
|
101
|
+
assert.strictEqual(result.truncated, false)
|
|
102
|
+
assert.strictEqual(result.expandedLinkedCount, 0)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('traverses linked nodes by depth', () => {
|
|
106
|
+
const seeds = [{ id: 'a', linkedMemoryIds: ['b', 'c'] }]
|
|
107
|
+
const result = traverseLinkedMemoryGraph(seeds, { maxDepth: 2, maxPerLookup: 20, maxLinkedExpansion: 60 }, fetchByIds)
|
|
108
|
+
const ids = result.entries.map((n) => n.id)
|
|
109
|
+
assert.ok(ids.includes('a'))
|
|
110
|
+
assert.ok(ids.includes('b'))
|
|
111
|
+
assert.ok(ids.includes('c'))
|
|
112
|
+
assert.ok(ids.includes('d')) // depth 2
|
|
113
|
+
assert.strictEqual(result.expandedLinkedCount, 3) // b, c, d
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('respects maxDepth', () => {
|
|
117
|
+
const limitedFetch = (ids: string[]): LinkedMemoryNode[] => {
|
|
118
|
+
const map: Record<string, string[]> = { a: ['b'], b: ['c'], c: ['d'], d: [] }
|
|
119
|
+
return ids.map((id) => ({ id, linkedMemoryIds: map[id] || [] }))
|
|
120
|
+
}
|
|
121
|
+
const seeds = [{ id: 'a', linkedMemoryIds: ['b'] }]
|
|
122
|
+
const result = traverseLinkedMemoryGraph(seeds, { maxDepth: 1, maxPerLookup: 20, maxLinkedExpansion: 60 }, limitedFetch)
|
|
123
|
+
const ids = result.entries.map((n) => n.id)
|
|
124
|
+
assert.ok(ids.includes('a'))
|
|
125
|
+
assert.ok(ids.includes('b'))
|
|
126
|
+
assert.ok(!ids.includes('c')) // depth 1 stops before c
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('respects maxPerLookup', () => {
|
|
130
|
+
const seeds = [{ id: 'a', linkedMemoryIds: ['b', 'c', 'd', 'e', 'f'] }]
|
|
131
|
+
const result = traverseLinkedMemoryGraph(seeds, { maxDepth: 3, maxPerLookup: 3, maxLinkedExpansion: 60 }, fetchByIds)
|
|
132
|
+
assert.strictEqual(result.entries.length, 3)
|
|
133
|
+
assert.strictEqual(result.truncated, true)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('respects maxLinkedExpansion', () => {
|
|
137
|
+
const seeds = [{ id: 'a', linkedMemoryIds: ['b', 'c', 'd', 'e', 'f'] }]
|
|
138
|
+
const result = traverseLinkedMemoryGraph(seeds, { maxDepth: 3, maxPerLookup: 20, maxLinkedExpansion: 2 }, fetchByIds)
|
|
139
|
+
assert.strictEqual(result.expandedLinkedCount, 2)
|
|
140
|
+
assert.strictEqual(result.truncated, true)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('handles circular links', () => {
|
|
144
|
+
const circularFetch = (ids: string[]): LinkedMemoryNode[] => {
|
|
145
|
+
const map: Record<string, string[]> = { a: ['b'], b: ['a'] }
|
|
146
|
+
return ids.map((id) => ({ id, linkedMemoryIds: map[id] || [] }))
|
|
147
|
+
}
|
|
148
|
+
const seeds = [{ id: 'a', linkedMemoryIds: ['b'] }]
|
|
149
|
+
const result = traverseLinkedMemoryGraph(seeds, { maxDepth: 10, maxPerLookup: 100, maxLinkedExpansion: 100 }, circularFetch)
|
|
150
|
+
assert.strictEqual(result.entries.length, 2) // just a and b
|
|
151
|
+
assert.strictEqual(result.truncated, false)
|
|
152
|
+
})
|
|
153
|
+
})
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export const DEFAULT_MEMORY_REFERENCE_DEPTH = 3
|
|
2
|
+
export const DEFAULT_MAX_MEMORIES_PER_LOOKUP = 20
|
|
3
|
+
export const DEFAULT_MAX_LINKED_MEMORIES_EXPANDED = 60
|
|
4
|
+
|
|
5
|
+
const MAX_MEMORY_REFERENCE_DEPTH = 12
|
|
6
|
+
const MAX_MEMORIES_PER_LOOKUP = 200
|
|
7
|
+
const MAX_LINKED_MEMORIES_EXPANDED = 1000
|
|
8
|
+
|
|
9
|
+
export interface MemoryLookupLimits {
|
|
10
|
+
maxDepth: number
|
|
11
|
+
maxPerLookup: number
|
|
12
|
+
maxLinkedExpansion: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MemoryLookupRequest {
|
|
16
|
+
depth?: number | null
|
|
17
|
+
limit?: number | null
|
|
18
|
+
linkedLimit?: number | null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LinkedMemoryNode {
|
|
22
|
+
id: string
|
|
23
|
+
linkedMemoryIds?: string[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface TraversalResult<TNode extends LinkedMemoryNode> {
|
|
27
|
+
entries: TNode[]
|
|
28
|
+
truncated: boolean
|
|
29
|
+
expandedLinkedCount: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseIntSetting(value: unknown): number | null {
|
|
33
|
+
const parsed = typeof value === 'number'
|
|
34
|
+
? value
|
|
35
|
+
: typeof value === 'string'
|
|
36
|
+
? Number.parseInt(value, 10)
|
|
37
|
+
: Number.NaN
|
|
38
|
+
if (!Number.isFinite(parsed)) return null
|
|
39
|
+
return Math.trunc(parsed)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function clamp(value: number, min: number, max: number): number {
|
|
43
|
+
return Math.max(min, Math.min(max, value))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function normalizeMemoryLookupLimits(settings: Record<string, unknown>): MemoryLookupLimits {
|
|
47
|
+
const depthRaw = parseIntSetting(settings.memoryReferenceDepth ?? settings.memoryMaxDepth)
|
|
48
|
+
const perLookupRaw = parseIntSetting(settings.maxMemoriesPerLookup ?? settings.memoryMaxPerLookup)
|
|
49
|
+
const linkedRaw = parseIntSetting(settings.maxLinkedMemoriesExpanded)
|
|
50
|
+
|
|
51
|
+
const maxDepth = clamp(depthRaw ?? DEFAULT_MEMORY_REFERENCE_DEPTH, 0, MAX_MEMORY_REFERENCE_DEPTH)
|
|
52
|
+
const maxPerLookup = clamp(perLookupRaw ?? DEFAULT_MAX_MEMORIES_PER_LOOKUP, 1, MAX_MEMORIES_PER_LOOKUP)
|
|
53
|
+
const maxLinkedExpansion = clamp(linkedRaw ?? DEFAULT_MAX_LINKED_MEMORIES_EXPANDED, 0, MAX_LINKED_MEMORIES_EXPANDED)
|
|
54
|
+
|
|
55
|
+
return { maxDepth, maxPerLookup, maxLinkedExpansion }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function resolveLookupRequest(
|
|
59
|
+
defaults: MemoryLookupLimits,
|
|
60
|
+
request: MemoryLookupRequest = {},
|
|
61
|
+
): MemoryLookupLimits {
|
|
62
|
+
const depth = parseIntSetting(request.depth)
|
|
63
|
+
const limit = parseIntSetting(request.limit)
|
|
64
|
+
const linkedLimit = parseIntSetting(request.linkedLimit)
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
maxDepth: clamp(depth ?? defaults.maxDepth, 0, defaults.maxDepth),
|
|
68
|
+
maxPerLookup: clamp(limit ?? defaults.maxPerLookup, 1, defaults.maxPerLookup),
|
|
69
|
+
maxLinkedExpansion: clamp(linkedLimit ?? defaults.maxLinkedExpansion, 0, defaults.maxLinkedExpansion),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function normalizeLinkedMemoryIds(input: unknown, selfId?: string): string[] {
|
|
74
|
+
if (!Array.isArray(input)) return []
|
|
75
|
+
const out: string[] = []
|
|
76
|
+
const seen = new Set<string>()
|
|
77
|
+
for (const raw of input) {
|
|
78
|
+
const id = typeof raw === 'string' ? raw.trim() : ''
|
|
79
|
+
if (!id || id === selfId || seen.has(id)) continue
|
|
80
|
+
seen.add(id)
|
|
81
|
+
out.push(id)
|
|
82
|
+
}
|
|
83
|
+
return out
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function traverseLinkedMemoryGraph<TNode extends LinkedMemoryNode>(
|
|
87
|
+
seedNodes: TNode[],
|
|
88
|
+
opts: MemoryLookupLimits,
|
|
89
|
+
fetchByIds: (ids: string[]) => TNode[],
|
|
90
|
+
): TraversalResult<TNode> {
|
|
91
|
+
if (!seedNodes.length || opts.maxPerLookup <= 0) {
|
|
92
|
+
return { entries: [], truncated: false, expandedLinkedCount: 0 }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const seen = new Set<string>()
|
|
96
|
+
const seedIds = new Set(seedNodes.map((n) => n.id))
|
|
97
|
+
const out: TNode[] = []
|
|
98
|
+
let queue: TNode[] = [...seedNodes]
|
|
99
|
+
let depth = 0
|
|
100
|
+
let truncated = false
|
|
101
|
+
let expandedLinkedCount = 0
|
|
102
|
+
|
|
103
|
+
while (queue.length > 0 && depth <= opts.maxDepth) {
|
|
104
|
+
const nextQueue: TNode[] = []
|
|
105
|
+
for (const entry of queue) {
|
|
106
|
+
if (seen.has(entry.id)) continue
|
|
107
|
+
|
|
108
|
+
const isLinkedExpansion = !seedIds.has(entry.id)
|
|
109
|
+
if (isLinkedExpansion) {
|
|
110
|
+
if (expandedLinkedCount >= opts.maxLinkedExpansion) {
|
|
111
|
+
truncated = true
|
|
112
|
+
return { entries: out, truncated, expandedLinkedCount }
|
|
113
|
+
}
|
|
114
|
+
expandedLinkedCount++
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
seen.add(entry.id)
|
|
118
|
+
out.push(entry)
|
|
119
|
+
if (out.length >= opts.maxPerLookup) {
|
|
120
|
+
truncated = true
|
|
121
|
+
return { entries: out, truncated, expandedLinkedCount }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (depth >= opts.maxDepth) continue
|
|
125
|
+
const linkedIds = normalizeLinkedMemoryIds(entry.linkedMemoryIds, entry.id).filter((id) => !seen.has(id))
|
|
126
|
+
if (!linkedIds.length) continue
|
|
127
|
+
const linkedEntries = fetchByIds(linkedIds)
|
|
128
|
+
for (const linked of linkedEntries) {
|
|
129
|
+
if (!linked?.id || seen.has(linked.id)) continue
|
|
130
|
+
nextQueue.push(linked)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
queue = nextQueue
|
|
134
|
+
depth++
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { entries: out, truncated, expandedLinkedCount }
|
|
138
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { deriveOpenClawWsUrl, normalizeOpenClawEndpoint } from '@/lib/openclaw-endpoint'
|
|
2
|
+
import { decryptKey, loadCredentials } from './storage'
|
|
3
|
+
|
|
4
|
+
export interface OpenClawHealthInput {
|
|
5
|
+
endpoint?: string | null
|
|
6
|
+
credentialId?: string | null
|
|
7
|
+
token?: string | null
|
|
8
|
+
model?: string | null
|
|
9
|
+
timeoutMs?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface OpenClawHealthResult {
|
|
13
|
+
ok: boolean
|
|
14
|
+
endpoint: string
|
|
15
|
+
wsUrl: string
|
|
16
|
+
authProvided: boolean
|
|
17
|
+
model: string | null
|
|
18
|
+
models: string[]
|
|
19
|
+
modelsStatus: number | null
|
|
20
|
+
chatStatus: number | null
|
|
21
|
+
completionSample?: string
|
|
22
|
+
error?: string
|
|
23
|
+
hint?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeToken(value: unknown): string | null {
|
|
27
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveCredentialToken(credentialId?: string | null): string | null {
|
|
31
|
+
const id = normalizeToken(credentialId)
|
|
32
|
+
if (!id) return null
|
|
33
|
+
const credentials = loadCredentials()
|
|
34
|
+
const credential = credentials[id]
|
|
35
|
+
if (!credential?.encryptedKey) return null
|
|
36
|
+
try {
|
|
37
|
+
return decryptKey(credential.encryptedKey)
|
|
38
|
+
} catch {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function extractModels(payload: any): string[] {
|
|
44
|
+
const models = Array.isArray(payload?.data) ? payload.data : []
|
|
45
|
+
return models
|
|
46
|
+
.map((item: any) => (typeof item?.id === 'string' ? item.id.trim() : ''))
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function extractChatText(payload: any): string {
|
|
51
|
+
const content = payload?.choices?.[0]?.message?.content
|
|
52
|
+
if (typeof content === 'string') return content.trim()
|
|
53
|
+
if (Array.isArray(content)) {
|
|
54
|
+
return content
|
|
55
|
+
.map((block: any) => {
|
|
56
|
+
if (typeof block?.text === 'string') return block.text
|
|
57
|
+
if (typeof block?.content === 'string') return block.content
|
|
58
|
+
return ''
|
|
59
|
+
})
|
|
60
|
+
.join(' ')
|
|
61
|
+
.trim()
|
|
62
|
+
}
|
|
63
|
+
return ''
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function describeHttpError(status: number): { error: string; hint?: string } {
|
|
67
|
+
if (status === 401) {
|
|
68
|
+
return {
|
|
69
|
+
error: 'OpenClaw endpoint rejected auth (401 Unauthorized).',
|
|
70
|
+
hint: 'Set a valid OpenClaw token credential on the agent/session or pass credentialId/token to this health check.',
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (status === 404) {
|
|
74
|
+
return {
|
|
75
|
+
error: 'OpenClaw endpoint path is invalid (404).',
|
|
76
|
+
hint: 'Point to the gateway root/ws URL and let SwarmClaw normalize it, or use an explicit /v1 endpoint.',
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (status === 405) {
|
|
80
|
+
return {
|
|
81
|
+
error: 'OpenClaw endpoint method mismatch (405).',
|
|
82
|
+
hint: 'Ensure this is an OpenAI-compatible chat endpoint exposed by the OpenClaw gateway.',
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
error: `OpenClaw endpoint returned HTTP ${status}.`,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function createTimeoutError(message: string): Error {
|
|
91
|
+
const timeoutErr = new Error(message)
|
|
92
|
+
;(timeoutErr as any).name = 'TimeoutError'
|
|
93
|
+
return timeoutErr
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, onTimeout?: () => void, message?: string): Promise<T> {
|
|
97
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
98
|
+
try {
|
|
99
|
+
return await new Promise<T>((resolve, reject) => {
|
|
100
|
+
timer = setTimeout(() => {
|
|
101
|
+
try { onTimeout?.() } catch { /* noop */ }
|
|
102
|
+
reject(createTimeoutError(message || `Request timed out after ${timeoutMs}ms`))
|
|
103
|
+
}, timeoutMs)
|
|
104
|
+
promise
|
|
105
|
+
.then((value) => {
|
|
106
|
+
if (timer) clearTimeout(timer)
|
|
107
|
+
resolve(value)
|
|
108
|
+
})
|
|
109
|
+
.catch((err) => {
|
|
110
|
+
if (timer) clearTimeout(timer)
|
|
111
|
+
reject(err)
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
} finally {
|
|
115
|
+
if (timer) clearTimeout(timer)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function fetchJsonWithTimeout(url: string, init: RequestInit, timeoutMs: number): Promise<{ response: Response; body: any }> {
|
|
120
|
+
const controller = new AbortController()
|
|
121
|
+
try {
|
|
122
|
+
const response = await withTimeout(
|
|
123
|
+
fetch(url, { ...init, signal: controller.signal }),
|
|
124
|
+
timeoutMs,
|
|
125
|
+
() => controller.abort(),
|
|
126
|
+
`Request timed out after ${timeoutMs}ms`,
|
|
127
|
+
)
|
|
128
|
+
const text = await withTimeout(
|
|
129
|
+
response.text(),
|
|
130
|
+
timeoutMs,
|
|
131
|
+
() => controller.abort(),
|
|
132
|
+
`Response read timed out after ${timeoutMs}ms`,
|
|
133
|
+
)
|
|
134
|
+
let body: any = {}
|
|
135
|
+
if (text) {
|
|
136
|
+
try {
|
|
137
|
+
body = JSON.parse(text)
|
|
138
|
+
} catch {
|
|
139
|
+
body = {}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return { response, body }
|
|
143
|
+
} catch (err: any) {
|
|
144
|
+
if (err?.name === 'AbortError') throw createTimeoutError(`Request timed out after ${timeoutMs}ms`)
|
|
145
|
+
throw err
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function probeOpenClawHealth(input: OpenClawHealthInput): Promise<OpenClawHealthResult> {
|
|
150
|
+
const endpoint = normalizeOpenClawEndpoint(input.endpoint || undefined)
|
|
151
|
+
const wsUrl = deriveOpenClawWsUrl(endpoint)
|
|
152
|
+
const timeoutMs = Math.max(1000, Math.min(30_000, Math.trunc(input.timeoutMs || 8000)))
|
|
153
|
+
const token = normalizeToken(input.token) || resolveCredentialToken(input.credentialId)
|
|
154
|
+
const authProvided = !!token
|
|
155
|
+
const headers: Record<string, string> = {
|
|
156
|
+
// Use text/plain to bypass Express body parsers in Hostinger/proxy setups.
|
|
157
|
+
// The OpenClaw gateway parses the body as JSON regardless of Content-Type.
|
|
158
|
+
'content-type': 'text/plain',
|
|
159
|
+
}
|
|
160
|
+
if (token) headers.authorization = `Bearer ${token}`
|
|
161
|
+
|
|
162
|
+
let models: string[] = []
|
|
163
|
+
let modelsStatus: number | null = null
|
|
164
|
+
let chatStatus: number | null = null
|
|
165
|
+
let completionSample = ''
|
|
166
|
+
let lastError = ''
|
|
167
|
+
let lastHint: string | undefined
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const { response: modelsRes, body } = await fetchJsonWithTimeout(`${endpoint}/models`, {
|
|
171
|
+
headers,
|
|
172
|
+
cache: 'no-store',
|
|
173
|
+
}, timeoutMs)
|
|
174
|
+
modelsStatus = modelsRes.status
|
|
175
|
+
if (modelsRes.ok) {
|
|
176
|
+
models = extractModels(body)
|
|
177
|
+
} else {
|
|
178
|
+
const err = describeHttpError(modelsRes.status)
|
|
179
|
+
lastError = err.error
|
|
180
|
+
lastHint = err.hint
|
|
181
|
+
}
|
|
182
|
+
} catch (err: any) {
|
|
183
|
+
if (err?.name === 'TimeoutError') {
|
|
184
|
+
lastError = `OpenClaw models probe timed out after ${timeoutMs}ms.`
|
|
185
|
+
} else {
|
|
186
|
+
lastError = err?.message || 'Failed to connect to OpenClaw endpoint.'
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
ok: false,
|
|
190
|
+
endpoint,
|
|
191
|
+
wsUrl,
|
|
192
|
+
authProvided,
|
|
193
|
+
model: null,
|
|
194
|
+
models: [],
|
|
195
|
+
modelsStatus: null,
|
|
196
|
+
chatStatus: null,
|
|
197
|
+
error: lastError,
|
|
198
|
+
hint: 'Verify the OpenClaw gateway is running and reachable at this host/port.',
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const model = normalizeToken(input.model) || models[0] || 'default'
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const { response: chatRes, body } = await fetchJsonWithTimeout(`${endpoint}/chat/completions`, {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers,
|
|
208
|
+
cache: 'no-store',
|
|
209
|
+
body: JSON.stringify({
|
|
210
|
+
model,
|
|
211
|
+
messages: [{ role: 'user', content: 'Reply with OPENCLAW_HEALTH_OK' }],
|
|
212
|
+
stream: false,
|
|
213
|
+
max_tokens: 12,
|
|
214
|
+
}),
|
|
215
|
+
}, timeoutMs)
|
|
216
|
+
chatStatus = chatRes.status
|
|
217
|
+
if (!chatRes.ok) {
|
|
218
|
+
const err = describeHttpError(chatRes.status)
|
|
219
|
+
lastError = err.error
|
|
220
|
+
lastHint = err.hint || lastHint
|
|
221
|
+
} else {
|
|
222
|
+
completionSample = extractChatText(body).slice(0, 240)
|
|
223
|
+
}
|
|
224
|
+
} catch (err: any) {
|
|
225
|
+
if (err?.name === 'TimeoutError') {
|
|
226
|
+
lastError = `OpenClaw chat probe timed out after ${timeoutMs}ms.`
|
|
227
|
+
} else {
|
|
228
|
+
lastError = err?.message || 'OpenClaw chat probe failed.'
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
ok: !!chatStatus && chatStatus >= 200 && chatStatus < 300,
|
|
234
|
+
endpoint,
|
|
235
|
+
wsUrl,
|
|
236
|
+
authProvided,
|
|
237
|
+
model,
|
|
238
|
+
models,
|
|
239
|
+
modelsStatus,
|
|
240
|
+
chatStatus,
|
|
241
|
+
completionSample: completionSample || undefined,
|
|
242
|
+
error: lastError || undefined,
|
|
243
|
+
hint: lastHint,
|
|
244
|
+
}
|
|
245
|
+
}
|