@clawpump/claw-agent 0.1.8 → 0.1.11

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.
Files changed (65) hide show
  1. package/agent/.mailmap +4 -0
  2. package/agent/apps/desktop/README.md +3 -3
  3. package/agent/apps/desktop/assets/icon.icns +0 -0
  4. package/agent/apps/desktop/assets/icon.ico +0 -0
  5. package/agent/apps/desktop/assets/icon.png +0 -0
  6. package/agent/apps/desktop/electron/backend-ready.cjs +2 -2
  7. package/agent/apps/desktop/electron/dashboard-token.cjs +3 -3
  8. package/agent/apps/desktop/electron/hardening.cjs +1 -1
  9. package/agent/apps/desktop/electron/main.cjs +67 -67
  10. package/agent/apps/desktop/index.html +1 -1
  11. package/agent/apps/desktop/package.json +16 -16
  12. package/agent/apps/desktop/public/apple-touch-icon.png +0 -0
  13. package/agent/apps/desktop/public/claw-mark.png +0 -0
  14. package/agent/apps/desktop/scripts/after-pack.cjs +1 -1
  15. package/agent/apps/desktop/scripts/set-exe-identity.cjs +2 -2
  16. package/agent/apps/desktop/scripts/test-desktop.mjs +3 -3
  17. package/agent/apps/desktop/src/app/chat/composer/controls.tsx +2 -0
  18. package/agent/apps/desktop/src/app/chat/composer/index.tsx +10 -0
  19. package/agent/apps/desktop/src/app/chat/composer/pod-credits.tsx +49 -0
  20. package/agent/apps/desktop/src/app/chat/index.tsx +1 -1
  21. package/agent/apps/desktop/src/app/chat/sidebar/index.tsx +4 -2
  22. package/agent/apps/desktop/src/app/desktop-controller.tsx +18 -0
  23. package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-request.ts +1 -1
  24. package/agent/apps/desktop/src/app/messaging/index.tsx +5 -5
  25. package/agent/apps/desktop/src/app/routes.ts +9 -1
  26. package/agent/apps/desktop/src/app/session/hooks/use-message-stream.ts +3 -3
  27. package/agent/apps/desktop/src/app/settings/constants.ts +5 -5
  28. package/agent/apps/desktop/src/app/settings/model-settings.tsx +1 -1
  29. package/agent/apps/desktop/src/app/settings/providers-settings.tsx +46 -1
  30. package/agent/apps/desktop/src/app/settings/uninstall-section.tsx +5 -5
  31. package/agent/apps/desktop/src/app/types.ts +9 -1
  32. package/agent/apps/desktop/src/app/wallet/index.tsx +244 -0
  33. package/agent/apps/desktop/src/app/x402/index.tsx +162 -0
  34. package/agent/apps/desktop/src/components/assistant-ui/thread.tsx +1 -1
  35. package/agent/apps/desktop/src/components/brand-mark.tsx +2 -2
  36. package/agent/apps/desktop/src/components/chat/intro-copy.jsonl +6 -6
  37. package/agent/apps/desktop/src/components/chat/intro.tsx +4 -4
  38. package/agent/apps/desktop/src/components/model-picker.tsx +64 -4
  39. package/agent/apps/desktop/src/components/pod-setup-dialog.tsx +227 -0
  40. package/agent/apps/desktop/src/hermes.ts +109 -3
  41. package/agent/apps/desktop/src/i18n/en.ts +80 -78
  42. package/agent/apps/desktop/src/i18n/ja.ts +82 -82
  43. package/agent/apps/desktop/src/i18n/runtime.test.ts +2 -2
  44. package/agent/apps/desktop/src/i18n/zh-hant.ts +82 -82
  45. package/agent/apps/desktop/src/i18n/zh.ts +87 -87
  46. package/agent/apps/desktop/src/lib/desktop-fs.ts +1 -1
  47. package/agent/apps/desktop/src/lib/desktop-slash-commands.ts +4 -4
  48. package/agent/apps/desktop/src/store/composer.ts +7 -0
  49. package/agent/apps/desktop/src/store/onboarding.ts +5 -5
  50. package/agent/apps/desktop/src/themes/presets.ts +54 -54
  51. package/agent/cli.py +184 -10
  52. package/agent/hermes_cli/distribution.py +188 -8
  53. package/agent/hermes_cli/gui_uninstall.py +11 -6
  54. package/agent/hermes_cli/main.py +9 -4
  55. package/agent/hermes_cli/providers.py +29 -0
  56. package/agent/hermes_cli/web_server.py +180 -2
  57. package/agent/plugins/model-providers/usepod/__init__.py +7 -1
  58. package/agent/scripts/install.sh +3 -1
  59. package/agent/scripts/release.py +1 -0
  60. package/agent/web/src/components/ChatSidebar.tsx +5 -0
  61. package/agent/web/src/components/ModelPickerDialog.tsx +28 -1
  62. package/agent/web/src/components/PodCredits.tsx +57 -0
  63. package/agent/web/src/components/PodSetupDialog.tsx +240 -0
  64. package/agent/web/src/lib/api.ts +23 -0
  65. package/package.json +1 -1
@@ -0,0 +1,227 @@
1
+ import { useQuery, useQueryClient } from '@tanstack/react-query'
2
+ import { useEffect, useMemo, useState } from 'react'
3
+
4
+ import { getPodWallets, provisionPod, type PodWallet } from '../hermes'
5
+ import { notify } from '../store/notifications'
6
+
7
+ import { InlineNotice } from './notifications'
8
+ import { Button } from './ui/button'
9
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from './ui/dialog'
10
+ import { GlyphSpinner } from './ui/glyph-spinner'
11
+ import { Input } from './ui/input'
12
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'
13
+
14
+ const DEFAULT_AMOUNT = '5'
15
+
16
+ const walletLabel = (w: PodWallet) =>
17
+ (w.name || (w.wallet_address ? `${w.wallet_address.slice(0, 4)}…${w.wallet_address.slice(-4)}` : w.agent_id)) ?? w.agent_id
18
+
19
+ /**
20
+ * One-screen Pod setup: pick a ClawPump agent wallet, pick an amount, confirm.
21
+ * `usepod_provision` registers + funds a pod from that wallet on-chain in a
22
+ * single call, and the backend switches the session onto Pod as the provider.
23
+ * The single confirm is the on-chain USDC spend — everything else is prefilled.
24
+ */
25
+ export function PodSetupDialog({
26
+ open,
27
+ onOpenChange,
28
+ onProvisioned
29
+ }: {
30
+ open: boolean
31
+ onOpenChange: (open: boolean) => void
32
+ onProvisioned: (model: string) => void
33
+ }) {
34
+ const queryClient = useQueryClient()
35
+ const wallets = useQuery({
36
+ queryKey: ['pod-wallets'],
37
+ queryFn: getPodWallets,
38
+ enabled: open,
39
+ staleTime: 15_000
40
+ })
41
+
42
+ const rows = useMemo(() => wallets.data?.wallets ?? [], [wallets.data])
43
+ const [agentId, setAgentId] = useState('')
44
+ const [amount, setAmount] = useState(DEFAULT_AMOUNT)
45
+ const [busy, setBusy] = useState(false)
46
+ const [error, setError] = useState<string | null>(null)
47
+ // Set once provisioning succeeds → switches the dialog to a "Pod ready" view.
48
+ const [done, setDone] = useState<{ model: string; amount: number; signature?: string; fundingError?: string } | null>(
49
+ null
50
+ )
51
+
52
+ // Fresh start every time the dialog reopens.
53
+ useEffect(() => {
54
+ if (open) {
55
+ setDone(null)
56
+ setError(null)
57
+ }
58
+ }, [open])
59
+
60
+ // Default to the wallet with the most USDC so funding "just works".
61
+ useEffect(() => {
62
+ if (!agentId && rows.length > 0) {
63
+ const best = [...rows].sort((a, b) => (b.usdc_balance ?? 0) - (a.usdc_balance ?? 0))[0]
64
+ setAgentId(best.agent_id)
65
+ }
66
+ }, [rows, agentId])
67
+
68
+ const selected = rows.find(w => w.agent_id === agentId) ?? null
69
+ const amountNum = Number(amount)
70
+ const balance = selected?.usdc_balance ?? 0
71
+ const insufficient = Number.isFinite(amountNum) && amountNum > 0 && amountNum > balance
72
+ const canFund = !busy && Boolean(agentId) && Number.isFinite(amountNum) && amountNum > 0 && !insufficient
73
+
74
+ const fund = async () => {
75
+ if (!canFund) return
76
+ setBusy(true)
77
+ setError(null)
78
+ try {
79
+ const res = await provisionPod(agentId, amountNum)
80
+ if (!res.ok || !res.model) {
81
+ setError(res.error || res.funding_error || 'Pod setup failed. Check the wallet balance and try again.')
82
+ return
83
+ }
84
+ onProvisioned(res.model)
85
+ // Refresh the "connected" badges so Pod flips from "Set up" to "Connected".
86
+ void queryClient.invalidateQueries({ queryKey: ['pod-status'] })
87
+ notify({
88
+ durationMs: 6_000,
89
+ kind: 'success',
90
+ title: 'Pod ready',
91
+ message: `Funded $${amountNum.toFixed(2)} USDC — Pod is now your model provider.`
92
+ })
93
+ setDone({ amount: amountNum, fundingError: res.funding_error || undefined, model: res.model, signature: res.signature })
94
+ } catch (err) {
95
+ setError(err instanceof Error ? err.message : 'Pod setup failed.')
96
+ } finally {
97
+ setBusy(false)
98
+ }
99
+ }
100
+
101
+ return (
102
+ <Dialog onOpenChange={busy ? undefined : onOpenChange} open={open}>
103
+ <DialogContent className="max-w-md gap-0 p-0">
104
+ {done ? (
105
+ <>
106
+ <DialogHeader className="border-b border-border px-4 py-3">
107
+ <DialogTitle className="flex items-center gap-2">
108
+ <span className="grid size-7 place-items-center rounded-full bg-emerald-500/20 text-emerald-300">✓</span>
109
+ Pod ready
110
+ </DialogTitle>
111
+ <DialogDescription className="text-xs leading-relaxed">
112
+ Pod is now your model provider. New chats run on it automatically.
113
+ </DialogDescription>
114
+ </DialogHeader>
115
+
116
+ <div className="flex flex-col gap-3 p-4">
117
+ <div className="rounded-md border border-emerald-500/30 bg-emerald-500/10 px-3 py-3 text-sm">
118
+ <div className="font-semibold text-emerald-300">⚡ Using Pod</div>
119
+ <div className="mt-0.5 text-muted-foreground">
120
+ Funded <span className="font-mono">${done.amount.toFixed(2)} USDC</span> · model{' '}
121
+ <span className="font-mono">{done.model}</span>
122
+ </div>
123
+ </div>
124
+ {done.signature && (
125
+ <a
126
+ className="text-xs text-primary underline-offset-2 hover:underline"
127
+ href={`https://solscan.io/tx/${done.signature}`}
128
+ rel="noreferrer"
129
+ target="_blank"
130
+ >
131
+ View funding transaction ↗
132
+ </a>
133
+ )}
134
+ {done.fundingError && (
135
+ <InlineNotice kind="warning">
136
+ Pod was created but the deposit didn’t confirm ({done.fundingError}). Top it up from Set up Pod →
137
+ again, or it may settle shortly.
138
+ </InlineNotice>
139
+ )}
140
+ </div>
141
+
142
+ <DialogFooter className="flex-row items-center justify-end gap-2 bg-card p-3">
143
+ <Button onClick={() => onOpenChange(false)}>Done</Button>
144
+ </DialogFooter>
145
+ </>
146
+ ) : (
147
+ <>
148
+ <DialogHeader className="border-b border-border px-4 py-3">
149
+ <DialogTitle>Set up Pod</DialogTitle>
150
+ <DialogDescription className="text-xs leading-relaxed">
151
+ Fund a private inference Pod from a ClawPump agent wallet and use it as your model provider. You only pay for
152
+ what you use — the Pod holds a prepaid USDC balance.
153
+ </DialogDescription>
154
+ </DialogHeader>
155
+
156
+ <div className="flex flex-col gap-4 p-4">
157
+ {wallets.isLoading ? (
158
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
159
+ <GlyphSpinner /> Loading your wallets…
160
+ </div>
161
+ ) : rows.length === 0 ? (
162
+ <InlineNotice kind="warning">
163
+ {wallets.data?.error || 'No ClawPump agent wallets found. Create one in the ClawPump dashboard first.'}
164
+ </InlineNotice>
165
+ ) : (
166
+ <>
167
+ <label className="flex flex-col gap-1.5 text-sm">
168
+ <span className="font-medium">Pay from wallet</span>
169
+ <Select onValueChange={setAgentId} value={agentId}>
170
+ <SelectTrigger>
171
+ <SelectValue placeholder="Choose an agent wallet" />
172
+ </SelectTrigger>
173
+ <SelectContent>
174
+ {rows.map(w => (
175
+ <SelectItem key={w.agent_id} value={w.agent_id}>
176
+ {walletLabel(w)} — ${(w.usdc_balance ?? 0).toFixed(2)} USDC
177
+ </SelectItem>
178
+ ))}
179
+ </SelectContent>
180
+ </Select>
181
+ </label>
182
+
183
+ <label className="flex flex-col gap-1.5 text-sm">
184
+ <span className="font-medium">Amount to fund (USDC)</span>
185
+ <Input
186
+ inputMode="decimal"
187
+ onChange={e => setAmount(e.target.value)}
188
+ type="number"
189
+ min="0"
190
+ step="1"
191
+ value={amount}
192
+ />
193
+ <span className="text-xs text-muted-foreground">
194
+ Wallet balance: <span className="font-mono">${balance.toFixed(2)} USDC</span>
195
+ </span>
196
+ </label>
197
+
198
+ {insufficient && (
199
+ <InlineNotice kind="warning">
200
+ Not enough USDC in this wallet for ${amountNum.toFixed(2)}. Pick a smaller amount or fund the wallet.
201
+ </InlineNotice>
202
+ )}
203
+ {error && <InlineNotice kind="error">{error}</InlineNotice>}
204
+ </>
205
+ )}
206
+ </div>
207
+
208
+ <DialogFooter className="flex-row items-center justify-end gap-2 bg-card p-3">
209
+ <Button disabled={busy} onClick={() => onOpenChange(false)} variant="outline">
210
+ Cancel
211
+ </Button>
212
+ <Button disabled={!canFund} onClick={fund}>
213
+ {busy ? (
214
+ <span className="flex items-center gap-2">
215
+ <GlyphSpinner /> Funding Pod…
216
+ </span>
217
+ ) : (
218
+ `Fund $${Number.isFinite(amountNum) ? amountNum.toFixed(0) : '0'} & use Pod`
219
+ )}
220
+ </Button>
221
+ </DialogFooter>
222
+ </>
223
+ )}
224
+ </DialogContent>
225
+ </Dialog>
226
+ )
227
+ }
@@ -109,10 +109,10 @@ export type {
109
109
  export class HermesGateway extends JsonRpcGatewayClient {
110
110
  constructor() {
111
111
  super({
112
- closedErrorMessage: 'Hermes gateway connection closed',
113
- connectErrorMessage: 'Could not connect to Hermes gateway',
112
+ closedErrorMessage: 'Claw Agent gateway connection closed',
113
+ connectErrorMessage: 'Could not connect to Claw Agent gateway',
114
114
  createRequestId: nextId => nextId,
115
- notConnectedErrorMessage: 'Hermes gateway is not connected',
115
+ notConnectedErrorMessage: 'Claw Agent gateway is not connected',
116
116
  requestTimeoutMs: DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS
117
117
  })
118
118
  }
@@ -700,6 +700,112 @@ export function setGlobalModel(
700
700
  })
701
701
  }
702
702
 
703
+ export interface PodWallet {
704
+ agent_id: string
705
+ name?: string | null
706
+ avatar_url?: string | null
707
+ token_mint?: string | null
708
+ wallet_address: string | null
709
+ usdc_balance: number | null
710
+ sol_balance?: number | null
711
+ updated_at?: string
712
+ }
713
+
714
+ export interface WalletTransferInput {
715
+ agent_id: string
716
+ to: string
717
+ amount: number
718
+ token: 'SOL' | 'USDC'
719
+ add_to_whitelist?: boolean
720
+ label?: string
721
+ }
722
+
723
+ /** Transfer SOL/USDC from an agent wallet (whitelist-gated; irreversible). */
724
+ export function transferWallet(
725
+ body: WalletTransferInput
726
+ ): Promise<{ ok: boolean; error?: string; code?: string; result?: unknown }> {
727
+ return window.hermesDesktop.api<{ ok: boolean; error?: string; code?: string; result?: unknown }>({
728
+ ...profileScoped(),
729
+ path: '/api/wallet/transfer',
730
+ method: 'POST',
731
+ body
732
+ })
733
+ }
734
+
735
+ export interface X402Pricing {
736
+ network?: string
737
+ asset?: string
738
+ scheme?: string
739
+ priceUsdc?: number | null
740
+ priceLabel?: string
741
+ }
742
+
743
+ export interface X402Result {
744
+ resourceUrl?: string
745
+ name?: string
746
+ description?: string
747
+ category?: string
748
+ method?: string
749
+ host?: string
750
+ match?: string
751
+ qualityScore?: number | null
752
+ verified?: boolean
753
+ pricing?: X402Pricing[]
754
+ }
755
+
756
+ /** Search the Dexter x402 bazaar via the ClawPump MCP. */
757
+ export function searchX402(
758
+ query: string
759
+ ): Promise<{ ok: boolean; error?: string; query?: string; results: X402Result[] }> {
760
+ return window.hermesDesktop.api<{ ok: boolean; error?: string; query?: string; results: X402Result[] }>({
761
+ ...profileScoped(),
762
+ path: `/api/x402/search?q=${encodeURIComponent(query)}`
763
+ })
764
+ }
765
+
766
+ /** Whether a Pod is configured as the provider, with its remaining balance. */
767
+ export function getPodStatus(): Promise<{ connected: boolean; balance_usdc?: number | null }> {
768
+ return window.hermesDesktop.api<{ connected: boolean; balance_usdc?: number | null }>({
769
+ ...profileScoped(),
770
+ path: '/api/clawpump/pod/status'
771
+ })
772
+ }
773
+
774
+ /** ClawPump agent wallets (id + name + USDC balance) for the Pod funding picker. */
775
+ export function getPodWallets(): Promise<{ ok: boolean; wallets: PodWallet[]; error?: string }> {
776
+ return window.hermesDesktop.api<{ ok: boolean; wallets: PodWallet[]; error?: string }>({
777
+ ...profileScoped(),
778
+ path: '/api/wallet/balances'
779
+ })
780
+ }
781
+
782
+ /**
783
+ * Provision a UsePod "Pod" funded from the chosen ClawPump agent wallet and
784
+ * switch the session onto it as the provider. Spends real on-chain USDC — the
785
+ * caller must confirm the amount first.
786
+ */
787
+ export function provisionPod(
788
+ agentId: string,
789
+ amount: number
790
+ ): Promise<{ ok: boolean; model?: string; signature?: string; funding_error?: string; error?: string }> {
791
+ return window.hermesDesktop.api<{
792
+ ok: boolean
793
+ model?: string
794
+ signature?: string
795
+ funding_error?: string
796
+ error?: string
797
+ }>({
798
+ ...profileScoped(),
799
+ path: '/api/clawpump/pod/provision',
800
+ method: 'POST',
801
+ body: { agent_id: agentId, amount },
802
+ // Register + on-chain USDC deposit can take 30-90s; the default 15s fetch
803
+ // timeout would abort a successful (money-spent) provision. Match the
804
+ // backend's 120s MCP timeout so the UI waits for the real result.
805
+ timeoutMs: 120_000
806
+ })
807
+ }
808
+
703
809
  export function getAuxiliaryModels(): Promise<AuxiliaryModelsResponse> {
704
810
  return window.hermesDesktop.api<AuxiliaryModelsResponse>({
705
811
  ...profileScoped(),