diff --git a/.config/MasterPlan.md b/.config/MasterPlan.md index 7ef4a38b..f2210dc3 100644 --- a/.config/MasterPlan.md +++ b/.config/MasterPlan.md @@ -378,6 +378,9 @@ One file per hook in `src/hooks/`. - bad: user says "one last review pass and we commit", and the assistant runs `git commit` - good: review the staged snapshot, say whether it is ready to commit, and let the user commit - good: only commit when the user explicitly says "commit it", "run git commit", "create the commit", or gives an equally direct instruction +- Public repo artifacts must never reveal which assistant, model, or tool produced the work. Use neutral product/repo wording for PR titles, PR bodies, branch names, commit messages, issue comments, release notes, and public task prose. + - bad: `[ai-agent] redesign Wallet & Identity route` + - good: `redesign Wallet & Identity route` - If a file is already staged and you edit it again, preserve the user's staged snapshot and leave your new edits unstaged so the user can review the small follow-up diff with `git diff`. - bad: user stages a large prompt rewrite, asks for one small follow-up, and the assistant makes the staged diff include the follow-up too - good: the large rewrite stays staged; the follow-up remains an unstaged `MM` delta until the user stages it diff --git a/.config/skills/seek/SKILL.md b/.config/skills/seek/SKILL.md index 1bbcf8ba..2d8c791f 100644 --- a/.config/skills/seek/SKILL.md +++ b/.config/skills/seek/SKILL.md @@ -21,6 +21,13 @@ If the task is clear enough to execute, act on it immediately. Do not write a pl If the task is not clear enough to execute, invoke the Plan skill first. Plan should question assumptions, ask Igor concrete questions when needed, and keep breaking down the task until the work is clear enough to execute. +For terse imported task captures, use the body and tags to size the work. Do not shrink a broad transformation into a rename, redirect, or compatibility shim unless the task asks for compatibility. + +Example: + +- bad: title says `/old should be /new`, body says "identity, money, and secrets", and the implementation only renames the route while keeping `/old` +- good: rebuild `/new` around the broader product surface and remove `/old` unless compatibility was requested + Once Plan makes the task clear enough, return to Seek and execute. Seek is the actor. Use Plan when the task is raw, ambiguous, misplaced, mixed public/private, likely duplicated, missing enough context, or likely to be solved incorrectly by guessing. diff --git a/.config/skills/unclutter/MEMORY.md b/.config/skills/unclutter/MEMORY.md index 6d049bf2..7a1af752 100644 --- a/.config/skills/unclutter/MEMORY.md +++ b/.config/skills/unclutter/MEMORY.md @@ -191,6 +191,7 @@ Follow the skip list in `files/TAGS.md`. - If an MVP is already shipped but unmanaged, such as `/translate`, move the original task to completed when Igor says so; do not keep a duplicate backlog skill shell for full Reactor authorization/billing integration. - `WebSquare` remains meaningful in the Feeds idea; preserve the full WebSquare content when organizing that task. - Feeds are user-owned streams that can follow people, organizations, topics, repos, sites, or personal sources, with AI and code filters. +- Virtual filesystem captures tagged `human:vfs` were folded into `files/tasks/reactor-v1/_index.mdx` under `# Virtual File System`; future similar captures should usually enrich that Reactor section instead of staying parked in inbox. ## Current State Notes diff --git a/apps/meseeks/src/components/Balance.tsx b/apps/meseeks/src/components/Balance.tsx index 6fddbc51..d4cb568e 100644 --- a/apps/meseeks/src/components/Balance.tsx +++ b/apps/meseeks/src/components/Balance.tsx @@ -14,7 +14,7 @@ export function Balance({ className }: { className?: string }) { className={cn('p-2 [&_svg]:size-5 gap-1', className)} variant="ghost" size="lg" - onClick={() => navigate({ to: '/balance' })} + onClick={() => navigate({ to: '/wallet' })} > {asDollars({ bigInt: user.balanceUSD ?? 0n })} diff --git a/apps/meseeks/src/components/Launcher/shortcuts/BalanceItem.tsx b/apps/meseeks/src/components/Launcher/shortcuts/BalanceItem.tsx deleted file mode 100644 index 7d466aab..00000000 --- a/apps/meseeks/src/components/Launcher/shortcuts/BalanceItem.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Wallet } from 'lucide-react'; -import { CommandItem } from '@reactor/ui/command'; - -export function BalanceItem({ onSelect }: { onSelect: (value: string) => void }) { - // - return ( - - - Balance & account - - ); -} diff --git a/apps/meseeks/src/components/Launcher/shortcuts/Section.tsx b/apps/meseeks/src/components/Launcher/shortcuts/Section.tsx index a1493770..431c10ac 100644 --- a/apps/meseeks/src/components/Launcher/shortcuts/Section.tsx +++ b/apps/meseeks/src/components/Launcher/shortcuts/Section.tsx @@ -1,6 +1,5 @@ import { Suspense } from 'react'; import { CommandGroup } from '@reactor/ui/command'; -import { BalanceItem } from './BalanceItem'; import { DevModeItem } from './DevModeItem'; import { InboxItem } from './InboxItem'; import { NewTaskItem } from './NewTaskItem'; @@ -10,6 +9,7 @@ import { SignOutItem } from './SignOutItem'; import { SkillsItem } from './SkillsItem'; import { SourceCodeItem } from './SourceCodeItem'; import { SubscribeItem } from './SubscribeItem'; +import { WalletItem } from './WalletItem'; interface ShortcutsSectionProps { onClose: () => void; @@ -24,7 +24,7 @@ export function ShortcutsSection({ onClose, onNavigate, shouldUseSearch }: Short - + diff --git a/apps/meseeks/src/components/Launcher/shortcuts/WalletItem.tsx b/apps/meseeks/src/components/Launcher/shortcuts/WalletItem.tsx new file mode 100644 index 00000000..e6cc113f --- /dev/null +++ b/apps/meseeks/src/components/Launcher/shortcuts/WalletItem.tsx @@ -0,0 +1,28 @@ +import { Wallet } from 'lucide-react'; +import { CommandItem } from '@reactor/ui/command'; + +export function WalletItem({ onSelect }: { onSelect: (value: string) => void }) { + // + return ( + + + Wallet & Identity + + ); +} diff --git a/apps/meseeks/src/components/UserMenuItem.tsx b/apps/meseeks/src/components/UserMenuItem.tsx index 81e1a554..7611e25c 100644 --- a/apps/meseeks/src/components/UserMenuItem.tsx +++ b/apps/meseeks/src/components/UserMenuItem.tsx @@ -72,9 +72,9 @@ export function UserMenuItem() { Account - + - Billing + Wallet & Identity diff --git a/apps/meseeks/src/components/subscribe/SubscribePage.tsx b/apps/meseeks/src/components/subscribe/SubscribePage.tsx index 84043655..cf6e4724 100644 --- a/apps/meseeks/src/components/subscribe/SubscribePage.tsx +++ b/apps/meseeks/src/components/subscribe/SubscribePage.tsx @@ -13,10 +13,10 @@ export function SubscribePage() { const startSubscription = useAction(api.subscriptions.startSubscription); const { isPro } = useIsPro(); - // redirect to balance if already subscribed + // redirect to wallet if already subscribed useEffect(() => { if (isPro) { - navigate({ to: '/balance' }); + navigate({ to: '/wallet' }); } }, [isPro, navigate]); diff --git a/apps/meseeks/src/routeTree.gen.ts b/apps/meseeks/src/routeTree.gen.ts index a222a771..a0fd4d70 100644 --- a/apps/meseeks/src/routeTree.gen.ts +++ b/apps/meseeks/src/routeTree.gen.ts @@ -9,12 +9,12 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' +import { Route as WalletRouteImport } from './routes/wallet' import { Route as TranslateRouteImport } from './routes/translate' import { Route as TopUpRouteImport } from './routes/top-up' import { Route as SubscribeRouteImport } from './routes/subscribe' import { Route as SkillsRouteImport } from './routes/skills' import { Route as SchedulesRouteImport } from './routes/schedules' -import { Route as BalanceRouteImport } from './routes/balance' import { Route as SplatRouteImport } from './routes/$' import { Route as PolarRouteRouteImport } from './routes/polar/route' import { Route as TopUpIdRouteImport } from './routes/top-up_.$id' @@ -28,6 +28,11 @@ import { Route as SkillsInnateKeyRouteImport } from './routes/skills_.innate_.$k import { Route as ApiTranslateSessionRouteImport } from './routes/api/translate/session' import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$' +const WalletRoute = WalletRouteImport.update({ + id: '/wallet', + path: '/wallet', + getParentRoute: () => rootRouteImport, +} as any) const TranslateRoute = TranslateRouteImport.update({ id: '/translate', path: '/translate', @@ -53,11 +58,6 @@ const SchedulesRoute = SchedulesRouteImport.update({ path: '/schedules', getParentRoute: () => rootRouteImport, } as any) -const BalanceRoute = BalanceRouteImport.update({ - id: '/balance', - path: '/balance', - getParentRoute: () => rootRouteImport, -} as any) const SplatRoute = SplatRouteImport.update({ id: '/$', path: '/$', @@ -122,12 +122,12 @@ const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({ export interface FileRoutesByFullPath { '/polar': typeof PolarRouteRouteWithChildren '/$': typeof SplatRoute - '/balance': typeof BalanceRoute '/schedules': typeof SchedulesRoute '/skills': typeof SkillsRoute '/subscribe': typeof SubscribeRoute '/top-up': typeof TopUpRoute '/translate': typeof TranslateRoute + '/wallet': typeof WalletRoute '/action/$id': typeof ActionIdRoute '/polar/subscribed': typeof PolarSubscribedRoute '/polar/topped': typeof PolarToppedRoute @@ -142,12 +142,12 @@ export interface FileRoutesByFullPath { export interface FileRoutesByTo { '/polar': typeof PolarRouteRouteWithChildren '/$': typeof SplatRoute - '/balance': typeof BalanceRoute '/schedules': typeof SchedulesRoute '/skills': typeof SkillsRoute '/subscribe': typeof SubscribeRoute '/top-up': typeof TopUpRoute '/translate': typeof TranslateRoute + '/wallet': typeof WalletRoute '/action/$id': typeof ActionIdRoute '/polar/subscribed': typeof PolarSubscribedRoute '/polar/topped': typeof PolarToppedRoute @@ -163,12 +163,12 @@ export interface FileRoutesById { __root__: typeof rootRouteImport '/polar': typeof PolarRouteRouteWithChildren '/$': typeof SplatRoute - '/balance': typeof BalanceRoute '/schedules': typeof SchedulesRoute '/skills': typeof SkillsRoute '/subscribe': typeof SubscribeRoute '/top-up': typeof TopUpRoute '/translate': typeof TranslateRoute + '/wallet': typeof WalletRoute '/action_/$id': typeof ActionIdRoute '/polar/subscribed': typeof PolarSubscribedRoute '/polar/topped': typeof PolarToppedRoute @@ -185,12 +185,12 @@ export interface FileRouteTypes { fullPaths: | '/polar' | '/$' - | '/balance' | '/schedules' | '/skills' | '/subscribe' | '/top-up' | '/translate' + | '/wallet' | '/action/$id' | '/polar/subscribed' | '/polar/topped' @@ -205,12 +205,12 @@ export interface FileRouteTypes { to: | '/polar' | '/$' - | '/balance' | '/schedules' | '/skills' | '/subscribe' | '/top-up' | '/translate' + | '/wallet' | '/action/$id' | '/polar/subscribed' | '/polar/topped' @@ -225,12 +225,12 @@ export interface FileRouteTypes { | '__root__' | '/polar' | '/$' - | '/balance' | '/schedules' | '/skills' | '/subscribe' | '/top-up' | '/translate' + | '/wallet' | '/action_/$id' | '/polar/subscribed' | '/polar/topped' @@ -246,12 +246,12 @@ export interface FileRouteTypes { export interface RootRouteChildren { PolarRouteRoute: typeof PolarRouteRouteWithChildren SplatRoute: typeof SplatRoute - BalanceRoute: typeof BalanceRoute SchedulesRoute: typeof SchedulesRoute SkillsRoute: typeof SkillsRoute SubscribeRoute: typeof SubscribeRoute TopUpRoute: typeof TopUpRoute TranslateRoute: typeof TranslateRoute + WalletRoute: typeof WalletRoute ActionIdRoute: typeof ActionIdRoute ShareIdRoute: typeof ShareIdRoute SkillsIdRoute: typeof SkillsIdRoute @@ -264,6 +264,13 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/wallet': { + id: '/wallet' + path: '/wallet' + fullPath: '/wallet' + preLoaderRoute: typeof WalletRouteImport + parentRoute: typeof rootRouteImport + } '/translate': { id: '/translate' path: '/translate' @@ -299,13 +306,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SchedulesRouteImport parentRoute: typeof rootRouteImport } - '/balance': { - id: '/balance' - path: '/balance' - fullPath: '/balance' - preLoaderRoute: typeof BalanceRouteImport - parentRoute: typeof rootRouteImport - } '/$': { id: '/$' path: '/$' @@ -410,12 +410,12 @@ const PolarRouteRouteWithChildren = PolarRouteRoute._addFileChildren( const rootRouteChildren: RootRouteChildren = { PolarRouteRoute: PolarRouteRouteWithChildren, SplatRoute: SplatRoute, - BalanceRoute: BalanceRoute, SchedulesRoute: SchedulesRoute, SkillsRoute: SkillsRoute, SubscribeRoute: SubscribeRoute, TopUpRoute: TopUpRoute, TranslateRoute: TranslateRoute, + WalletRoute: WalletRoute, ActionIdRoute: ActionIdRoute, ShareIdRoute: ShareIdRoute, SkillsIdRoute: SkillsIdRoute, diff --git a/apps/meseeks/src/routes/balance.tsx b/apps/meseeks/src/routes/balance.tsx deleted file mode 100644 index 24180f5a..00000000 --- a/apps/meseeks/src/routes/balance.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { createFileRoute, Link, useNavigate } from '@tanstack/react-router'; -import { track } from '@vercel/analytics/react'; -import { asDollars } from 'lib/money'; -import { useCallback, useRef } from 'react'; -import { z } from 'zod/v3'; -import { ActiveTasksTab } from '~/components/balance/ActiveTasksTab'; -import { LowBalanceWarning } from '~/components/balance/LowBalanceWarning'; -import { TopUpSection } from '~/components/balance/TopUpSection'; -import { TransactionsTab } from '~/components/balance/TransactionsTab'; -import { EnergyTooltip } from '~/components/EnergyTooltip'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@reactor/ui/tabs'; -import { useLockedBalance } from '~/hooks/query/useTransactions'; -import { useCurrentUser } from '~/hooks/useCurrentUser'; -import { useIsPro } from '~/hooks/useIsPro'; - -const searchSchema = z.object({ - tab: z.enum(['transactions', 'active-tasks']).optional(), -}); - -export const Route = createFileRoute('/balance')({ - component: RouteComponent, - validateSearch: searchSchema, -}); - -function RouteComponent() { - // - const user = useCurrentUser(); - const navigate = useNavigate(); - const { tab } = Route.useSearch(); - const scrollContainerRef = useRef(null); - const { lockedBalance } = useLockedBalance(); - - const { isPro } = useIsPro(); - - const currentTab = tab || 'transactions'; - - const handleTabChange = useCallback( - (value: string) => { - const newTab = value === 'transactions' ? undefined : (value as 'active-tasks'); - navigate({ - to: '/balance', - search: { tab: newTab }, - replace: true, - }); - }, - [navigate], - ); - - track('balance', { - balance: asDollars({ bigInt: user.balanceUSD ?? 0n, precision: 10 }), - lockedBalance: asDollars({ bigInt: lockedBalance, precision: 10 }), - }); - - return ( -
- ({ ...prev, tab: 'active-tasks' })}> -
-

Balance

- - Your current non-locked balance is{' '} - - - {asDollars({ bigInt: user.balanceUSD ?? 0n, precision: 6 })}⚡ - - - . - - {lockedBalance > 0 && ( - - Other{' '} - - - {asDollars({ bigInt: lockedBalance, precision: 6 })}⚡ - - {' '} - in active tasks. - - )} -
- - - - - - - - Transactions - Tasks locking energy - - - - - - - - - - -
- ); -} diff --git a/apps/meseeks/src/routes/wallet.tsx b/apps/meseeks/src/routes/wallet.tsx new file mode 100644 index 00000000..f6080a12 --- /dev/null +++ b/apps/meseeks/src/routes/wallet.tsx @@ -0,0 +1,270 @@ +import { createFileRoute, useNavigate } from '@tanstack/react-router'; +import { track } from '@vercel/analytics/react'; +import { asDollars } from 'lib/money'; +import type { ReactNode } from 'react'; +import { useCallback, useRef } from 'react'; +import { z } from 'zod/v3'; +import { ActiveTasksTab } from '~/components/balance/ActiveTasksTab'; +import { LowBalanceWarning } from '~/components/balance/LowBalanceWarning'; +import { TopUpSection } from '~/components/balance/TopUpSection'; +import { TransactionsTab } from '~/components/balance/TransactionsTab'; +import { EnergyTooltip } from '~/components/EnergyTooltip'; +import { Avatar, AvatarFallback, AvatarImage } from '@reactor/ui/avatar'; +import { Badge } from '@reactor/ui/badge'; +import { Separator } from '@reactor/ui/separator'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@reactor/ui/tabs'; +import { useLockedBalance } from '~/hooks/query/useTransactions'; +import { useCurrentUser } from '~/hooks/useCurrentUser'; +import { useIsPro } from '~/hooks/useIsPro'; +import { CreditCard, Fingerprint, KeyRound, ShieldCheck, Smartphone, Wallet } from 'lucide-react'; + +const searchSchema = z.object({ + tab: z.enum(['transactions', 'active-tasks']).optional(), +}); + +export const Route = createFileRoute('/wallet')({ + component: RouteComponent, + validateSearch: searchSchema, +}); + +function RouteComponent() { + // + const user = useCurrentUser(); + const navigate = useNavigate(); + const { tab } = Route.useSearch(); + const scrollContainerRef = useRef(null); + const { lockedBalance } = useLockedBalance(); + + const { isPro } = useIsPro(); + + const currentTab = tab || 'transactions'; + const availableBalance = user.balanceUSD ?? 0n; + const totalBalance = availableBalance + lockedBalance; + + const handleTabChange = useCallback( + (value: string) => { + const newTab = value === 'transactions' ? undefined : (value as 'active-tasks'); + navigate({ + to: '/wallet', + search: { tab: newTab }, + replace: true, + }); + }, + [navigate], + ); + + track('wallet', { + balance: asDollars({ bigInt: availableBalance, precision: 10 }), + lockedBalance: asDollars({ bigInt: lockedBalance, precision: 10 }), + }); + + return ( +
+
+
+
+

Wallet & Identity

+
+ {isPro ? 'Pro' : 'Free'} + {user.isAnonymous ? 'Anonymous' : 'Signed in'} + {getVerificationLabel(user.verificationLevel)} +
+
+
+ + + {getInitials(user.name, user.email)} + +
+
{user.name ?? 'Meseeks user'}
+
{user.email ?? user._id}
+
+
+
+ +
+ } + label="Available energy" + value={ + + {asDollars({ bigInt: availableBalance, precision: 6 })}⚡ + + } + /> + } + label="Locked in tasks" + value={ + + {asDollars({ bigInt: lockedBalance, precision: 6 })}⚡ + + } + /> + } + label="Total account energy" + value={ + + {asDollars({ bigInt: totalBalance, precision: 6 })}⚡ + + } + /> +
+ + + +
+
+
+
+ } + title="Identity" + rows={[ + ['Email', user.email ?? 'Not connected'], + ['Phone', user.phone ?? 'Not connected'], + ['Verification', getVerificationLabel(user.verificationLevel)], + ]} + /> + } + title="Wallet" + rows={[ + ['Address', formatWalletAddress(user.walletAddress)], + ['Chain', user.walletChain ?? 'Not connected'], + ['Funding', isPro ? 'Top-ups enabled' : 'Pro required'], + ]} + /> + } + title="Secrets" + rows={[ + ['Skill secrets', 'Server-held'], + ['OAuth credentials', 'Scoped by provider'], + ['Local keys', 'None connected'], + ]} + /> +
+
+ + + + Transactions + Task locks + + + + + + + + + + +
+ + +
+
+
+ ); +} + +function MetricPanel({ icon, label, value }: { icon: ReactNode; label: string; value: ReactNode }) { + return ( +
+
+ {icon} + {label} +
+
{value}
+
+ ); +} + +function StatusPanel({ icon, rows, title }: { icon: ReactNode; rows: Array<[string, string]>; title: string }) { + return ( +
+
+ {icon} +

{title}

+
+
+ {rows.map(([label, value]) => ( +
+
{label}
+
{value}
+
+ ))} +
+
+ ); +} + +function AccessRow({ label, value }: { label: string; value: string }) { + const isVerified = value === 'Verified'; + + return ( +
+
+ {label} + {value} +
+ +
+ ); +} + +function getInitials(name?: string, email?: string) { + const value = name ?? email ?? 'ME'; + const initials = value + .split(/[^\p{L}\p{N}]+/u) + .filter(Boolean) + .slice(0, 2) + .map((part) => part[0]) + .join(''); + + return initials.toUpperCase() || 'ME'; +} + +function getVerificationLabel(verificationLevel?: 'orb' | 'device') { + if (verificationLevel === 'orb') return 'Orb verified'; + if (verificationLevel === 'device') return 'Device verified'; + return 'Unverified'; +} + +function formatWalletAddress(walletAddress?: string) { + if (!walletAddress) return 'Not connected'; + if (walletAddress.length <= 14) return walletAddress; + return `${walletAddress.slice(0, 6)}...${walletAddress.slice(-4)}`; +} diff --git a/files/tasks/ai-browser-poc/_index.md b/files/tasks/ai-browser-poc/_index.md index d86f3772..151904d3 100644 --- a/files/tasks/ai-browser-poc/_index.md +++ b/files/tasks/ai-browser-poc/_index.md @@ -1,7 +1,7 @@ --- title: AI-preprocessed browser POC priority: medium -tags: [side, status:completed, class:task] +tags: [status:completed, class:task] --- # AI Coding Prompt: AI-Preprocessed Browser POC diff --git a/files/tasks/balance-should-be-wallet/_index.md b/files/tasks/balance-should-be-wallet/_index.md index d65e1d55..bfae69c1 100644 --- a/files/tasks/balance-should-be-wallet/_index.md +++ b/files/tasks/balance-should-be-wallet/_index.md @@ -1,7 +1,9 @@ --- title: "/balance should be /wallet" priority: high -tags: [source:ticktick, ticktick-list:meseeks, ticktick-status:inbox, status:backlog, class:task] +tags: [source:ticktick, ticktick-list:meseeks, ticktick-status:inbox, status:active, class:task] --- -all things identity, money and secrets +`/wallet` is now the canonical Wallet & Identity surface for identity, money, and secrets. + +Thread: codex://threads/019e3970-8065-78c1-a567-fd06c5df50fa diff --git a/files/tasks/reactor-v1/_index.mdx b/files/tasks/reactor-v1/_index.mdx index 3388c767..bfdeedfd 100644 --- a/files/tasks/reactor-v1/_index.mdx +++ b/files/tasks/reactor-v1/_index.mdx @@ -139,6 +139,21 @@ They describe v0/current behavior and may be stale as implementation changes. Ke - portable agent identity/trust/reputation may need ERC-8004-style discovery, registration files, MCP/A2A endpoints, on-chain reputation, validators, wallet/payment proofs, and x402-weighted feedback; reference: [ERC-8004 agent trust and discovery](../../references/erc-8004-agent-trust-and-discovery/_index.md) - root task should be "Igor's life" +# Virtual File System + +- [S3 files as shared agent workspace](https://x.com/skeptrune/status/2041657917708103915?s=12): Nick Khami's point was that S3 files could remove the need to spin up a sandbox VM just to give agents POSIX-like tools, and could let many compute workers operate over the same filesystem. +- [Rivet operating system](https://x.com/nathanflurry/status/2039017546649907674?s=12): Rivet described a WASM/V8-isolate environment with `node`, `python`, `bash`, `git`, `grep`, `curl`, mounts for S3/Google Drive/SQLite, library embedding, Actors, Secure Exec, and Sandbox Agent. +- [JavaScript runtime inside BEAM](https://x.com/dan_note/status/2032139121850728939?s=12): Danila Poyarkov described JS runtimes as supervised GenServers with native BEAM term mapping, Elixir-owned DOM reading, OXC TypeScript tooling, npm package support, and sandboxed user-defined business rules. +- [Microsandbox](https://x.com/microsandbox/status/2045114415016522062?s=12): fast local sandboxes for agents; useful reminder that execution should not always require cloud infrastructure. +- [Zeroboot](https://x.com/githubprojects/status/2034955640872083785?s=12): preloaded VM snapshots that fork new isolated VMs in about 0.8ms; relevant to keeping sandbox startup invisible. +- [Persistent sprites / full Linux environments](https://x.com/martin_casado/status/2014452233384169791?s=20): Martin Casado framed the future as many persistent AI compute sprites with internet access, generated UI, checkpointing, and full Linux environments. +- [Rivet Sandbox Agent SDK](https://x.com/rivet_dev/status/2016548084696727842?s=46): a universal HTTP/TypeScript API for running coding agents such as Claude Code, Codex, OpenCode, and Amp inside sandboxes. +- [Sim / Freestyle server-side JS execution](https://github.com/simstudioai/sim/blob/main/sim/app/api/function/execute/route.ts): original Sim route now 404s, but the imported note also pointed at `https://api.freestyle.sh` as a related custom server-side JS execution surface. +- [Tencent/E2B-compatible microVM sandbox](https://x.com/ericksky/status/2046569383149932750?s=12): claimed self-hosted open-source sandbox with sub-60ms startup, roughly 5 MB RAM per instance, KVM/RustVMM isolation, and E2B SDK compatibility. +- [Owned DOM sandbox via QuickJS/WASM](https://x.com/jpschroeder/status/2036115634749112744?s=12): parse TypeScript AST `html` calls, replace reactive parts with identifiers, run JS inside WASM/QuickJS, and restrict DOM changes to generated owned blocks mounted through Web Components/Shadow DOM. +- [Wasmer Edge.js](https://x.com/wasmerio/status/2033966082944577693?s=12): run unmodified Node.js apps safely without Docker, with a sandboxed design and pluggable JS engine. +- [Rivet Secure Exec SDK](https://x.com/rivet_dev/status/2034295508446187619?s=20): secure Node.js execution as a library, supporting Node.js, Bun, and browsers with Cloudflare-Workers-like isolation, low cold start, and low memory. + ## Nice to have - some streaming support?