@clawpump/claw-agent 0.1.7 → 0.1.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.
Files changed (64) 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 +65 -65
  10. package/agent/apps/desktop/index.html +1 -1
  11. package/agent/apps/desktop/package.json +11 -11
  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/set-exe-identity.cjs +2 -2
  15. package/agent/apps/desktop/src/app/chat/composer/controls.tsx +2 -0
  16. package/agent/apps/desktop/src/app/chat/composer/index.tsx +10 -0
  17. package/agent/apps/desktop/src/app/chat/composer/pod-credits.tsx +49 -0
  18. package/agent/apps/desktop/src/app/chat/index.tsx +1 -1
  19. package/agent/apps/desktop/src/app/chat/sidebar/index.tsx +4 -2
  20. package/agent/apps/desktop/src/app/desktop-controller.tsx +18 -0
  21. package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-request.ts +1 -1
  22. package/agent/apps/desktop/src/app/messaging/index.tsx +5 -5
  23. package/agent/apps/desktop/src/app/routes.ts +9 -1
  24. package/agent/apps/desktop/src/app/session/hooks/use-message-stream.ts +3 -3
  25. package/agent/apps/desktop/src/app/settings/constants.ts +5 -5
  26. package/agent/apps/desktop/src/app/settings/model-settings.tsx +1 -1
  27. package/agent/apps/desktop/src/app/settings/providers-settings.tsx +46 -1
  28. package/agent/apps/desktop/src/app/settings/uninstall-section.tsx +5 -5
  29. package/agent/apps/desktop/src/app/types.ts +9 -1
  30. package/agent/apps/desktop/src/app/wallet/index.tsx +244 -0
  31. package/agent/apps/desktop/src/app/x402/index.tsx +162 -0
  32. package/agent/apps/desktop/src/components/assistant-ui/thread.tsx +1 -1
  33. package/agent/apps/desktop/src/components/brand-mark.tsx +2 -2
  34. package/agent/apps/desktop/src/components/chat/intro-copy.jsonl +6 -6
  35. package/agent/apps/desktop/src/components/chat/intro.tsx +4 -4
  36. package/agent/apps/desktop/src/components/model-picker.tsx +64 -4
  37. package/agent/apps/desktop/src/components/pod-setup-dialog.tsx +227 -0
  38. package/agent/apps/desktop/src/hermes.ts +109 -3
  39. package/agent/apps/desktop/src/i18n/en.ts +80 -78
  40. package/agent/apps/desktop/src/i18n/ja.ts +82 -82
  41. package/agent/apps/desktop/src/i18n/runtime.test.ts +2 -2
  42. package/agent/apps/desktop/src/i18n/zh-hant.ts +82 -82
  43. package/agent/apps/desktop/src/i18n/zh.ts +87 -87
  44. package/agent/apps/desktop/src/lib/desktop-fs.ts +1 -1
  45. package/agent/apps/desktop/src/lib/desktop-slash-commands.ts +4 -4
  46. package/agent/apps/desktop/src/store/composer.ts +7 -0
  47. package/agent/apps/desktop/src/store/onboarding.ts +5 -5
  48. package/agent/apps/desktop/src/themes/presets.ts +54 -54
  49. package/agent/cli.py +184 -10
  50. package/agent/hermes_cli/distribution.py +188 -8
  51. package/agent/hermes_cli/providers.py +29 -0
  52. package/agent/hermes_cli/web_server.py +403 -34
  53. package/agent/plugins/model-providers/usepod/__init__.py +7 -1
  54. package/agent/scripts/release.py +1 -0
  55. package/agent/web/public/claw-logo.png +0 -0
  56. package/agent/web/src/App.tsx +6 -4
  57. package/agent/web/src/components/ChatSidebar.tsx +5 -0
  58. package/agent/web/src/components/ModelPickerDialog.tsx +28 -1
  59. package/agent/web/src/components/PodCredits.tsx +57 -0
  60. package/agent/web/src/components/PodSetupDialog.tsx +240 -0
  61. package/agent/web/src/lib/api.ts +135 -0
  62. package/agent/web/src/pages/AgentMailPage.tsx +684 -0
  63. package/agent/web/src/pages/WalletPage.tsx +53 -5
  64. package/package.json +1 -1
@@ -0,0 +1,57 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { api } from "@/lib/api";
3
+
4
+ /**
5
+ * Compact "Pod $X.XX" credits pill, shown next to the model badge only when the
6
+ * active provider is UsePod (Pod). Polls /api/clawpump/pod/status; keeps the
7
+ * last value so a transient probe failure never blanks it. Mirrors the desktop
8
+ * PodCredits.
9
+ */
10
+ export default function PodCredits({ provider }: { provider: string }) {
11
+ const isPod = provider === "usepod";
12
+ const [balance, setBalance] = useState<number | null>(null);
13
+ const [connected, setConnected] = useState(false);
14
+ const lastBalance = useRef<number | null>(null);
15
+
16
+ useEffect(() => {
17
+ if (!isPod) return;
18
+ let cancelled = false;
19
+ const tick = () => {
20
+ void api
21
+ .getPodStatus()
22
+ .then((r) => {
23
+ if (cancelled) return;
24
+ setConnected(!!r.connected);
25
+ if (r.balance_usdc != null) {
26
+ lastBalance.current = r.balance_usdc;
27
+ setBalance(r.balance_usdc);
28
+ } else if (lastBalance.current != null) {
29
+ setBalance(lastBalance.current);
30
+ }
31
+ })
32
+ .catch(() => undefined);
33
+ };
34
+ tick();
35
+ const id = window.setInterval(tick, 60_000);
36
+ return () => {
37
+ cancelled = true;
38
+ window.clearInterval(id);
39
+ };
40
+ }, [isPod]);
41
+
42
+ if (!isPod || !connected) return null;
43
+
44
+ const low = balance != null && balance < 0.5;
45
+ return (
46
+ <span
47
+ title={balance != null ? `Pod credits: $${balance.toFixed(4)} USDC` : "Pod connected"}
48
+ className={`inline-flex shrink-0 items-center gap-1 rounded-md border px-1.5 py-0.5 text-[0.6875rem] font-medium ${
49
+ low
50
+ ? "border-amber-500/40 bg-amber-500/10 text-amber-300"
51
+ : "border-emerald-500/30 bg-emerald-500/10 text-emerald-300"
52
+ }`}
53
+ >
54
+ ⚡ {balance != null ? `$${balance.toFixed(2)}` : "Pod"}
55
+ </span>
56
+ );
57
+ }
@@ -0,0 +1,240 @@
1
+ import { useEffect, useMemo, useRef, useState } from "react";
2
+ import { Zap } from "lucide-react";
3
+ import { api } from "@/lib/api";
4
+ import type { AgentWalletBalance } from "@/lib/api";
5
+ import { Button } from "@nous-research/ui/ui/components/button";
6
+ import { Input } from "@nous-research/ui/ui/components/input";
7
+ import { Select, SelectOption } from "@nous-research/ui/ui/components/select";
8
+ import { Spinner } from "@nous-research/ui/ui/components/spinner";
9
+
10
+ const DEFAULT_AMOUNT = "5";
11
+
12
+ const walletLabel = (w: AgentWalletBalance): string =>
13
+ w.name ||
14
+ (w.wallet_address
15
+ ? `${w.wallet_address.slice(0, 4)}…${w.wallet_address.slice(-4)}`
16
+ : w.agent_id);
17
+
18
+ /**
19
+ * One-screen Pod setup for the web dashboard: pick a ClawPump agent wallet, pick
20
+ * an amount, confirm. `usepod_provision` registers + funds a pod from that wallet
21
+ * on-chain in one call, and the backend switches the session onto Pod as the
22
+ * provider. The single confirm is the on-chain USDC spend; everything else is
23
+ * prefilled. Mirrors the desktop PodSetupDialog.
24
+ */
25
+ export default function PodSetupDialog({
26
+ onClose,
27
+ onProvisioned,
28
+ }: {
29
+ onClose: () => void;
30
+ onProvisioned: (model: string) => void;
31
+ }) {
32
+ const [wallets, setWallets] = useState<AgentWalletBalance[] | null>(null);
33
+ const [loadError, setLoadError] = useState<string | null>(null);
34
+ const [agentId, setAgentId] = useState("");
35
+ const [amount, setAmount] = useState(DEFAULT_AMOUNT);
36
+ const [busy, setBusy] = useState(false);
37
+ const [error, setError] = useState<string | null>(null);
38
+ const [done, setDone] = useState<{
39
+ model: string;
40
+ amount: number;
41
+ signature?: string;
42
+ fundingError?: string;
43
+ } | null>(null);
44
+
45
+ useEffect(() => {
46
+ let cancelled = false;
47
+ void api
48
+ .getWalletBalances()
49
+ .then((r) => {
50
+ if (cancelled) return;
51
+ const rows = (r.wallets ?? []).filter((w) => w.agent_id);
52
+ setWallets(rows);
53
+ if (rows.length > 0) {
54
+ const best = [...rows].sort(
55
+ (a, b) => (b.usdc_balance ?? 0) - (a.usdc_balance ?? 0),
56
+ )[0];
57
+ setAgentId(best.agent_id);
58
+ }
59
+ })
60
+ .catch((e) => !cancelled && setLoadError(e instanceof Error ? e.message : "Failed to load wallets"));
61
+ return () => {
62
+ cancelled = true;
63
+ };
64
+ }, []);
65
+
66
+ const rows = wallets ?? [];
67
+ const selected = rows.find((w) => w.agent_id === agentId) ?? null;
68
+ const amountNum = Number(amount);
69
+ const balance = selected?.usdc_balance ?? 0;
70
+ const insufficient = Number.isFinite(amountNum) && amountNum > 0 && amountNum > balance;
71
+ const canFund =
72
+ !busy && Boolean(agentId) && Number.isFinite(amountNum) && amountNum > 0 && !insufficient;
73
+
74
+ const heading = useMemo(() => (done ? "Pod ready" : "Set up Pod"), [done]);
75
+
76
+ // Ref guard: setBusy is async, so a fast double-click could fire two
77
+ // provisions (= double on-chain spend) before the disabled state re-renders.
78
+ const submitting = useRef(false);
79
+
80
+ const fund = async () => {
81
+ if (!canFund || submitting.current) return;
82
+ submitting.current = true;
83
+ setBusy(true);
84
+ setError(null);
85
+ try {
86
+ const res = await api.provisionPod(agentId, amountNum);
87
+ if (!res.ok || !res.model) {
88
+ setError(res.error || res.funding_error || "Pod setup failed. Check the wallet balance and try again.");
89
+ return;
90
+ }
91
+ // Show the "Pod ready" view; the actual session switch + close happens on
92
+ // Done (onProvisioned), because the switch unmounts this dialog.
93
+ setDone({
94
+ amount: amountNum,
95
+ model: res.model,
96
+ signature: res.signature,
97
+ fundingError: res.funding_error || undefined,
98
+ });
99
+ } catch (e) {
100
+ setError(e instanceof Error ? e.message : "Pod setup failed.");
101
+ } finally {
102
+ setBusy(false);
103
+ submitting.current = false;
104
+ }
105
+ };
106
+
107
+ return (
108
+ <div
109
+ className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
110
+ onClick={() => {
111
+ if (busy) return;
112
+ // After a successful provision, dismissing applies the switch (same as
113
+ // Done) so a backdrop click doesn't silently skip using the new Pod.
114
+ if (done) onProvisioned(done.model);
115
+ else onClose();
116
+ }}
117
+ >
118
+ <div
119
+ className="w-full max-w-md rounded-lg border border-border bg-card shadow-xl"
120
+ onClick={(e) => e.stopPropagation()}
121
+ >
122
+ <div className="flex items-center gap-2 border-b border-border px-4 py-3">
123
+ <Zap className="h-4 w-4 text-primary" />
124
+ <h2 className="text-sm font-semibold">
125
+ {done?.fundingError ? "Pod created — funding failed" : heading}
126
+ </h2>
127
+ </div>
128
+
129
+ {done ? (
130
+ <div className="flex flex-col gap-3 p-4">
131
+ {done.fundingError ? (
132
+ <div className="rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-3 text-sm">
133
+ <div className="font-semibold text-amber-300">⚠ Pod created but not funded</div>
134
+ <div className="mt-0.5 text-muted-foreground">
135
+ The on-chain deposit didn’t confirm ({done.fundingError}). The pod (
136
+ <span className="font-mono">{done.model}</span>) is selected but has no balance — it
137
+ may settle shortly, or run Set up Pod again to top it up.
138
+ </div>
139
+ </div>
140
+ ) : (
141
+ <div className="rounded-md border border-emerald-500/30 bg-emerald-500/10 px-3 py-3 text-sm">
142
+ <div className="font-semibold text-emerald-300">⚡ Using Pod</div>
143
+ <div className="mt-0.5 text-muted-foreground">
144
+ Funded <span className="font-mono">${done.amount.toFixed(2)} USDC</span> · model{" "}
145
+ <span className="font-mono">{done.model}</span>. New chats run on Pod automatically.
146
+ </div>
147
+ </div>
148
+ )}
149
+ {done.signature && (
150
+ <a
151
+ className="text-xs text-primary hover:underline"
152
+ href={`https://solscan.io/tx/${done.signature}`}
153
+ rel="noreferrer"
154
+ target="_blank"
155
+ >
156
+ View funding transaction ↗
157
+ </a>
158
+ )}
159
+ <div className="flex justify-end">
160
+ <Button onClick={() => onProvisioned(done.model)}>Done</Button>
161
+ </div>
162
+ </div>
163
+ ) : (
164
+ <>
165
+ <div className="flex flex-col gap-4 p-4">
166
+ <p className="text-xs leading-relaxed text-muted-foreground">
167
+ Fund a private inference Pod from a ClawPump agent wallet and use it as your model
168
+ provider. You only pay for what you use — the Pod holds a prepaid USDC balance.
169
+ </p>
170
+
171
+ {wallets === null ? (
172
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
173
+ <Spinner className="text-xs" /> Loading your wallets…
174
+ </div>
175
+ ) : loadError || rows.length === 0 ? (
176
+ <div className="rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-300">
177
+ {loadError ||
178
+ "No ClawPump agent wallets found. Create one in the ClawPump dashboard first."}
179
+ </div>
180
+ ) : (
181
+ <>
182
+ <label className="flex flex-col gap-1.5 text-sm">
183
+ <span className="font-medium">Pay from wallet</span>
184
+ <Select value={agentId} onValueChange={setAgentId}>
185
+ {rows.map((w) => (
186
+ <SelectOption key={w.agent_id} value={w.agent_id}>
187
+ {`${walletLabel(w)} — $${(w.usdc_balance ?? 0).toFixed(2)} USDC`}
188
+ </SelectOption>
189
+ ))}
190
+ </Select>
191
+ </label>
192
+
193
+ <label className="flex flex-col gap-1.5 text-sm">
194
+ <span className="font-medium">Amount to fund (USDC)</span>
195
+ <Input
196
+ type="number"
197
+ min="0"
198
+ step="1"
199
+ value={amount}
200
+ onChange={(e) => setAmount(e.target.value)}
201
+ />
202
+ <span className="text-xs text-muted-foreground">
203
+ Wallet balance: <span className="font-mono">${balance.toFixed(2)} USDC</span>
204
+ </span>
205
+ </label>
206
+
207
+ {insufficient && (
208
+ <div className="rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-300">
209
+ Not enough USDC in this wallet for ${amountNum.toFixed(2)}. Pick a smaller amount.
210
+ </div>
211
+ )}
212
+ {error && (
213
+ <div className="rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive">
214
+ {error}
215
+ </div>
216
+ )}
217
+ </>
218
+ )}
219
+ </div>
220
+
221
+ <div className="flex justify-end gap-2 border-t border-border px-4 py-3">
222
+ <Button variant="outline" disabled={busy} onClick={onClose}>
223
+ Cancel
224
+ </Button>
225
+ <Button disabled={!canFund} onClick={() => void fund()}>
226
+ {busy ? (
227
+ <span className="flex items-center gap-2">
228
+ <Spinner className="text-xs" /> Funding Pod…
229
+ </span>
230
+ ) : (
231
+ `Fund $${Number.isFinite(amountNum) ? amountNum.toFixed(2) : "0.00"} & use Pod`
232
+ )}
233
+ </Button>
234
+ </div>
235
+ </>
236
+ )}
237
+ </div>
238
+ </div>
239
+ );
240
+ }
@@ -313,6 +313,8 @@ function appendProfileParam(url: string, profile?: string): string {
313
313
  export interface AgentWalletBalance {
314
314
  agent_id: string;
315
315
  name?: string | null;
316
+ avatar_url?: string | null;
317
+ token_mint?: string | null;
316
318
  wallet_address: string | null;
317
319
  sol_balance: number | null;
318
320
  usdc_balance: number | null;
@@ -341,6 +343,19 @@ export interface WalletTransferResponse {
341
343
  result?: unknown;
342
344
  }
343
345
 
346
+ export interface PodStatusResponse {
347
+ connected: boolean;
348
+ balance_usdc?: number | null;
349
+ }
350
+
351
+ export interface PodProvisionResponse {
352
+ ok: boolean;
353
+ model?: string;
354
+ signature?: string;
355
+ funding_error?: string;
356
+ error?: string;
357
+ }
358
+
344
359
  export interface X402Pricing {
345
360
  network?: string;
346
361
  asset?: string;
@@ -369,6 +384,86 @@ export interface X402SearchResponse {
369
384
  results: X402Result[];
370
385
  }
371
386
 
387
+ // ── Agent Mail (AgentMail, via the ClawPump MCP) ───────────────────────
388
+ export interface MailInbox {
389
+ id: string;
390
+ agentId: string;
391
+ provider: string;
392
+ inboxId: string;
393
+ emailAddress: string;
394
+ username: string;
395
+ domain: string;
396
+ webhookId: string | null;
397
+ verified: boolean;
398
+ status: string;
399
+ createdAt: string;
400
+ updatedAt: string;
401
+ }
402
+
403
+ export interface MailMessage {
404
+ id: string;
405
+ agentId: string;
406
+ inboxId: string;
407
+ messageId: string;
408
+ threadId: string | null;
409
+ direction: "inbound" | "outbound";
410
+ fromAddress: string | null;
411
+ toAddresses: string[];
412
+ ccAddresses: string[];
413
+ subject: string | null;
414
+ textBody: string | null;
415
+ htmlBody: string | null;
416
+ preview: string | null;
417
+ read: boolean;
418
+ agentmailCreatedAt: string | null;
419
+ createdAt: string;
420
+ }
421
+
422
+ export interface MailAddressResponse {
423
+ ok: boolean;
424
+ error?: string;
425
+ has_inbox: boolean;
426
+ inbox: MailInbox | null;
427
+ }
428
+
429
+ export interface MailMessagesResponse {
430
+ ok: boolean;
431
+ error?: string;
432
+ messages: MailMessage[];
433
+ }
434
+
435
+ export interface MailMessageResponse {
436
+ ok: boolean;
437
+ error?: string;
438
+ message: MailMessage | null;
439
+ }
440
+
441
+ export interface MailCreateResponse {
442
+ ok: boolean;
443
+ error?: string;
444
+ inbox?: MailInbox | null;
445
+ alreadyExisted?: boolean;
446
+ note?: string | null;
447
+ }
448
+
449
+ export interface MailSendBody {
450
+ agent_id: string;
451
+ to: string[];
452
+ subject: string;
453
+ text?: string;
454
+ html?: string;
455
+ cc?: string[];
456
+ bcc?: string[];
457
+ reply_to?: string;
458
+ confirm: boolean;
459
+ }
460
+
461
+ export interface MailSendResponse {
462
+ ok: boolean;
463
+ error?: string;
464
+ result?: unknown;
465
+ }
466
+
372
467
  export const api = {
373
468
  getStatus: () => fetchJSON<StatusResponse>("/api/status"),
374
469
  getWalletBalances: () =>
@@ -381,6 +476,46 @@ export const api = {
381
476
  headers: { "Content-Type": "application/json" },
382
477
  body: JSON.stringify(body),
383
478
  }),
479
+
480
+ // ── UsePod "Pod" — pay-as-you-go inference funded from a ClawPump wallet ──
481
+ getPodStatus: () =>
482
+ fetchJSON<PodStatusResponse>("/api/clawpump/pod/status"),
483
+ provisionPod: (agentId: string, amount: number) =>
484
+ fetchJSON<PodProvisionResponse>("/api/clawpump/pod/provision", {
485
+ method: "POST",
486
+ headers: { "Content-Type": "application/json" },
487
+ body: JSON.stringify({ agent_id: agentId, amount }),
488
+ }),
489
+
490
+ // ── Agent Mail (AgentMail) ─────────────────────────────────────────
491
+ // Every call carries an explicit agent_id — the MCP requires it once the
492
+ // account has more than one agent.
493
+ getMailAddress: (agentId: string) =>
494
+ fetchJSON<MailAddressResponse>(
495
+ `/api/mail/address?agent_id=${encodeURIComponent(agentId)}`,
496
+ ),
497
+ listMail: (opts: { agentId: string; direction?: "inbound" | "outbound"; limit?: number }) => {
498
+ const qs = new URLSearchParams({ agent_id: opts.agentId });
499
+ if (opts.direction) qs.set("direction", opts.direction);
500
+ if (opts.limit) qs.set("limit", String(opts.limit));
501
+ return fetchJSON<MailMessagesResponse>(`/api/mail/messages?${qs.toString()}`);
502
+ },
503
+ readMail: (messageId: string, agentId: string) =>
504
+ fetchJSON<MailMessageResponse>(
505
+ `/api/mail/message?message_id=${encodeURIComponent(messageId)}&agent_id=${encodeURIComponent(agentId)}`,
506
+ ),
507
+ createInbox: (body: { agent_id: string; username?: string; confirm: boolean }) =>
508
+ fetchJSON<MailCreateResponse>("/api/mail/create", {
509
+ method: "POST",
510
+ headers: { "Content-Type": "application/json" },
511
+ body: JSON.stringify(body),
512
+ }),
513
+ sendMail: (body: MailSendBody) =>
514
+ fetchJSON<MailSendResponse>("/api/mail/send", {
515
+ method: "POST",
516
+ headers: { "Content-Type": "application/json" },
517
+ body: JSON.stringify(body),
518
+ }),
384
519
  /**
385
520
  * Identity probe for the dashboard auth gate (Phase 7).
386
521
  *