@swarmclawai/swarmclaw 1.2.6 → 1.2.9
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 +54 -23
- package/next.config.ts +1 -0
- package/package.json +4 -3
- package/scripts/easy-setup.mjs +1 -1
- package/scripts/postinstall.mjs +1 -1
- package/skills/swarmclaw.md +115 -0
- package/skills/tools/browser.md +131 -0
- package/skills/tools/execute.md +98 -0
- package/skills/tools/files.md +98 -0
- package/skills/tools/memory.md +104 -0
- package/skills/tools/platform.md +144 -0
- package/skills/tools/skills.md +83 -0
- package/src/app/agents/[id]/page.tsx +1 -18
- package/src/app/api/agents/thread-route.test.ts +0 -1
- package/src/app/api/approvals/route.test.ts +6 -22
- package/src/app/api/chats/[id]/messages/route.ts +23 -19
- package/src/app/api/chats/messages-route.test.ts +105 -51
- package/src/app/api/connectors/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
- package/src/app/api/openclaw/deploy/route.ts +2 -0
- package/src/app/api/portability/export/route.ts +8 -0
- package/src/app/api/portability/import/route.test.ts +80 -0
- package/src/app/api/portability/import/route.ts +28 -0
- package/src/app/api/settings/route.ts +0 -2
- package/src/app/api/setup/doctor/route.ts +4 -4
- package/src/app/api/wallets/[id]/route.ts +15 -157
- package/src/app/api/wallets/generate/route.ts +22 -0
- package/src/app/api/wallets/route.test.ts +147 -0
- package/src/app/api/wallets/route.ts +13 -95
- package/src/app/autonomy/page.tsx +2 -57
- package/src/app/protocols/page.tsx +2 -21
- package/src/app/settings/page.tsx +0 -9
- package/src/app/wallets/page.tsx +105 -5
- package/src/cli/index.js +21 -33
- package/src/cli/spec.js +19 -30
- package/src/components/agents/agent-chat-list.tsx +23 -1
- package/src/components/agents/agent-sheet.tsx +2 -40
- package/src/components/agents/inspector-panel.tsx +165 -131
- package/src/components/chat/chat-area.tsx +38 -9
- package/src/components/chat/chat-card.tsx +0 -31
- package/src/components/chat/message-bubble.tsx +1 -108
- package/src/components/chat/message-list.tsx +33 -19
- package/src/components/connectors/connector-sheet.tsx +25 -1
- package/src/components/gateways/gateway-sheet.tsx +5 -2
- package/src/components/layout/sidebar-rail.tsx +6 -10
- package/src/components/projects/project-detail.tsx +3 -35
- package/src/components/projects/tabs/overview-tab.tsx +3 -59
- package/src/components/projects/tabs/work-tab.tsx +7 -77
- package/src/components/protocols/structured-session-launcher.tsx +1 -22
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/components/tasks/task-card.tsx +4 -34
- package/src/components/tasks/task-sheet.tsx +6 -36
- package/src/components/wallets/wallet-list.tsx +150 -0
- package/src/lib/agent-execute-defaults.test.ts +24 -0
- package/src/lib/agent-execute-defaults.ts +62 -0
- package/src/lib/app/navigation.test.ts +0 -13
- package/src/lib/app/navigation.ts +2 -7
- package/src/lib/app/view-constants.ts +14 -19
- package/src/lib/chat/queued-message-queue.test.ts +134 -1
- package/src/lib/chat/queued-message-queue.ts +77 -2
- package/src/lib/server/agents/agent-service.ts +5 -0
- package/src/lib/server/agents/agent-thread-session.ts +0 -1
- package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
- package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
- package/src/lib/server/agents/delegation-jobs.ts +0 -25
- package/src/lib/server/agents/main-agent-loop.ts +1 -49
- package/src/lib/server/agents/subagent-runtime.ts +0 -1
- package/src/lib/server/approval-match.ts +0 -85
- package/src/lib/server/approvals.test.ts +6 -6
- package/src/lib/server/approvals.ts +0 -6
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
- package/src/lib/server/builtin-extensions.ts +1 -2
- package/src/lib/server/capability-router.test.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +15 -14
- package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -4
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +81 -64
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
- package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
- package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
- package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
- package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
- package/src/lib/server/chat-execution/message-classifier.ts +11 -16
- package/src/lib/server/chat-execution/prompt-builder.test.ts +27 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +14 -31
- package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
- package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
- package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
- package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
- package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +13 -126
- package/src/lib/server/chat-execution/stream-agent-chat.ts +46 -21
- package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
- package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
- package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
- package/src/lib/server/chats/chat-session-service.ts +3 -5
- package/src/lib/server/connectors/connector-inbound.ts +0 -1
- package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
- package/src/lib/server/connectors/connector-service.ts +39 -9
- package/src/lib/server/connectors/discord.ts +2 -2
- package/src/lib/server/connectors/matrix.ts +3 -2
- package/src/lib/server/connectors/signal.ts +5 -4
- package/src/lib/server/connectors/slack.ts +10 -9
- package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
- package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
- package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
- package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
- package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
- package/src/lib/server/connectors/swarmdock.ts +255 -0
- package/src/lib/server/connectors/teams.ts +3 -2
- package/src/lib/server/connectors/telegram.ts +4 -4
- package/src/lib/server/connectors/whatsapp.ts +2 -2
- package/src/lib/server/daemon/controller.ts +7 -0
- package/src/lib/server/execution-brief.test.ts +2 -25
- package/src/lib/server/execution-brief.ts +12 -35
- package/src/lib/server/execution-engine/task-attempt.ts +0 -1
- package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
- package/src/lib/server/messages/message-repository.test.ts +70 -0
- package/src/lib/server/messages/message-repository.ts +11 -6
- package/src/lib/server/openclaw/deploy.ts +32 -2
- package/src/lib/server/persistence/storage-context.ts +0 -5
- package/src/lib/server/plugins-advanced.test.ts +1 -2
- package/src/lib/server/portability/export.ts +109 -0
- package/src/lib/server/portability/import.ts +159 -0
- package/src/lib/server/protocols/protocol-normalization.ts +0 -4
- package/src/lib/server/protocols/protocol-queries.ts +0 -6
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
- package/src/lib/server/protocols/protocol-service.ts +0 -1
- package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
- package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
- package/src/lib/server/protocols/protocol-swarm.ts +0 -2
- package/src/lib/server/protocols/protocol-types.ts +0 -2
- package/src/lib/server/provider-health.ts +1 -10
- package/src/lib/server/runtime/daemon-state/core.ts +0 -9
- package/src/lib/server/runtime/daemon-state.test.ts +0 -35
- package/src/lib/server/runtime/heartbeat-service.ts +3 -23
- package/src/lib/server/runtime/process-manager.ts +13 -9
- package/src/lib/server/runtime/queue/core.ts +11 -33
- package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
- package/src/lib/server/runtime/scheduler.ts +0 -13
- package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/queries.ts +15 -1
- package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
- package/src/lib/server/runtime/session-run-manager.test.ts +58 -28
- package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
- package/src/lib/server/sandbox/session-runtime.ts +40 -28
- package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
- package/src/lib/server/session-tools/context.ts +1 -1
- package/src/lib/server/session-tools/credential-env.ts +109 -0
- package/src/lib/server/session-tools/crud.ts +3 -17
- package/src/lib/server/session-tools/delegate.ts +0 -4
- package/src/lib/server/session-tools/edit_file.ts +3 -2
- package/src/lib/server/session-tools/execute.test.ts +58 -0
- package/src/lib/server/session-tools/execute.ts +334 -0
- package/src/lib/server/session-tools/files-tool.ts +635 -0
- package/src/lib/server/session-tools/index.ts +14 -8
- package/src/lib/server/session-tools/memory-tool.ts +242 -0
- package/src/lib/server/session-tools/memory.ts +1 -1
- package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
- package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
- package/src/lib/server/session-tools/platform-tool.ts +617 -0
- package/src/lib/server/session-tools/session-info.ts +3 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
- package/src/lib/server/session-tools/shell.ts +7 -122
- package/src/lib/server/session-tools/skills-tool.ts +396 -0
- package/src/lib/server/session-tools/team-context.ts +0 -3
- package/src/lib/server/session-tools/web.ts +2 -2
- package/src/lib/server/storage-normalization.ts +10 -0
- package/src/lib/server/storage.ts +18 -45
- package/src/lib/server/tasks/task-checkout.ts +59 -0
- package/src/lib/server/tasks/task-lifecycle.ts +2 -0
- package/src/lib/server/tasks/task-route-service.ts +4 -26
- package/src/lib/server/tasks/task-service.ts +0 -7
- package/src/lib/server/tool-aliases.ts +2 -2
- package/src/lib/server/tool-capability-policy-advanced.test.ts +13 -6
- package/src/lib/server/tool-capability-policy.test.ts +2 -1
- package/src/lib/server/tool-capability-policy.ts +60 -35
- package/src/lib/server/tool-planning.ts +11 -12
- package/src/lib/server/universal-tool-access.ts +0 -1
- package/src/lib/server/wallets/wallet-crypto.ts +33 -0
- package/src/lib/server/wallets/wallet-repository.ts +24 -0
- package/src/lib/server/wallets/wallet-service.ts +119 -0
- package/src/lib/server/working-state/extraction.ts +8 -42
- package/src/lib/server/working-state/normalization.ts +10 -103
- package/src/lib/server/working-state/service.ts +12 -21
- package/src/lib/setup-defaults.ts +5 -0
- package/src/lib/strip-internal-metadata.test.ts +1 -1
- package/src/lib/strip-internal-metadata.ts +1 -1
- package/src/lib/tool-definitions.ts +1 -1
- package/src/lib/validation/schemas.test.ts +16 -0
- package/src/lib/validation/schemas.ts +49 -2
- package/src/stores/slices/data-slice.ts +5 -1
- package/src/stores/slices/ui-slice.ts +0 -4
- package/src/stores/use-chat-store.test.ts +231 -0
- package/src/stores/use-chat-store.ts +62 -13
- package/src/types/agent.ts +264 -0
- package/src/types/app-settings.ts +173 -0
- package/src/types/approval.ts +25 -0
- package/src/types/connector.ts +188 -0
- package/src/types/extension.ts +386 -0
- package/src/types/index.ts +16 -3555
- package/src/types/message.ts +56 -0
- package/src/types/misc.ts +737 -0
- package/src/types/protocol.ts +420 -0
- package/src/types/provider.ts +52 -0
- package/src/types/run.ts +180 -0
- package/src/types/schedule.ts +59 -0
- package/src/types/session.ts +215 -0
- package/src/types/skill.ts +157 -0
- package/src/types/swarmdock.ts +29 -0
- package/src/types/task.ts +144 -0
- package/src/types/working-state.ts +204 -0
- package/src/views/settings/section-heartbeat.tsx +2 -2
- package/src/views/settings/section-runtime-loop.tsx +0 -14
- package/src/app/api/canvas/[sessionId]/route.ts +0 -35
- package/src/app/api/missions/[id]/actions/route.ts +0 -31
- package/src/app/api/missions/[id]/events/route.ts +0 -14
- package/src/app/api/missions/[id]/route.ts +0 -10
- package/src/app/api/missions/route.test.ts +0 -244
- package/src/app/api/missions/route.ts +0 -57
- package/src/app/api/wallets/[id]/approve/route.ts +0 -79
- package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
- package/src/app/api/wallets/[id]/send/route.ts +0 -113
- package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
- package/src/app/missions/[id]/page.tsx +0 -3
- package/src/app/missions/page.tsx +0 -685
- package/src/components/canvas/canvas-panel.tsx +0 -267
- package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
- package/src/components/wallets/wallet-panel.tsx +0 -1010
- package/src/components/wallets/wallet-section.tsx +0 -260
- package/src/features/missions/queries.ts +0 -23
- package/src/lib/canvas-content.test.ts +0 -360
- package/src/lib/canvas-content.ts +0 -198
- package/src/lib/server/canvas-content.test.ts +0 -32
- package/src/lib/server/canvas-content.ts +0 -6
- package/src/lib/server/ethereum.ts +0 -591
- package/src/lib/server/evm-swap.ts +0 -476
- package/src/lib/server/missions/mission-intent.test.ts +0 -63
- package/src/lib/server/missions/mission-intent.ts +0 -569
- package/src/lib/server/missions/mission-repository.ts +0 -74
- package/src/lib/server/missions/mission-service/actions.ts +0 -6
- package/src/lib/server/missions/mission-service/bindings.ts +0 -9
- package/src/lib/server/missions/mission-service/context.ts +0 -4
- package/src/lib/server/missions/mission-service/core.ts +0 -2271
- package/src/lib/server/missions/mission-service/queries.ts +0 -12
- package/src/lib/server/missions/mission-service/recovery.ts +0 -5
- package/src/lib/server/missions/mission-service/ticks.ts +0 -9
- package/src/lib/server/missions/mission-service.test.ts +0 -888
- package/src/lib/server/missions/mission-service.ts +0 -6
- package/src/lib/server/session-tools/canvas.ts +0 -105
- package/src/lib/server/session-tools/sandbox.ts +0 -281
- package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
- package/src/lib/server/session-tools/wallet.ts +0 -1287
- package/src/lib/server/solana.ts +0 -327
- package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
- package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
- package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
- package/src/lib/server/wallet/wallet-service.test.ts +0 -81
- package/src/lib/server/wallet/wallet-service.ts +0 -225
- package/src/lib/wallet/wallet-transactions.test.ts +0 -75
- package/src/lib/wallet/wallet-transactions.ts +0 -43
- package/src/lib/wallet/wallet.test.ts +0 -333
- package/src/lib/wallet/wallet.ts +0 -183
- package/src/views/settings/section-wallets.tsx +0 -35
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
4
|
-
import { api } from '@/lib/app/api-client'
|
|
5
|
-
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
6
|
-
import { useAppStore } from '@/stores/use-app-store'
|
|
7
|
-
import type { AgentWallet, WalletAssetBalance, WalletPortfolioSummary, WalletChain } from '@/types'
|
|
8
|
-
import { toast } from 'sonner'
|
|
9
|
-
import { errorMessage } from '@/lib/shared-utils'
|
|
10
|
-
import {
|
|
11
|
-
SUPPORTED_WALLET_CHAINS,
|
|
12
|
-
formatWalletAmount,
|
|
13
|
-
getWalletBalanceAtomic,
|
|
14
|
-
getWalletChainMeta,
|
|
15
|
-
getWalletLimitAtomic,
|
|
16
|
-
} from '@/lib/wallet/wallet'
|
|
17
|
-
|
|
18
|
-
type SafeWallet = Omit<AgentWallet, 'encryptedPrivateKey'> & {
|
|
19
|
-
balanceAtomic?: string
|
|
20
|
-
balanceLamports?: number
|
|
21
|
-
balanceFormatted?: string
|
|
22
|
-
balanceSymbol?: string
|
|
23
|
-
assets?: WalletAssetBalance[]
|
|
24
|
-
portfolioSummary?: WalletPortfolioSummary
|
|
25
|
-
isActive?: boolean
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface WalletSectionProps {
|
|
29
|
-
agentId: string
|
|
30
|
-
wallets: SafeWallet[]
|
|
31
|
-
activeWalletId: string | null
|
|
32
|
-
onWalletCreated: () => void
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function WalletSection({ agentId, wallets, activeWalletId, onWalletCreated }: WalletSectionProps) {
|
|
36
|
-
const appSettings = useAppStore((s) => s.appSettings)
|
|
37
|
-
const [creating, setCreating] = useState(false)
|
|
38
|
-
const [activatingWalletId, setActivatingWalletId] = useState<string | null>(null)
|
|
39
|
-
const [error, setError] = useState<string | null>(null)
|
|
40
|
-
const [copiedWalletId, setCopiedWalletId] = useState<string | null>(null)
|
|
41
|
-
|
|
42
|
-
const connectedChains = useMemo(() => new Set(wallets.map((wallet) => wallet.chain)), [wallets])
|
|
43
|
-
const availableChains = useMemo(
|
|
44
|
-
() => SUPPORTED_WALLET_CHAINS.filter((chain) => !connectedChains.has(chain)),
|
|
45
|
-
[connectedChains],
|
|
46
|
-
)
|
|
47
|
-
const sortedWallets = useMemo(
|
|
48
|
-
() => [...wallets].sort((a, b) => {
|
|
49
|
-
const aActive = a.id === activeWalletId || a.isActive === true
|
|
50
|
-
const bActive = b.id === activeWalletId || b.isActive === true
|
|
51
|
-
if (aActive !== bActive) return aActive ? -1 : 1
|
|
52
|
-
return a.chain.localeCompare(b.chain)
|
|
53
|
-
}),
|
|
54
|
-
[activeWalletId, wallets],
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
const [chain, setChain] = useState<WalletChain>(availableChains[0] || 'solana')
|
|
58
|
-
const walletApprovalsEnabled = appSettings.walletApprovalsEnabled !== false
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (availableChains.length === 0) return
|
|
62
|
-
if (!availableChains.includes(chain)) setChain(availableChains[0])
|
|
63
|
-
}, [availableChains, chain])
|
|
64
|
-
|
|
65
|
-
const createWallet = useCallback(async () => {
|
|
66
|
-
if (!availableChains.includes(chain)) return
|
|
67
|
-
setCreating(true)
|
|
68
|
-
setError(null)
|
|
69
|
-
try {
|
|
70
|
-
await api('POST', '/wallets', { agentId, chain })
|
|
71
|
-
toast.success('Agent wallet created successfully')
|
|
72
|
-
await onWalletCreated()
|
|
73
|
-
} catch (err: unknown) {
|
|
74
|
-
const msg = errorMessage(err)
|
|
75
|
-
setError(msg)
|
|
76
|
-
toast.error(msg)
|
|
77
|
-
} finally {
|
|
78
|
-
setCreating(false)
|
|
79
|
-
}
|
|
80
|
-
}, [agentId, availableChains, chain, onWalletCreated])
|
|
81
|
-
|
|
82
|
-
const copyAddress = useCallback(async (wallet: SafeWallet) => {
|
|
83
|
-
const copiedValue = await copyTextToClipboard(wallet.publicKey)
|
|
84
|
-
if (!copiedValue) return
|
|
85
|
-
setCopiedWalletId(wallet.id)
|
|
86
|
-
setTimeout(() => {
|
|
87
|
-
setCopiedWalletId((current) => current === wallet.id ? null : current)
|
|
88
|
-
}, 2000)
|
|
89
|
-
}, [])
|
|
90
|
-
|
|
91
|
-
const setActiveWallet = useCallback(async (walletId: string) => {
|
|
92
|
-
setActivatingWalletId(walletId)
|
|
93
|
-
setError(null)
|
|
94
|
-
try {
|
|
95
|
-
await api('PATCH', `/wallets/${walletId}`, { makeActive: true })
|
|
96
|
-
toast.success('Default wallet updated')
|
|
97
|
-
await onWalletCreated()
|
|
98
|
-
} catch (err: unknown) {
|
|
99
|
-
const msg = errorMessage(err)
|
|
100
|
-
setError(msg)
|
|
101
|
-
toast.error(msg)
|
|
102
|
-
} finally {
|
|
103
|
-
setActivatingWalletId(null)
|
|
104
|
-
}
|
|
105
|
-
}, [onWalletCreated])
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<div className="mb-8">
|
|
109
|
-
<div className="flex items-center gap-2 mb-3">
|
|
110
|
-
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em]">
|
|
111
|
-
Wallets
|
|
112
|
-
</label>
|
|
113
|
-
<span className="px-1.5 py-0.5 rounded-[4px] bg-amber-500/15 text-amber-400 text-[9px] font-600 uppercase tracking-wide">
|
|
114
|
-
Experimental
|
|
115
|
-
</span>
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
|
-
{sortedWallets.length > 0 ? (
|
|
119
|
-
<div className="space-y-3">
|
|
120
|
-
<div className="p-4 rounded-[12px] border border-white/[0.06] bg-surface-2/50">
|
|
121
|
-
<div className="flex items-center justify-between gap-3 mb-2">
|
|
122
|
-
<div>
|
|
123
|
-
<div className="text-[12px] font-600 text-text-1">Combined Wallet Summary</div>
|
|
124
|
-
<p className="text-[11px] text-text-3/70 mt-1">
|
|
125
|
-
{sortedWallets.length} wallet{sortedWallets.length === 1 ? '' : 's'} connected. The active wallet is used by default when the agent does not specify a chain explicitly.
|
|
126
|
-
</p>
|
|
127
|
-
</div>
|
|
128
|
-
<div className="text-right">
|
|
129
|
-
<div className="text-[18px] font-600 text-text-1">{sortedWallets.length}</div>
|
|
130
|
-
<div className="text-[10px] uppercase tracking-wide text-text-3/50">Connected</div>
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
<div className="grid gap-2 md:grid-cols-2">
|
|
134
|
-
{sortedWallets.map((wallet) => {
|
|
135
|
-
const walletMeta = getWalletChainMeta(wallet.chain)
|
|
136
|
-
const balanceFormatted = wallet.balanceFormatted || formatWalletAmount(wallet.chain, getWalletBalanceAtomic(wallet), { minFractionDigits: 4, maxFractionDigits: 6 })
|
|
137
|
-
const isActive = wallet.id === activeWalletId || wallet.isActive === true
|
|
138
|
-
return (
|
|
139
|
-
<div key={wallet.id} className="rounded-[10px] border border-white/[0.06] bg-black/10 px-3 py-2">
|
|
140
|
-
<div className="flex items-center gap-2">
|
|
141
|
-
<span className="text-[10px] text-text-3/60 uppercase tracking-wide font-600">{walletMeta.label}</span>
|
|
142
|
-
{isActive && (
|
|
143
|
-
<span className="px-1.5 py-0.5 rounded-[999px] bg-accent-soft text-accent-bright text-[9px] font-600 uppercase tracking-wide">
|
|
144
|
-
Active
|
|
145
|
-
</span>
|
|
146
|
-
)}
|
|
147
|
-
</div>
|
|
148
|
-
<div className="mt-1 text-[14px] font-600 text-text-1">{balanceFormatted} {walletMeta.symbol}</div>
|
|
149
|
-
{wallet.portfolioSummary?.nonZeroAssets ? (
|
|
150
|
-
<div className="mt-1 text-[10px] text-text-3/55">
|
|
151
|
-
{wallet.portfolioSummary.nonZeroAssets} asset{wallet.portfolioSummary.nonZeroAssets === 1 ? '' : 's'} tracked
|
|
152
|
-
</div>
|
|
153
|
-
) : null}
|
|
154
|
-
<div className="mt-1 text-[10px] text-text-3/55 font-mono truncate">{wallet.publicKey}</div>
|
|
155
|
-
</div>
|
|
156
|
-
)
|
|
157
|
-
})}
|
|
158
|
-
</div>
|
|
159
|
-
</div>
|
|
160
|
-
|
|
161
|
-
{sortedWallets.map((wallet) => {
|
|
162
|
-
const walletMeta = getWalletChainMeta(wallet.chain)
|
|
163
|
-
const balanceFormatted = wallet.balanceFormatted || formatWalletAmount(wallet.chain, getWalletBalanceAtomic(wallet), { minFractionDigits: 4, maxFractionDigits: 6 })
|
|
164
|
-
const perTxLimit = formatWalletAmount(wallet.chain, getWalletLimitAtomic(wallet, 'perTx'), { maxFractionDigits: 6 })
|
|
165
|
-
const dailyLimit = formatWalletAmount(wallet.chain, getWalletLimitAtomic(wallet, 'daily'), { maxFractionDigits: 6 })
|
|
166
|
-
const isActive = wallet.id === activeWalletId || wallet.isActive === true
|
|
167
|
-
return (
|
|
168
|
-
<div key={wallet.id} className="p-4 rounded-[12px] border border-white/[0.06] bg-surface-2/50 space-y-3">
|
|
169
|
-
<div className="flex items-center gap-2">
|
|
170
|
-
<span className="text-[10px] text-text-3/60 uppercase tracking-wide font-600">
|
|
171
|
-
{walletMeta.label}
|
|
172
|
-
</span>
|
|
173
|
-
{isActive && (
|
|
174
|
-
<span className="px-1.5 py-0.5 rounded-[999px] bg-accent-soft text-accent-bright text-[9px] font-600 uppercase tracking-wide">
|
|
175
|
-
Default
|
|
176
|
-
</span>
|
|
177
|
-
)}
|
|
178
|
-
<span className="flex-1" />
|
|
179
|
-
<span className="text-[13px] font-600 text-text-1">
|
|
180
|
-
{balanceFormatted} {walletMeta.symbol}
|
|
181
|
-
</span>
|
|
182
|
-
</div>
|
|
183
|
-
<div className="flex items-center gap-2">
|
|
184
|
-
<code className="text-[11px] text-text-3 bg-black/20 px-2 py-1 rounded-[6px] font-mono truncate flex-1">
|
|
185
|
-
{wallet.publicKey}
|
|
186
|
-
</code>
|
|
187
|
-
<button
|
|
188
|
-
type="button"
|
|
189
|
-
onClick={() => copyAddress(wallet)}
|
|
190
|
-
className="shrink-0 px-2 py-1 rounded-[6px] text-[10px] text-text-3 hover:text-text-2 border border-white/[0.08] bg-surface transition-colors cursor-pointer"
|
|
191
|
-
style={{ fontFamily: 'inherit' }}
|
|
192
|
-
>
|
|
193
|
-
{copiedWalletId === wallet.id ? 'Copied!' : 'Copy'}
|
|
194
|
-
</button>
|
|
195
|
-
{!isActive && (
|
|
196
|
-
<button
|
|
197
|
-
type="button"
|
|
198
|
-
onClick={() => setActiveWallet(wallet.id)}
|
|
199
|
-
disabled={activatingWalletId === wallet.id}
|
|
200
|
-
className="shrink-0 px-2 py-1 rounded-[6px] text-[10px] font-600 text-accent-bright border border-accent-bright/20 bg-accent-soft/10 hover:bg-accent-soft/20 transition-colors cursor-pointer disabled:opacity-50"
|
|
201
|
-
style={{ fontFamily: 'inherit' }}
|
|
202
|
-
>
|
|
203
|
-
{activatingWalletId === wallet.id ? 'Setting...' : 'Set Default'}
|
|
204
|
-
</button>
|
|
205
|
-
)}
|
|
206
|
-
</div>
|
|
207
|
-
<div className="flex flex-wrap items-center gap-3 text-[10px] text-text-3/60">
|
|
208
|
-
<span>Limit: {perTxLimit} {walletMeta.symbol}/tx</span>
|
|
209
|
-
<span>Daily: {dailyLimit} {walletMeta.symbol}</span>
|
|
210
|
-
<span>{!walletApprovalsEnabled ? 'Approvals off globally' : (wallet.requireApproval ? 'Approval required' : 'Auto-send')}</span>
|
|
211
|
-
{wallet.portfolioSummary?.nonZeroAssets ? (
|
|
212
|
-
<span>{wallet.portfolioSummary.nonZeroAssets} asset{wallet.portfolioSummary.nonZeroAssets === 1 ? '' : 's'} detected</span>
|
|
213
|
-
) : null}
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
)
|
|
217
|
-
})}
|
|
218
|
-
</div>
|
|
219
|
-
) : null}
|
|
220
|
-
|
|
221
|
-
{availableChains.length > 0 ? (
|
|
222
|
-
<div className="mt-3 p-4 rounded-[12px] border border-white/[0.06] bg-surface-2/50">
|
|
223
|
-
<p className="text-[12px] text-text-3/70 mb-3">
|
|
224
|
-
{getWalletChainMeta(chain).createDescription}
|
|
225
|
-
</p>
|
|
226
|
-
<label className="block text-[11px] text-text-3/70 mb-1">Wallet Type</label>
|
|
227
|
-
<select
|
|
228
|
-
value={chain}
|
|
229
|
-
onChange={(event) => setChain(event.target.value as WalletChain)}
|
|
230
|
-
className="w-full mb-3 px-3 py-2 rounded-[8px] border border-white/[0.08] bg-surface text-[12px] text-text-1 outline-none focus:border-accent/40"
|
|
231
|
-
style={{ fontFamily: 'inherit' }}
|
|
232
|
-
>
|
|
233
|
-
{availableChains.map((availableChain) => (
|
|
234
|
-
<option key={availableChain} value={availableChain}>
|
|
235
|
-
{getWalletChainMeta(availableChain).label}
|
|
236
|
-
</option>
|
|
237
|
-
))}
|
|
238
|
-
</select>
|
|
239
|
-
<button
|
|
240
|
-
type="button"
|
|
241
|
-
onClick={createWallet}
|
|
242
|
-
disabled={creating}
|
|
243
|
-
className="px-3 py-1.5 rounded-[8px] bg-accent-soft text-accent-bright text-[11px] font-600 hover:bg-accent-bright/15 transition-all cursor-pointer disabled:opacity-50 border border-accent-bright/20"
|
|
244
|
-
style={{ fontFamily: 'inherit' }}
|
|
245
|
-
>
|
|
246
|
-
{creating ? 'Creating...' : `Create ${getWalletChainMeta(chain).label} Wallet`}
|
|
247
|
-
</button>
|
|
248
|
-
{error && <p className="text-[11px] text-red-400 mt-2">{error}</p>}
|
|
249
|
-
</div>
|
|
250
|
-
) : (
|
|
251
|
-
<div className="mt-3 p-4 rounded-[12px] border border-white/[0.06] bg-surface-2/50">
|
|
252
|
-
<p className="text-[12px] text-text-3/70">
|
|
253
|
-
This agent already has both supported wallet types connected. Use the default toggle above to choose which wallet autonomous actions use when no chain is specified.
|
|
254
|
-
</p>
|
|
255
|
-
{error && <p className="text-[11px] text-red-400 mt-2">{error}</p>}
|
|
256
|
-
</div>
|
|
257
|
-
)}
|
|
258
|
-
</div>
|
|
259
|
-
)
|
|
260
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { useQuery } from '@tanstack/react-query'
|
|
2
|
-
import { api } from '@/lib/app/api-client'
|
|
3
|
-
import type { Mission } from '@/types'
|
|
4
|
-
|
|
5
|
-
type MissionListOptions = {
|
|
6
|
-
enabled?: boolean
|
|
7
|
-
limit?: number
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const missionQueryKeys = {
|
|
11
|
-
all: ['missions'] as const,
|
|
12
|
-
list: (limit: number) => ['missions', 'list', { limit }] as const,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function useMissionsQuery(options: MissionListOptions = {}) {
|
|
16
|
-
const limit = options.limit ?? 80
|
|
17
|
-
return useQuery<Mission[]>({
|
|
18
|
-
queryKey: missionQueryKeys.list(limit),
|
|
19
|
-
queryFn: () => api<Mission[]>('GET', `/missions?limit=${limit}`),
|
|
20
|
-
enabled: options.enabled,
|
|
21
|
-
staleTime: 30_000,
|
|
22
|
-
})
|
|
23
|
-
}
|
|
@@ -1,360 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import {
|
|
4
|
-
normalizeCanvasDocument,
|
|
5
|
-
normalizeCanvasContent,
|
|
6
|
-
isCanvasDocument,
|
|
7
|
-
summarizeCanvasContent,
|
|
8
|
-
} from './canvas-content'
|
|
9
|
-
|
|
10
|
-
describe('normalizeCanvasContent', () => {
|
|
11
|
-
it('returns null for null/undefined', () => {
|
|
12
|
-
assert.equal(normalizeCanvasContent(null), null)
|
|
13
|
-
assert.equal(normalizeCanvasContent(undefined), null)
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('returns string content as-is', () => {
|
|
17
|
-
assert.equal(normalizeCanvasContent('<p>hello</p>'), '<p>hello</p>')
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('returns null for empty string', () => {
|
|
21
|
-
assert.equal(normalizeCanvasContent(''), null)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('normalizes a valid structured document', () => {
|
|
25
|
-
const doc = normalizeCanvasContent({
|
|
26
|
-
title: 'Test',
|
|
27
|
-
blocks: [{ type: 'markdown', markdown: '# Hello' }],
|
|
28
|
-
})
|
|
29
|
-
assert.ok(doc !== null && typeof doc === 'object')
|
|
30
|
-
assert.equal((doc as { kind: string }).kind, 'structured')
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('returns null for object without valid blocks', () => {
|
|
34
|
-
assert.equal(normalizeCanvasContent({ blocks: [] }), null)
|
|
35
|
-
assert.equal(normalizeCanvasContent({}), null)
|
|
36
|
-
assert.equal(normalizeCanvasContent({ blocks: [{ type: 'unknown' }] }), null)
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
describe('normalizeCanvasDocument', () => {
|
|
41
|
-
it('returns null for non-object input', () => {
|
|
42
|
-
assert.equal(normalizeCanvasDocument('string'), null)
|
|
43
|
-
assert.equal(normalizeCanvasDocument(42), null)
|
|
44
|
-
assert.equal(normalizeCanvasDocument(null), null)
|
|
45
|
-
assert.equal(normalizeCanvasDocument([]), null)
|
|
46
|
-
assert.equal(normalizeCanvasDocument(true), null)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('normalizes a markdown block', () => {
|
|
50
|
-
const doc = normalizeCanvasDocument({
|
|
51
|
-
blocks: [{ type: 'markdown', markdown: '# Hi' }],
|
|
52
|
-
})
|
|
53
|
-
assert.ok(doc)
|
|
54
|
-
assert.equal(doc.blocks.length, 1)
|
|
55
|
-
assert.equal(doc.blocks[0].type, 'markdown')
|
|
56
|
-
assert.equal(doc.kind, 'structured')
|
|
57
|
-
assert.equal(doc.theme, 'slate') // default theme
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('applies theme when valid', () => {
|
|
61
|
-
const doc = normalizeCanvasDocument({
|
|
62
|
-
blocks: [{ type: 'markdown', markdown: 'x' }],
|
|
63
|
-
theme: 'emerald',
|
|
64
|
-
})
|
|
65
|
-
assert.ok(doc)
|
|
66
|
-
assert.equal(doc.theme, 'emerald')
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('defaults to slate for invalid theme', () => {
|
|
70
|
-
const doc = normalizeCanvasDocument({
|
|
71
|
-
blocks: [{ type: 'markdown', markdown: 'x' }],
|
|
72
|
-
theme: 'neon',
|
|
73
|
-
})
|
|
74
|
-
assert.ok(doc)
|
|
75
|
-
assert.equal(doc.theme, 'slate')
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('truncates title and subtitle', () => {
|
|
79
|
-
const doc = normalizeCanvasDocument({
|
|
80
|
-
title: 'A'.repeat(300),
|
|
81
|
-
subtitle: 'B'.repeat(500),
|
|
82
|
-
blocks: [{ type: 'markdown', markdown: 'x' }],
|
|
83
|
-
})
|
|
84
|
-
assert.ok(doc)
|
|
85
|
-
assert.equal(doc.title!.length, 180)
|
|
86
|
-
assert.equal(doc.subtitle!.length, 320)
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('skips invalid blocks and keeps valid ones', () => {
|
|
90
|
-
const doc = normalizeCanvasDocument({
|
|
91
|
-
blocks: [
|
|
92
|
-
{ type: 'markdown', markdown: 'valid' },
|
|
93
|
-
{ type: 'unknown_type' },
|
|
94
|
-
null,
|
|
95
|
-
'just a string',
|
|
96
|
-
{ type: 'code', code: 'console.log(1)' },
|
|
97
|
-
],
|
|
98
|
-
})
|
|
99
|
-
assert.ok(doc)
|
|
100
|
-
assert.equal(doc.blocks.length, 2)
|
|
101
|
-
assert.equal(doc.blocks[0].type, 'markdown')
|
|
102
|
-
assert.equal(doc.blocks[1].type, 'code')
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it('enforces max 24 blocks', () => {
|
|
106
|
-
const blocks = Array.from({ length: 30 }, () => ({
|
|
107
|
-
type: 'markdown',
|
|
108
|
-
markdown: 'x',
|
|
109
|
-
}))
|
|
110
|
-
const doc = normalizeCanvasDocument({ blocks })
|
|
111
|
-
assert.ok(doc)
|
|
112
|
-
assert.equal(doc.blocks.length, 24)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
it('normalizes metrics block', () => {
|
|
116
|
-
const doc = normalizeCanvasDocument({
|
|
117
|
-
blocks: [{
|
|
118
|
-
type: 'metrics',
|
|
119
|
-
items: [
|
|
120
|
-
{ label: 'CPU', value: '90%', tone: 'warning' },
|
|
121
|
-
{ label: 'Mem', value: '4GB', detail: 'of 16GB', tone: 'positive' },
|
|
122
|
-
{ label: 'missing value' }, // should be skipped
|
|
123
|
-
{ value: 'missing label' }, // should be skipped
|
|
124
|
-
],
|
|
125
|
-
}],
|
|
126
|
-
})
|
|
127
|
-
assert.ok(doc)
|
|
128
|
-
assert.equal(doc.blocks.length, 1)
|
|
129
|
-
const block = doc.blocks[0] as { type: string; items: Array<{ label: string; value: string; tone: string }> }
|
|
130
|
-
assert.equal(block.items.length, 2)
|
|
131
|
-
assert.equal(block.items[0].tone, 'warning')
|
|
132
|
-
assert.equal(block.items[1].tone, 'positive')
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
it('enforces max 24 metric items', () => {
|
|
136
|
-
const items = Array.from({ length: 30 }, (_, i) => ({
|
|
137
|
-
label: `L${i}`, value: `V${i}`,
|
|
138
|
-
}))
|
|
139
|
-
const doc = normalizeCanvasDocument({
|
|
140
|
-
blocks: [{ type: 'metrics', items }],
|
|
141
|
-
})
|
|
142
|
-
assert.ok(doc)
|
|
143
|
-
const block = doc.blocks[0] as { items: unknown[] }
|
|
144
|
-
assert.equal(block.items.length, 24)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('normalizes cards block', () => {
|
|
148
|
-
const doc = normalizeCanvasDocument({
|
|
149
|
-
blocks: [{
|
|
150
|
-
type: 'cards',
|
|
151
|
-
items: [
|
|
152
|
-
{ title: 'Card 1', body: 'content', tone: 'negative' },
|
|
153
|
-
{ body: 'no title' }, // skipped
|
|
154
|
-
],
|
|
155
|
-
}],
|
|
156
|
-
})
|
|
157
|
-
assert.ok(doc)
|
|
158
|
-
const block = doc.blocks[0] as { items: Array<{ title: string; tone: string }> }
|
|
159
|
-
assert.equal(block.items.length, 1)
|
|
160
|
-
assert.equal(block.items[0].tone, 'negative')
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
it('normalizes table block', () => {
|
|
164
|
-
const doc = normalizeCanvasDocument({
|
|
165
|
-
blocks: [{
|
|
166
|
-
type: 'table',
|
|
167
|
-
table: {
|
|
168
|
-
columns: ['Name', 'Age'],
|
|
169
|
-
rows: [['Alice', 30], ['Bob', true]],
|
|
170
|
-
caption: 'Users',
|
|
171
|
-
},
|
|
172
|
-
}],
|
|
173
|
-
})
|
|
174
|
-
assert.ok(doc)
|
|
175
|
-
assert.equal(doc.blocks[0].type, 'table')
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
it('rejects table with no columns', () => {
|
|
179
|
-
const doc = normalizeCanvasDocument({
|
|
180
|
-
blocks: [{
|
|
181
|
-
type: 'table',
|
|
182
|
-
table: { columns: [], rows: [] },
|
|
183
|
-
}],
|
|
184
|
-
})
|
|
185
|
-
assert.equal(doc, null) // no valid blocks
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
it('rejects table with no rows', () => {
|
|
189
|
-
const doc = normalizeCanvasDocument({
|
|
190
|
-
blocks: [{
|
|
191
|
-
type: 'table',
|
|
192
|
-
table: { columns: ['A'], rows: [] },
|
|
193
|
-
}],
|
|
194
|
-
})
|
|
195
|
-
assert.equal(doc, null)
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
it('limits table to 20 columns and 100 rows', () => {
|
|
199
|
-
const columns = Array.from({ length: 25 }, (_, i) => `Col${i}`)
|
|
200
|
-
const rows = Array.from({ length: 110 }, () => columns.map((_, i) => `val${i}`))
|
|
201
|
-
const doc = normalizeCanvasDocument({
|
|
202
|
-
blocks: [{
|
|
203
|
-
type: 'table',
|
|
204
|
-
table: { columns, rows },
|
|
205
|
-
}],
|
|
206
|
-
})
|
|
207
|
-
assert.ok(doc)
|
|
208
|
-
const block = doc.blocks[0] as { table: { columns: string[]; rows: unknown[][] } }
|
|
209
|
-
assert.equal(block.table.columns.length, 20)
|
|
210
|
-
assert.equal(block.table.rows.length, 100)
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
it('normalizes actions block', () => {
|
|
214
|
-
const doc = normalizeCanvasDocument({
|
|
215
|
-
blocks: [{
|
|
216
|
-
type: 'actions',
|
|
217
|
-
items: [
|
|
218
|
-
{ label: 'Submit', intent: 'primary', href: 'https://x.com' },
|
|
219
|
-
{ label: 'Delete', intent: 'danger' },
|
|
220
|
-
{ intent: 'primary' }, // no label → skipped
|
|
221
|
-
],
|
|
222
|
-
}],
|
|
223
|
-
})
|
|
224
|
-
assert.ok(doc)
|
|
225
|
-
const block = doc.blocks[0] as { items: Array<{ label: string; intent: string }> }
|
|
226
|
-
assert.equal(block.items.length, 2)
|
|
227
|
-
assert.equal(block.items[0].intent, 'primary')
|
|
228
|
-
assert.equal(block.items[1].intent, 'danger')
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
it('defaults action intent to secondary', () => {
|
|
232
|
-
const doc = normalizeCanvasDocument({
|
|
233
|
-
blocks: [{
|
|
234
|
-
type: 'actions',
|
|
235
|
-
items: [{ label: 'Go', intent: 'invalid' }],
|
|
236
|
-
}],
|
|
237
|
-
})
|
|
238
|
-
assert.ok(doc)
|
|
239
|
-
const block = doc.blocks[0] as { items: Array<{ intent: string }> }
|
|
240
|
-
assert.equal(block.items[0].intent, 'secondary')
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
it('normalizes code block with language', () => {
|
|
244
|
-
const doc = normalizeCanvasDocument({
|
|
245
|
-
blocks: [{
|
|
246
|
-
type: 'code',
|
|
247
|
-
code: 'const x = 1;',
|
|
248
|
-
language: 'typescript',
|
|
249
|
-
}],
|
|
250
|
-
})
|
|
251
|
-
assert.ok(doc)
|
|
252
|
-
const block = doc.blocks[0] as { type: string; code: string; language: string }
|
|
253
|
-
assert.equal(block.code, 'const x = 1;')
|
|
254
|
-
assert.equal(block.language, 'typescript')
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
it('rejects code block with empty code', () => {
|
|
258
|
-
const doc = normalizeCanvasDocument({
|
|
259
|
-
blocks: [{ type: 'code', code: '' }],
|
|
260
|
-
})
|
|
261
|
-
assert.equal(doc, null)
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
it('coerces number/boolean to string in asTrimmedString contexts', () => {
|
|
265
|
-
const doc = normalizeCanvasDocument({
|
|
266
|
-
blocks: [{
|
|
267
|
-
type: 'metrics',
|
|
268
|
-
items: [{ label: 42, value: true }],
|
|
269
|
-
}],
|
|
270
|
-
})
|
|
271
|
-
assert.ok(doc)
|
|
272
|
-
const block = doc.blocks[0] as { items: Array<{ label: string; value: string }> }
|
|
273
|
-
assert.equal(block.items[0].label, '42')
|
|
274
|
-
assert.equal(block.items[0].value, 'true')
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
it('truncates markdown content to 20000 chars', () => {
|
|
278
|
-
const longMarkdown = 'x'.repeat(25000)
|
|
279
|
-
const doc = normalizeCanvasDocument({
|
|
280
|
-
blocks: [{ type: 'markdown', markdown: longMarkdown }],
|
|
281
|
-
})
|
|
282
|
-
assert.ok(doc)
|
|
283
|
-
const block = doc.blocks[0] as { markdown: string }
|
|
284
|
-
assert.equal(block.markdown.length, 20000)
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
it('uses provided updatedAt when valid', () => {
|
|
288
|
-
const doc = normalizeCanvasDocument({
|
|
289
|
-
blocks: [{ type: 'markdown', markdown: 'x' }],
|
|
290
|
-
updatedAt: 1234567890,
|
|
291
|
-
})
|
|
292
|
-
assert.ok(doc)
|
|
293
|
-
assert.equal(doc.updatedAt, 1234567890)
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
it('falls back to Date.now() for invalid updatedAt', () => {
|
|
297
|
-
const before = Date.now()
|
|
298
|
-
const doc = normalizeCanvasDocument({
|
|
299
|
-
blocks: [{ type: 'markdown', markdown: 'x' }],
|
|
300
|
-
updatedAt: 'not a number',
|
|
301
|
-
})
|
|
302
|
-
const after = Date.now()
|
|
303
|
-
assert.ok(doc)
|
|
304
|
-
assert.ok(doc.updatedAt! >= before && doc.updatedAt! <= after)
|
|
305
|
-
})
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
describe('isCanvasDocument', () => {
|
|
309
|
-
it('returns true for valid document object', () => {
|
|
310
|
-
assert.equal(isCanvasDocument({
|
|
311
|
-
blocks: [{ type: 'markdown', markdown: 'hi' }],
|
|
312
|
-
}), true)
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
it('returns false for non-documents', () => {
|
|
316
|
-
assert.equal(isCanvasDocument('string'), false)
|
|
317
|
-
assert.equal(isCanvasDocument(null), false)
|
|
318
|
-
assert.equal(isCanvasDocument({}), false)
|
|
319
|
-
assert.equal(isCanvasDocument({ blocks: [] }), false)
|
|
320
|
-
})
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
describe('summarizeCanvasContent', () => {
|
|
324
|
-
it('summarizes null content', () => {
|
|
325
|
-
const summary = summarizeCanvasContent(null)
|
|
326
|
-
assert.equal(summary.kind, 'empty')
|
|
327
|
-
assert.equal(summary.hasContent, false)
|
|
328
|
-
assert.equal(summary.contentLength, 0)
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
it('summarizes string content', () => {
|
|
332
|
-
const summary = summarizeCanvasContent('<p>hello</p>')
|
|
333
|
-
assert.equal(summary.kind, 'html')
|
|
334
|
-
assert.equal(summary.hasContent, true)
|
|
335
|
-
assert.equal(summary.contentLength, 12)
|
|
336
|
-
assert.equal(summary.preview, '<p>hello</p>')
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
it('truncates preview to 500 chars for long strings', () => {
|
|
340
|
-
const long = 'x'.repeat(600)
|
|
341
|
-
const summary = summarizeCanvasContent(long)
|
|
342
|
-
assert.equal((summary.preview as string).length, 500)
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
it('summarizes structured document', () => {
|
|
346
|
-
const doc = normalizeCanvasDocument({
|
|
347
|
-
title: 'My Doc',
|
|
348
|
-
blocks: [
|
|
349
|
-
{ type: 'markdown', markdown: '# Heading' },
|
|
350
|
-
{ type: 'code', code: 'x = 1' },
|
|
351
|
-
],
|
|
352
|
-
})!
|
|
353
|
-
const summary = summarizeCanvasContent(doc)
|
|
354
|
-
assert.equal(summary.kind, 'structured')
|
|
355
|
-
assert.equal(summary.hasContent, true)
|
|
356
|
-
assert.equal(summary.blockCount, 2)
|
|
357
|
-
assert.equal(summary.title, 'My Doc')
|
|
358
|
-
assert.deepEqual(summary.blockTypes, ['markdown', 'code'])
|
|
359
|
-
})
|
|
360
|
-
})
|