From 0a932c8c9193855bcb3f23a7b894d9b6956111a9 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sat, 7 Feb 2026 16:03:27 -0800 Subject: [PATCH 01/34] default apis --- .../apps/web/src/components/tools-view.tsx | 475 ++++++++++++++---- .../web/src/components/ui/collapsible.tsx | 33 ++ 2 files changed, 413 insertions(+), 95 deletions(-) create mode 100644 executor/apps/web/src/components/ui/collapsible.tsx diff --git a/executor/apps/web/src/components/tools-view.tsx b/executor/apps/web/src/components/tools-view.tsx index 8ea3e830f..2d49d01c4 100644 --- a/executor/apps/web/src/components/tools-view.tsx +++ b/executor/apps/web/src/components/tools-view.tsx @@ -11,6 +11,7 @@ import { Server, Zap, Search, + ChevronRight, } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; @@ -31,6 +32,11 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; @@ -43,6 +49,163 @@ import { parse as parseDomain } from "tldts"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; +// ── API Presets ── + +interface ApiPreset { + name: string; + label: string; + description: string; + type: "openapi" | "mcp"; + spec?: string; + url?: string; + baseUrl?: string; + authNote?: string; +} + +const API_PRESETS: ApiPreset[] = [ + { + name: "github", + label: "GitHub", + description: "Repos, issues, PRs, actions, users, orgs", + type: "openapi", + spec: "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.yaml", + baseUrl: "https://api.github.com", + authNote: "Add a bearer credential with a PAT for authenticated access", + }, + { + name: "vercel", + label: "Vercel", + description: "Deployments, projects, domains, env vars, teams", + type: "openapi", + spec: "https://openapi.vercel.sh", + baseUrl: "https://api.vercel.com", + authNote: "Requires API token as bearer credential", + }, + { + name: "slack", + label: "Slack", + description: "Messages, channels, users, reactions, files", + type: "openapi", + spec: "https://api.slack.com/specs/openapi/v2/slack_web.json", + baseUrl: "https://slack.com/api", + authNote: "Requires a bot token as bearer credential", + }, + { + name: "stripe", + label: "Stripe", + description: "Payments, customers, subscriptions, invoices", + type: "openapi", + spec: "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json", + baseUrl: "https://api.stripe.com", + authNote: "Requires API key as bearer credential", + }, + + { + name: "openai", + label: "OpenAI", + description: "Chat completions, embeddings, images, files", + type: "openapi", + spec: "https://app.stainless.com/api/spec/documented/openai/openapi.documented.yml", + baseUrl: "https://api.openai.com", + authNote: "Requires API key as bearer credential", + }, + { + name: "cloudflare", + label: "Cloudflare", + description: "DNS, zones, workers, KV, R2, firewall", + type: "openapi", + spec: "https://raw.githubusercontent.com/cloudflare/api-schemas/main/openapi.yaml", + baseUrl: "https://api.cloudflare.com/client/v4", + authNote: "Requires API token as bearer credential", + }, + { + name: "sentry", + label: "Sentry", + description: "Issues, events, projects, releases, alerts", + type: "openapi", + spec: "https://raw.githubusercontent.com/getsentry/sentry-api-schema/refs/heads/main/openapi-derefed.json", + baseUrl: "https://sentry.io/api/0", + authNote: "Requires auth token as bearer credential", + }, + { + name: "jira", + label: "Jira", + description: "Issues, projects, boards, sprints, users", + type: "openapi", + spec: "https://developer.atlassian.com/cloud/jira/platform/swagger-v3.v3.json", + baseUrl: "https://your-domain.atlassian.net/rest/api/3", + authNote: "Requires API token with basic auth (email:token)", + }, + { + name: "pagerduty", + label: "PagerDuty", + description: "Incidents, services, schedules, escalations", + type: "openapi", + spec: "https://raw.githubusercontent.com/PagerDuty/api-schema/main/reference/REST/openapiv3.json", + baseUrl: "https://api.pagerduty.com", + authNote: "Requires API key as bearer credential", + }, + { + name: "digitalocean", + label: "DigitalOcean", + description: "Droplets, databases, domains, apps, spaces", + type: "openapi", + spec: "https://api-engineering.nyc3.cdn.digitaloceanspaces.com/spec-ci/DigitalOcean-public.v2.yaml", + baseUrl: "https://api.digitalocean.com", + authNote: "Requires API token as bearer credential", + }, + { + name: "twilio", + label: "Twilio", + description: "SMS, calls, conversations, verify, phone numbers", + type: "openapi", + spec: "https://raw.githubusercontent.com/twilio/twilio-oai/main/spec/json/twilio_api_v2010.json", + baseUrl: "https://api.twilio.com", + authNote: "Requires Account SID + Auth Token as basic auth", + }, + { + name: "notion", + label: "Notion", + description: "Pages, databases, blocks, search, users", + type: "openapi", + spec: "https://developers.notion.com/openapi.json", + baseUrl: "https://api.notion.com", + authNote: "Requires integration token as bearer credential", + }, + + { + name: "resend", + label: "Resend", + description: "Send emails, manage domains, API keys", + type: "openapi", + spec: "https://raw.githubusercontent.com/resend/resend-openapi/main/resend.yaml", + baseUrl: "https://api.resend.com", + }, +]; + +/** Derive a favicon URL from any URL string via Google's favicon service. */ +function faviconForUrl(url: string | undefined | null): string | null { + if (!url) return null; + try { + const domain = new URL(url).hostname; + return `https://www.google.com/s2/favicons?domain=${domain}&sz=32`; + } catch { + return null; + } +} + +function getFaviconUrl(preset: ApiPreset): string | null { + return faviconForUrl(preset.baseUrl ?? preset.url); +} + +function getSourceFavicon(source: ToolSourceRecord): string | null { + const url = + source.type === "mcp" + ? (source.config.url as string) + : (source.config.baseUrl as string) ?? (source.config.spec as string); + return faviconForUrl(url); +} + // ── Add Source Dialog ── const RAW_HOSTS = new Set([ @@ -57,14 +220,11 @@ function inferNameFromUrl(url: string): string { const u = new URL(url); const parsed = parseDomain(url); - // For raw/CDN hosts, infer from the first meaningful path segment - // e.g. raw.githubusercontent.com/github/rest-api-description/... -> "github" if (RAW_HOSTS.has(u.hostname)) { const segments = u.pathname.split("/").filter(Boolean); if (segments.length > 0) return segments[0].toLowerCase(); } - // For subdomains like "api.github.com", prefer the domain name if (parsed.domainWithoutSuffix) { return parsed.domainWithoutSuffix; } @@ -79,19 +239,26 @@ function inferNameFromUrl(url: string): string { } } -function AddSourceDialog({ onAdded }: { onAdded: () => void }) { +function AddSourceDialog({ + onAdded, + existingSourceNames, +}: { + onAdded: () => void; + existingSourceNames: Set; +}) { const { context } = useSession(); const [open, setOpen] = useState(false); + const [presetsOpen, setPresetsOpen] = useState(false); const [type, setType] = useState<"mcp" | "openapi">("mcp"); const [name, setName] = useState(""); const [nameManuallyEdited, setNameManuallyEdited] = useState(false); const [endpoint, setEndpoint] = useState(""); const [baseUrl, setBaseUrl] = useState(""); const [submitting, setSubmitting] = useState(false); + const [addingPreset, setAddingPreset] = useState(null); const handleEndpointChange = (value: string) => { setEndpoint(value); - // Auto-infer name from URL if user hasn't manually edited the name if (!nameManuallyEdited) { const inferred = inferNameFromUrl(value); if (inferred) setName(inferred); @@ -103,18 +270,67 @@ function AddSourceDialog({ onAdded }: { onAdded: () => void }) { setNameManuallyEdited(true); }; + const resetForm = () => { + setName(""); + setEndpoint(""); + setBaseUrl(""); + setNameManuallyEdited(false); + setPresetsOpen(false); + setAddingPreset(null); + }; + const handleOpenChange = (isOpen: boolean) => { setOpen(isOpen); - if (!isOpen) { - // Reset on close - setName(""); - setEndpoint(""); - setBaseUrl(""); - setNameManuallyEdited(false); + if (!isOpen) resetForm(); + }; + + const addSource = async ( + sourceName: string, + sourceType: "mcp" | "openapi", + config: Record, + ) => { + if (!context) return; + const result = await api.upsertToolSource({ + workspaceId: context.workspaceId, + name: sourceName, + type: sourceType, + config, + }); + const warnings = (result as unknown as Record) + .warnings as string[] | undefined; + if (warnings && warnings.length > 0) { + toast.warning(`Source "${sourceName}" saved but had issues`, { + description: warnings.join("\n"), + duration: 10000, + }); + } else { + toast.success(`Source "${sourceName}" added`); + } + }; + + const handlePresetAdd = async (preset: ApiPreset) => { + setAddingPreset(preset.name); + try { + const config: Record = + preset.type === "mcp" + ? { url: preset.url } + : { + spec: preset.spec, + ...(preset.baseUrl ? { baseUrl: preset.baseUrl } : {}), + }; + await addSource(preset.name, preset.type, config); + if (preset.authNote) { + toast.info(preset.authNote, { duration: 6000 }); + } + onAdded(); + } catch (err) { + toast.error(err instanceof Error ? err.message : "Failed to add source"); + } finally { + setAddingPreset(null); } }; - const handleSubmit = async () => { + const handleCustomSubmit = async () => { if (!context || !name.trim() || !endpoint.trim()) return; setSubmitting(true); try { @@ -122,26 +338,8 @@ function AddSourceDialog({ onAdded }: { onAdded: () => void }) { type === "mcp" ? { url: endpoint } : { spec: endpoint, ...(baseUrl ? { baseUrl } : {}) }; - - const result = await api.upsertToolSource({ - workspaceId: context.workspaceId, - name: name.trim(), - type, - config, - }); - const warnings = (result as unknown as Record).warnings as string[] | undefined; - if (warnings && warnings.length > 0) { - toast.warning(`Source "${name}" saved but had issues`, { - description: warnings.join("\n"), - duration: 10000, - }); - } else { - toast.success(`Source "${name}" added`); - } - setName(""); - setEndpoint(""); - setBaseUrl(""); - setNameManuallyEdited(false); + await addSource(name.trim(), type, config); + resetForm(); setOpen(false); onAdded(); } catch (err) { @@ -159,77 +357,159 @@ function AddSourceDialog({ onAdded }: { onAdded: () => void }) { Add Source - - + + Add Tool Source -
-
- - -
-
- - handleEndpointChange(e.target.value)} - placeholder={ - type === "mcp" - ? "https://mcp-server.example.com/sse" - : "https://api.example.com/openapi.json" - } - className="h-8 text-xs font-mono bg-background" - /> -
-
- - handleNameChange(e.target.value)} - placeholder="e.g. my-service" - className="h-8 text-xs font-mono bg-background" - /> -
- {type === "openapi" && ( + +
+ {/* Custom source form — always visible */} +
+
+ + +
setBaseUrl(e.target.value)} - placeholder="https://api.example.com" + value={endpoint} + onChange={(e) => handleEndpointChange(e.target.value)} + placeholder={ + type === "mcp" + ? "https://mcp-server.example.com/sse" + : "https://api.example.com/openapi.json" + } className="h-8 text-xs font-mono bg-background" />
- )} - +
+ + handleNameChange(e.target.value)} + placeholder="e.g. my-service" + className="h-8 text-xs font-mono bg-background" + /> +
+ {type === "openapi" && ( +
+ + setBaseUrl(e.target.value)} + placeholder="https://api.example.com" + className="h-8 text-xs font-mono bg-background" + /> +
+ )} + +
+ + {/* Collapsible presets */} + + + + + Quick add from catalog + + {API_PRESETS.length} + + + +
+ {API_PRESETS.map((preset) => { + const alreadyAdded = existingSourceNames.has(preset.name); + const isAdding = addingPreset === preset.name; + return ( + + ); + })} +
+
+
@@ -263,11 +543,16 @@ function SourceCard({ }; const TypeIcon = source.type === "mcp" ? Server : Globe; + const favicon = getSourceFavicon(source); return (
-
- +
+ {favicon ? ( + + ) : ( + + )}
@@ -460,7 +745,7 @@ export function ToolsView() { Tool Sources - + s.name))} />
diff --git a/executor/apps/web/src/components/ui/collapsible.tsx b/executor/apps/web/src/components/ui/collapsible.tsx new file mode 100644 index 000000000..2f7a4e7fc --- /dev/null +++ b/executor/apps/web/src/components/ui/collapsible.tsx @@ -0,0 +1,33 @@ +"use client" + +import { Collapsible as CollapsiblePrimitive } from "radix-ui" + +function Collapsible({ + ...props +}: React.ComponentProps) { + return +} + +function CollapsibleTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CollapsibleContent({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } From 82011a33e44a3ba28636dcd2bc96172e6dd6ef1f Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sat, 7 Feb 2026 16:43:24 -0800 Subject: [PATCH 02/34] Enhance tool source support by adding GraphQL type integration - Updated ToolSourceRecord and related interfaces to include "graphql" as a valid type. - Modified database mapping and service logic to accommodate GraphQL tool sources. - Enhanced API and UI components to support GraphQL endpoint configuration and validation. - Implemented GraphQL-specific logic for tool approval decisions and introspection handling. --- executor/apps/server/src/database.ts | 4 +- executor/apps/server/src/index.ts | 6 +- executor/apps/server/src/service.ts | 135 ++++-- executor/apps/server/src/tool-sources.ts | 406 +++++++++++++++++- executor/apps/server/src/types.ts | 8 +- .../apps/web/src/components/tools-view.tsx | 48 ++- executor/apps/web/src/lib/api.ts | 2 +- executor/apps/web/src/lib/types.ts | 4 +- executor/packages/contracts/src/index.ts | 4 +- 9 files changed, 559 insertions(+), 58 deletions(-) diff --git a/executor/apps/server/src/database.ts b/executor/apps/server/src/database.ts index e5cce54a7..9116589c7 100644 --- a/executor/apps/server/src/database.ts +++ b/executor/apps/server/src/database.ts @@ -106,7 +106,7 @@ function mapToolSourceRow(row: Record): ToolSourceRecord { id: String(row.id), workspaceId: String(row.workspace_id), name: String(row.name), - type: String(row.type) as "mcp" | "openapi", + type: String(row.type) as ToolSourceRecord["type"], config: parseMetadata(row.config_json), enabled: Number(row.enabled) === 1, createdAt: Number(row.created_at), @@ -700,7 +700,7 @@ export class ExecutorDatabase { id?: string; workspaceId: string; name: string; - type: "mcp" | "openapi"; + type: ToolSourceRecord["type"]; config: Record; enabled?: boolean; }): ToolSourceRecord { diff --git a/executor/apps/server/src/index.ts b/executor/apps/server/src/index.ts index 786c27fbc..c0bc0b000 100644 --- a/executor/apps/server/src/index.ts +++ b/executor/apps/server/src/index.ts @@ -213,7 +213,7 @@ const server = Bun.serve({ id?: string; workspaceId?: string; name?: string; - type?: "mcp" | "openapi"; + type?: "mcp" | "openapi" | "graphql"; config?: Record; enabled?: boolean; }>(request); @@ -222,8 +222,8 @@ const server = Bun.serve({ return error(400, "workspaceId, name, type, and config are required"); } - if (body.type !== "mcp" && body.type !== "openapi") { - return error(400, "type must be 'mcp' or 'openapi'"); + if (body.type !== "mcp" && body.type !== "openapi" && body.type !== "graphql") { + return error(400, "type must be 'mcp', 'openapi', or 'graphql'"); } try { diff --git a/executor/apps/server/src/service.ts b/executor/apps/server/src/service.ts index 16c6e6bc8..856819fcc 100644 --- a/executor/apps/server/src/service.ts +++ b/executor/apps/server/src/service.ts @@ -4,7 +4,7 @@ import { InProcessExecutionAdapter } from "./adapters/in-process-execution-adapt import { APPROVAL_DENIED_PREFIX } from "./execution-constants"; import { createDiscoverTool } from "./tool-discovery"; import type { ExternalToolSourceConfig } from "./tool-sources"; -import { loadExternalTools } from "./tool-sources"; +import { loadExternalTools, parseGraphqlOperationPaths } from "./tool-sources"; import type { AccessPolicyRecord, AnonymousContext, @@ -22,6 +22,7 @@ import type { ToolCallRequest, ToolDefinition, ToolDescriptor, + ToolSourceRecord, RuntimeOutputEvent, PolicyDecision, ResolvedToolCredential, @@ -75,7 +76,7 @@ function sourceSignature(workspaceId: string, sources: Array<{ id: string; updat } function normalizeExternalToolSource(raw: { - type: "mcp" | "openapi"; + type: ToolSourceRecord["type"]; name: string; config: Record; }): ExternalToolSourceConfig { @@ -92,6 +93,13 @@ function normalizeExternalToolSource(raw: { return merged as unknown as ExternalToolSourceConfig; } + if (raw.type === "graphql") { + if (typeof merged.endpoint !== "string" || merged.endpoint.trim().length === 0) { + throw new Error(`GraphQL source '${raw.name}' missing endpoint`); + } + return merged as unknown as ExternalToolSourceConfig; + } + const spec = merged.spec; if (typeof spec !== "string" && typeof spec !== "object") { throw new Error(`OpenAPI source '${raw.name}' missing spec`); @@ -192,16 +200,7 @@ export class ExecutorService { })); } - listToolSources(workspaceId: string): Array<{ - id: string; - workspaceId: string; - name: string; - type: "mcp" | "openapi"; - enabled: boolean; - config: Record; - createdAt: number; - updatedAt: number; - }> { + listToolSources(workspaceId: string): ToolSourceRecord[] { return this.db.listToolSources(workspaceId); } @@ -209,19 +208,10 @@ export class ExecutorService { id?: string; workspaceId: string; name: string; - type: "mcp" | "openapi"; + type: ToolSourceRecord["type"]; config: Record; enabled?: boolean; - }): Promise<{ - id: string; - workspaceId: string; - name: string; - type: "mcp" | "openapi"; - enabled: boolean; - config: Record; - createdAt: number; - updatedAt: number; - }> { + }): Promise { const source = this.db.upsertToolSource(input); this.workspaceToolCache.delete(source.workspaceId); await this.getWorkspaceTools(source.workspaceId); @@ -597,6 +587,69 @@ export class ExecutorService { }; } + /** + * For GraphQL tools with _graphqlSource, resolve the approval decision + * by parsing the query and checking policies against each virtual field path + * (e.g. linear.mutation.issueCreate, linear.query.issues). + * + * Returns the most restrictive decision across all field paths. + */ + private getGraphqlDecision( + task: TaskRecord, + tool: ToolDefinition, + input: unknown, + workspaceTools: Map, + ): { decision: PolicyDecision; effectivePaths: string[] } { + const sourceName = tool._graphqlSource!; + const payload = input && typeof input === "object" ? (input as Record) : {}; + const queryString = typeof payload.query === "string" ? payload.query : ""; + + if (!queryString.trim()) { + // No query to parse — fall back to tool's own approval + return { decision: this.getToolDecision(task, tool), effectivePaths: [tool.path] }; + } + + const { fieldPaths } = parseGraphqlOperationPaths(sourceName, queryString); + if (fieldPaths.length === 0) { + return { decision: this.getToolDecision(task, tool), effectivePaths: [tool.path] }; + } + + const policies = this.db.listAccessPolicies(task.workspaceId); + let worstDecision: PolicyDecision = "allow"; + + for (const fieldPath of fieldPaths) { + // Look up the pseudo-tool for this field path to get its default approval + const pseudoTool = workspaceTools.get(fieldPath); + const fieldDecision = pseudoTool + ? this.getDecisionForContext(pseudoTool, { + workspaceId: task.workspaceId, + actorId: task.actorId, + clientId: task.clientId, + }, policies) + : // Unknown field — check if any policies match, otherwise default to mutation=required + this.getDecisionForContext( + { ...tool, path: fieldPath, approval: fieldPath.includes(".mutation.") ? "required" : "auto" }, + { + workspaceId: task.workspaceId, + actorId: task.actorId, + clientId: task.clientId, + }, + policies, + ); + + // Escalate: deny > require_approval > allow + if (fieldDecision === "deny") { + worstDecision = "deny"; + break; // Can't get worse + } + if (fieldDecision === "require_approval") { + worstDecision = "require_approval"; + } + } + + return { decision: worstDecision, effectivePaths: fieldPaths }; + } + private async invokeTool(task: TaskRecord, call: ToolCallRequest): Promise { const { toolPath, input, callId } = call; const workspaceTools = await this.getWorkspaceTools(task.workspaceId); @@ -605,15 +658,29 @@ export class ExecutorService { throw new Error(`Unknown tool: ${toolPath}`); } - const decision = this.getToolDecision(task, tool); + // Determine approval decision — for GraphQL tools, parse the query for granular paths + let decision: PolicyDecision; + let effectiveToolPath = toolPath; + + if (tool._graphqlSource) { + const result = this.getGraphqlDecision(task, tool, input, workspaceTools); + decision = result.decision; + // Use the field paths for event reporting so approvals show what's actually being called + if (result.effectivePaths.length > 0) { + effectiveToolPath = result.effectivePaths.join(", "); + } + } else { + decision = this.getToolDecision(task, tool); + } + if (decision === "deny") { this.publish(task.id, "task", "tool.call.denied", { taskId: task.id, callId, - toolPath, + toolPath: effectiveToolPath, reason: "policy_deny", }); - throw new Error(`${APPROVAL_DENIED_PREFIX}${toolPath} (policy denied)`); + throw new Error(`${APPROVAL_DENIED_PREFIX}${effectiveToolPath} (policy denied)`); } let credential: ResolvedToolCredential | undefined; @@ -630,7 +697,7 @@ export class ExecutorService { this.publish(task.id, "task", "tool.call.started", { taskId: task.id, callId, - toolPath, + toolPath: effectiveToolPath, approval: decision === "require_approval" ? "required" : "auto", input: asPayload(input), }); @@ -639,7 +706,7 @@ export class ExecutorService { const approval = this.db.createApproval({ id: createApprovalId(), taskId: task.id, - toolPath, + toolPath: effectiveToolPath, input, }); @@ -652,15 +719,15 @@ export class ExecutorService { createdAt: approval.createdAt, }); - const decision = await this.waitForApproval(approval.id); - if (decision === "denied") { + const approvalDecision = await this.waitForApproval(approval.id); + if (approvalDecision === "denied") { this.publish(task.id, "task", "tool.call.denied", { taskId: task.id, callId, - toolPath, + toolPath: effectiveToolPath, approvalId: approval.id, }); - throw new Error(`${APPROVAL_DENIED_PREFIX}${toolPath} (${approval.id})`); + throw new Error(`${APPROVAL_DENIED_PREFIX}${effectiveToolPath} (${approval.id})`); } } @@ -677,7 +744,7 @@ export class ExecutorService { this.publish(task.id, "task", "tool.call.completed", { taskId: task.id, callId, - toolPath, + toolPath: effectiveToolPath, output: asPayload(value), }); return value; @@ -686,7 +753,7 @@ export class ExecutorService { this.publish(task.id, "task", "tool.call.failed", { taskId: task.id, callId, - toolPath, + toolPath: effectiveToolPath, error: message, }); throw error; diff --git a/executor/apps/server/src/tool-sources.ts b/executor/apps/server/src/tool-sources.ts index ade6fb9a4..a2eac9ba6 100644 --- a/executor/apps/server/src/tool-sources.ts +++ b/executor/apps/server/src/tool-sources.ts @@ -32,7 +32,22 @@ export interface OpenApiToolSourceConfig { overrides?: Record; } -export type ExternalToolSourceConfig = McpToolSourceConfig | OpenApiToolSourceConfig; +export interface GraphqlToolSourceConfig { + type: "graphql"; + name: string; + endpoint: string; + /** Optional static introspection result — if omitted, we introspect at load time */ + schema?: Record; + auth?: OpenApiAuth; + defaultQueryApproval?: ToolApprovalMode; + defaultMutationApproval?: ToolApprovalMode; + overrides?: Record; +} + +export type ExternalToolSourceConfig = + | McpToolSourceConfig + | OpenApiToolSourceConfig + | GraphqlToolSourceConfig; function toObject(value: unknown): Record { return value && typeof value === "object" && !Array.isArray(value) @@ -398,6 +413,391 @@ async function loadOpenApiTools(config: OpenApiToolSourceConfig): Promise; + type: GqlTypeRef; +} + +interface GqlInputField { + name: string; + description: string | null; + type: GqlTypeRef; + defaultValue: string | null; +} + +interface GqlEnumValue { + name: string; + description: string | null; +} + +interface GqlType { + kind: string; + name: string; + fields: GqlField[] | null; + inputFields: GqlInputField[] | null; + enumValues: GqlEnumValue[] | null; +} + +interface GqlSchema { + queryType: { name: string } | null; + mutationType: { name: string } | null; + types: GqlType[]; +} + +/** Resolve a GqlTypeRef to the underlying named type (unwrapping NON_NULL/LIST wrappers) */ +function unwrapType(ref: GqlTypeRef): string | null { + if (ref.kind === "NON_NULL" && ref.ofType) return unwrapType(ref.ofType); + if (ref.kind === "LIST" && ref.ofType) return unwrapType(ref.ofType); + return ref.name; +} + +/** + * Convert a GraphQL type reference to a TypeScript-like type hint, + * recursively expanding INPUT_OBJECT types so the model sees actual fields. + */ +function gqlTypeToHint(ref: GqlTypeRef, typeMap?: Map, depth = 0): string { + if (ref.kind === "NON_NULL" && ref.ofType) return gqlTypeToHint(ref.ofType, typeMap, depth); + if (ref.kind === "LIST" && ref.ofType) return `${gqlTypeToHint(ref.ofType, typeMap, depth)}[]`; + + if (ref.name && typeMap && depth < 3) { + const resolved = typeMap.get(ref.name); + if (resolved?.kind === "INPUT_OBJECT" && resolved.inputFields) { + return expandInputObject(resolved, typeMap, depth); + } + if (resolved?.kind === "ENUM" && resolved.enumValues && resolved.enumValues.length > 0) { + const values = resolved.enumValues.slice(0, 8).map((v) => `"${v.name}"`); + const suffix = resolved.enumValues.length > 8 ? " | ..." : ""; + return values.join(" | ") + suffix; + } + } + + // Map common GraphQL scalars to TS primitives + if (ref.name) { + switch (ref.name) { + case "String": + case "ID": + case "DateTime": + case "Date": + case "UUID": + case "JSONString": + case "TimelessDate": + return "string"; + case "Int": + case "Float": + return "number"; + case "Boolean": + return "boolean"; + case "JSON": + case "JSONObject": + return "Record"; + default: + return ref.name; + } + } + return "unknown"; +} + +function expandInputObject(type: GqlType, typeMap: Map, depth: number): string { + const fields = type.inputFields; + if (!fields || fields.length === 0) return "Record"; + const entries = fields.slice(0, 16).map((f) => { + const required = f.type.kind === "NON_NULL"; + return `${f.name}${required ? "" : "?"}: ${gqlTypeToHint(f.type, typeMap, depth + 1)}`; + }); + const suffix = fields.length > 16 ? "; ..." : ""; + return `{ ${entries.join("; ")}${suffix} }`; +} + +function gqlFieldArgsTypeHint(args: GqlField["args"], typeMap?: Map): string { + if (args.length === 0) return "{}"; + const entries = args.slice(0, 12).map((a) => { + const required = a.type.kind === "NON_NULL"; + return `${a.name}${required ? "" : "?"}: ${gqlTypeToHint(a.type, typeMap)}`; + }); + return `{ ${entries.join("; ")} }`; +} + +function isRequired(ref: GqlTypeRef): boolean { + return ref.kind === "NON_NULL"; +} + +/** Build a minimal GraphQL document for a single root field with its arguments */ +function buildFieldQuery( + operationType: "query" | "mutation", + fieldName: string, + args: GqlField["args"], +): string { + if (args.length === 0) { + return `${operationType} { ${fieldName} }`; + } + const varDefs = args.map((a) => `$${a.name}: ${printGqlType(a.type)}`).join(", "); + const fieldArgs = args.map((a) => `${a.name}: $${a.name}`).join(", "); + return `${operationType}(${varDefs}) { ${fieldName}(${fieldArgs}) }`; +} + +function printGqlType(ref: GqlTypeRef): string { + if (ref.kind === "NON_NULL" && ref.ofType) return `${printGqlType(ref.ofType)}!`; + if (ref.kind === "LIST" && ref.ofType) return `[${printGqlType(ref.ofType)}]`; + return ref.name ?? "String"; +} + +/** + * Parse a GraphQL query string to extract the operation type and root field names. + * This is intentionally simple — no full parser needed, just enough for policy routing. + */ +export function parseGraphqlOperationPaths( + sourceName: string, + queryString: string, +): { operationType: "query" | "mutation" | "subscription"; fieldPaths: string[] } { + const trimmed = queryString.trim(); + + // Determine operation type + let operationType: "query" | "mutation" | "subscription" = "query"; + if (/^mutation\b/i.test(trimmed)) operationType = "mutation"; + else if (/^subscription\b/i.test(trimmed)) operationType = "subscription"; + + // Find the first { ... } block and extract top-level field names + const braceStart = trimmed.indexOf("{"); + if (braceStart === -1) return { operationType, fieldPaths: [] }; + + // Walk the content inside the first braces, extract field names at depth 0 + const content = trimmed.slice(braceStart + 1); + const fieldPaths: string[] = []; + let depth = 0; + let current = ""; + + for (const char of content) { + if (char === "{") { + if (depth === 0 && current.trim()) { + // Grab the field name (before any args in parens) + const fieldName = current.trim().split(/[\s(]/)[0]; + if (fieldName && !fieldName.startsWith("__")) { + fieldPaths.push(`${sanitizeSegment(sourceName)}.${operationType}.${sanitizeSegment(fieldName)}`); + } + } + depth++; + current = ""; + } else if (char === "}") { + if (depth === 0) { + // End of top-level block — grab last field if any + const fieldName = current.trim().split(/[\s(]/)[0]; + if (fieldName && !fieldName.startsWith("__")) { + fieldPaths.push(`${sanitizeSegment(sourceName)}.${operationType}.${sanitizeSegment(fieldName)}`); + } + break; + } + depth--; + current = ""; + } else if (depth === 0) { + if (char === "\n" || char === ",") { + const fieldName = current.trim().split(/[\s(]/)[0]; + if (fieldName && !fieldName.startsWith("__")) { + fieldPaths.push(`${sanitizeSegment(sourceName)}.${operationType}.${sanitizeSegment(fieldName)}`); + } + current = ""; + } else { + current += char; + } + } + } + + return { operationType, fieldPaths }; +} + +async function loadGraphqlTools(config: GraphqlToolSourceConfig): Promise { + const authHeaders = buildStaticAuthHeaders(config.auth); + const sourceKey = `graphql:${config.name}`; + const credentialSpec = buildCredentialSpec(sourceKey, config.auth); + const sourceName = sanitizeSegment(config.name); + + // Introspect the schema + const introspectionResult = await fetch(config.endpoint, { + method: "POST", + headers: { + "content-type": "application/json", + ...authHeaders, + }, + body: JSON.stringify({ query: INTROSPECTION_QUERY }), + }); + + if (!introspectionResult.ok) { + const text = await introspectionResult.text().catch(() => ""); + throw new Error(`GraphQL introspection failed: HTTP ${introspectionResult.status}: ${text.slice(0, 300)}`); + } + + const introspectionJson = (await introspectionResult.json()) as { data?: { __schema?: GqlSchema }; errors?: unknown[] }; + if (introspectionJson.errors) { + throw new Error(`GraphQL introspection errors: ${JSON.stringify(introspectionJson.errors).slice(0, 500)}`); + } + const schema = introspectionJson.data?.__schema; + if (!schema) { + throw new Error("GraphQL introspection returned no schema"); + } + + // Index types by name + const typeMap = new Map(); + for (const t of schema.types) { + typeMap.set(t.name, t); + } + + const tools: ToolDefinition[] = []; + + // Create the main graphql tool — this is the one that actually executes queries + const mainToolPath = `${sourceName}.graphql`; + tools.push({ + path: mainToolPath, + source: sourceKey, + description: `Execute a GraphQL query or mutation against ${config.name}. Use the ${sourceName}.query.* and ${sourceName}.mutation.* tool descriptions to see available operations.`, + approval: "auto", // Actual approval is determined dynamically per-invocation + metadata: { + argsType: "{ query: string; variables?: Record }", + returnsType: "unknown", + }, + credential: credentialSpec, + // Tag as graphql source so invokeTool knows to do dynamic path extraction + _graphqlSource: config.name, + run: async (input: unknown, context) => { + const payload = toObject(input); + const query = String(payload.query ?? ""); + const variables = payload.variables ?? undefined; + + if (!query.trim()) { + throw new Error("GraphQL query string is required"); + } + + const response = await fetch(config.endpoint, { + method: "POST", + headers: { + "content-type": "application/json", + ...authHeaders, + ...(context.credential?.headers ?? {}), + }, + body: JSON.stringify({ query, variables }), + }); + + if (!response.ok) { + const text = await response.text().catch(() => ""); + throw new Error(`HTTP ${response.status} ${response.statusText}: ${text.slice(0, 500)}`); + } + + const result = await response.json() as { data?: unknown; errors?: unknown[] }; + if (result.errors && (!result.data || Object.keys(result.data as object).length === 0)) { + throw new Error(`GraphQL errors: ${JSON.stringify(result.errors).slice(0, 1000)}`); + } + // Return both data and errors if partial + if (result.errors) return result; + return result.data; + }, + } as ToolDefinition & { _graphqlSource: string }); + + // Create pseudo-tools for each query/mutation field — these are for discovery/intellisense + // but they all route through the main .graphql tool + const rootTypes: Array<{ typeName: string | null; operationType: "query" | "mutation" }> = [ + { typeName: schema.queryType?.name ?? null, operationType: "query" }, + { typeName: schema.mutationType?.name ?? null, operationType: "mutation" }, + ]; + + for (const { typeName, operationType } of rootTypes) { + if (!typeName) continue; + const rootType = typeMap.get(typeName); + if (!rootType?.fields) continue; + + const defaultApproval = operationType === "query" + ? (config.defaultQueryApproval ?? "auto") + : (config.defaultMutationApproval ?? "required"); + + for (const field of rootType.fields) { + if (field.name.startsWith("__")) continue; + + const fieldPath = `${sourceName}.${operationType}.${sanitizeSegment(field.name)}`; + const approval = config.overrides?.[field.name]?.approval ?? defaultApproval; + + // Build the example query for the description + const exampleQuery = buildFieldQuery(operationType, field.name, field.args); + + tools.push({ + path: fieldPath, + source: sourceKey, + description: field.description + ? `${field.description}\n\nExample: ${sourceName}.graphql({ query: \`${exampleQuery}\`, variables: {...} })` + : `GraphQL ${operationType}: ${field.name}\n\nExample: ${sourceName}.graphql({ query: \`${exampleQuery}\`, variables: {...} })`, + approval, + metadata: { + argsType: gqlFieldArgsTypeHint(field.args, typeMap), + returnsType: gqlTypeToHint(field.type, typeMap), + }, + // Pseudo-tools don't have a run — they exist for discovery and policy matching only + _pseudoTool: true, + run: async (input: unknown, context) => { + // If someone calls this directly, delegate to the main graphql tool + const payload = toObject(input); + if (!payload.query) { + // Auto-build the query from the variables + payload.query = buildFieldQuery(operationType, field.name, field.args); + } + // Find and invoke the main tool + const mainTool = tools.find((t) => t.path === mainToolPath); + if (!mainTool) throw new Error(`Main GraphQL tool not found`); + return mainTool.run(payload, context); + }, + } as ToolDefinition & { _pseudoTool: boolean }); + } + } + + return tools; +} + export function parseToolSourcesFromEnv(raw: string | undefined): ExternalToolSourceConfig[] { if (!raw || raw.trim().length === 0) { return []; @@ -411,7 +811,7 @@ export function parseToolSourcesFromEnv(raw: string | undefined): ExternalToolSo return parsed as ExternalToolSourceConfig[]; } -export async function loadExternalTools(sources: ExternalToolSourceConfig[]): Promise { +export async function loadExternalTools(sources: ExternalToolSourceConfig[]): Promise<{ tools: ToolDefinition[]; warnings: string[] }> { const loaded: ToolDefinition[] = []; const warnings: string[] = []; @@ -421,6 +821,8 @@ export async function loadExternalTools(sources: ExternalToolSourceConfig[]): Pr loaded.push(...(await loadMcpTools(source))); } else if (source.type === "openapi") { loaded.push(...(await loadOpenApiTools(source))); + } else if (source.type === "graphql") { + loaded.push(...(await loadGraphqlTools(source))); } } catch (error) { const message = error instanceof Error ? error.message : String(error); diff --git a/executor/apps/server/src/types.ts b/executor/apps/server/src/types.ts index c789b8539..73e0d26ce 100644 --- a/executor/apps/server/src/types.ts +++ b/executor/apps/server/src/types.ts @@ -188,6 +188,10 @@ export interface ToolDefinition { source?: string; metadata?: ToolTypeMetadata; credential?: ToolCredentialSpec; + /** For GraphQL sources: the source name used for dynamic path extraction */ + _graphqlSource?: string; + /** For GraphQL pseudo-tools: marks tools that exist only for discovery/policy */ + _pseudoTool?: boolean; run(input: unknown, context: ToolRunContext): Promise; } @@ -200,11 +204,13 @@ export interface ToolDescriptor { returnsType?: string; } +export type ToolSourceType = "mcp" | "openapi" | "graphql"; + export interface ToolSourceRecord { id: string; workspaceId: string; name: string; - type: "mcp" | "openapi"; + type: ToolSourceType; config: Record; enabled: boolean; createdAt: number; diff --git a/executor/apps/web/src/components/tools-view.tsx b/executor/apps/web/src/components/tools-view.tsx index 2d49d01c4..d6443bff0 100644 --- a/executor/apps/web/src/components/tools-view.tsx +++ b/executor/apps/web/src/components/tools-view.tsx @@ -55,9 +55,10 @@ interface ApiPreset { name: string; label: string; description: string; - type: "openapi" | "mcp"; + type: "openapi" | "mcp" | "graphql"; spec?: string; url?: string; + endpoint?: string; baseUrl?: string; authNote?: string; } @@ -181,6 +182,14 @@ const API_PRESETS: ApiPreset[] = [ spec: "https://raw.githubusercontent.com/resend/resend-openapi/main/resend.yaml", baseUrl: "https://api.resend.com", }, + { + name: "linear", + label: "Linear", + description: "Issues, projects, teams, cycles, labels", + type: "graphql", + endpoint: "https://api.linear.app/graphql", + authNote: "Requires API key as bearer credential", + }, ]; /** Derive a favicon URL from any URL string via Google's favicon service. */ @@ -195,14 +204,16 @@ function faviconForUrl(url: string | undefined | null): string | null { } function getFaviconUrl(preset: ApiPreset): string | null { - return faviconForUrl(preset.baseUrl ?? preset.url); + return faviconForUrl(preset.baseUrl ?? preset.endpoint ?? preset.url); } function getSourceFavicon(source: ToolSourceRecord): string | null { const url = source.type === "mcp" ? (source.config.url as string) - : (source.config.baseUrl as string) ?? (source.config.spec as string); + : source.type === "graphql" + ? (source.config.endpoint as string) + : (source.config.baseUrl as string) ?? (source.config.spec as string); return faviconForUrl(url); } @@ -249,7 +260,7 @@ function AddSourceDialog({ const { context } = useSession(); const [open, setOpen] = useState(false); const [presetsOpen, setPresetsOpen] = useState(false); - const [type, setType] = useState<"mcp" | "openapi">("mcp"); + const [type, setType] = useState<"mcp" | "openapi" | "graphql">("mcp"); const [name, setName] = useState(""); const [nameManuallyEdited, setNameManuallyEdited] = useState(false); const [endpoint, setEndpoint] = useState(""); @@ -286,7 +297,7 @@ function AddSourceDialog({ const addSource = async ( sourceName: string, - sourceType: "mcp" | "openapi", + sourceType: "mcp" | "openapi" | "graphql", config: Record, ) => { if (!context) return; @@ -314,10 +325,12 @@ function AddSourceDialog({ const config: Record = preset.type === "mcp" ? { url: preset.url } - : { - spec: preset.spec, - ...(preset.baseUrl ? { baseUrl: preset.baseUrl } : {}), - }; + : preset.type === "graphql" + ? { endpoint: preset.endpoint } + : { + spec: preset.spec, + ...(preset.baseUrl ? { baseUrl: preset.baseUrl } : {}), + }; await addSource(preset.name, preset.type, config); if (preset.authNote) { toast.info(preset.authNote, { duration: 6000 }); @@ -337,7 +350,9 @@ function AddSourceDialog({ const config: Record = type === "mcp" ? { url: endpoint } - : { spec: endpoint, ...(baseUrl ? { baseUrl } : {}) }; + : type === "graphql" + ? { endpoint: endpoint } + : { spec: endpoint, ...(baseUrl ? { baseUrl } : {}) }; await addSource(name.trim(), type, config); resetForm(); setOpen(false); @@ -383,12 +398,15 @@ function AddSourceDialog({ OpenAPI Spec + + GraphQL +
@@ -577,7 +597,9 @@ function SourceCard({ {source.type === "mcp" ? (source.config.url as string) - : (source.config.spec as string)} + : source.type === "graphql" + ? (source.config.endpoint as string) + : (source.config.spec as string)}
- + /> {approvalsLoading ? (
@@ -204,7 +186,7 @@ export function ApprovalsView() { {count} pending approval{count !== 1 ? "s" : ""}
{approvals!.map((a) => ( - + ))}
)} diff --git a/executor/apps/web/src/components/dashboard-view.tsx b/executor/apps/web/src/components/dashboard-view.tsx index 1258c6a12..80db85624 100644 --- a/executor/apps/web/src/components/dashboard-view.tsx +++ b/executor/apps/web/src/components/dashboard-view.tsx @@ -16,8 +16,9 @@ import { Skeleton } from "@/components/ui/skeleton"; import { PageHeader } from "@/components/page-header"; import { TaskStatusBadge } from "@/components/status-badge"; import { useSession } from "@/lib/session-context"; -import { usePoll } from "@/hooks/use-poll"; -import * as api from "@/lib/api"; +import { useWorkspaceTools } from "@/hooks/use-workspace-tools"; +import { useQuery } from "convex/react"; +import { convexApi } from "@/lib/convex-api"; import type { TaskRecord, PendingApprovalRecord } from "@/lib/types"; function formatTime(ts: number) { @@ -118,26 +119,17 @@ function RecentTaskRow({ task }: { task: TaskRecord }) { export function DashboardView() { const { context, loading: sessionLoading } = useSession(); - const { data: tasks } = usePoll({ - fetcher: () => api.listTasks(context!.workspaceId), - enabled: !!context, - }); + const tasks = useQuery( + convexApi.database.listTasks, + context ? { workspaceId: context.workspaceId } : "skip", + ); - const { data: approvals } = usePoll({ - fetcher: () => api.listPendingApprovals(context!.workspaceId), - enabled: !!context, - }); + const approvals = useQuery( + convexApi.database.listPendingApprovals, + context ? { workspaceId: context.workspaceId } : "skip", + ); - const { data: tools } = usePoll({ - fetcher: () => - api.listToolsForContext({ - workspaceId: context!.workspaceId, - actorId: context!.actorId, - clientId: context!.clientId, - }), - enabled: !!context, - interval: 10000, - }); + const { tools } = useWorkspaceTools(context ?? null); if (sessionLoading) { return ( diff --git a/executor/apps/web/src/components/tasks-view.tsx b/executor/apps/web/src/components/tasks-view.tsx index 05e48ddb0..70308d890 100644 --- a/executor/apps/web/src/components/tasks-view.tsx +++ b/executor/apps/web/src/components/tasks-view.tsx @@ -1,10 +1,9 @@ "use client"; -import { useState, useEffect, useCallback, useRef } from "react"; +import { useState, useCallback, useMemo } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import { Play, - RefreshCw, ChevronRight, X, Send, @@ -26,13 +25,11 @@ import { Separator } from "@/components/ui/separator"; import { PageHeader } from "@/components/page-header"; import { TaskStatusBadge } from "@/components/status-badge"; import { useSession } from "@/lib/session-context"; -import { usePoll } from "@/hooks/use-poll"; -import * as api from "@/lib/api"; +import { useWorkspaceTools } from "@/hooks/use-workspace-tools"; +import { useMutation, useQuery } from "convex/react"; +import { convexApi } from "@/lib/convex-api"; import type { TaskRecord, - RuntimeTargetDescriptor, - TaskEventRecord, - ToolDescriptor, } from "@/lib/types"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; @@ -69,35 +66,23 @@ function formatDate(ts: number) { // ── Task Composer ── -function TaskComposer({ onCreated }: { onCreated: () => void }) { +function TaskComposer() { const { context } = useSession(); const [code, setCode] = useState(DEFAULT_CODE); const [runtimeId, setRuntimeId] = useState("local-bun"); const [timeoutMs, setTimeoutMs] = useState("15000"); const [submitting, setSubmitting] = useState(false); + const createTask = useMutation(convexApi.database.createTask); - const { data: runtimes } = usePoll({ - fetcher: api.listRuntimeTargets, - interval: 30000, - enabled: !!context, - }); - - const { data: tools } = usePoll({ - fetcher: () => - api.listToolsForContext({ - workspaceId: context!.workspaceId, - actorId: context!.actorId, - clientId: context!.clientId, - }), - enabled: !!context, - interval: 10000, - }); + const runtimes = useQuery(convexApi.database.listRuntimeTargets, {}); + const { tools } = useWorkspaceTools(context ?? null); const handleSubmit = async () => { if (!context || !code.trim()) return; setSubmitting(true); try { - const result = await api.createTask({ + const task = await createTask({ + id: `task_${crypto.randomUUID()}`, code, runtimeId, timeoutMs: parseInt(timeoutMs) || 15000, @@ -105,8 +90,7 @@ function TaskComposer({ onCreated }: { onCreated: () => void }) { actorId: context.actorId, clientId: context.clientId, }); - toast.success(`Task created: ${result.taskId}`); - onCreated(); + toast.success(`Task created: ${task.id}`); } catch (err) { toast.error( err instanceof Error ? err.message : "Failed to create task", @@ -231,49 +215,35 @@ function TaskDetail({ workspaceId: string; onClose: () => void; }) { - const [liveTask, setLiveTask] = useState(task); - const [liveStdout, setLiveStdout] = useState(task.stdout ?? ""); - const [liveStderr, setLiveStderr] = useState(task.stderr ?? ""); - const eventSourceRef = useRef(null); - - useEffect(() => { - setLiveTask(task); - setLiveStdout(task.stdout ?? ""); - setLiveStderr(task.stderr ?? ""); - }, [task]); - - useEffect(() => { - const isTerminal = ["completed", "failed", "timed_out", "denied"].includes( - liveTask.status, - ); - if (isTerminal) return; - - const source = api.subscribeToTaskEvents( - task.id, - workspaceId, - (_eventName: string, event: TaskEventRecord) => { - const payload = event.payload as Record; - if (event.type === "task.completed" || event.type === "task.failed" || event.type === "task.timed_out" || event.type === "task.denied") { - // Refetch full task - api.getTask(task.id, workspaceId).then(setLiveTask); - } - if (event.type === "task.running") { - setLiveTask((prev) => ({ ...prev, status: "running" })); - } - if (event.type === "task.stdout") { - setLiveStdout((prev) => prev + (payload.line as string) + "\n"); - } - if (event.type === "task.stderr") { - setLiveStderr((prev) => prev + (payload.line as string) + "\n"); - } - }, - ); - eventSourceRef.current = source; + const liveTaskData = useQuery( + convexApi.database.getTaskInWorkspace, + workspaceId ? { taskId: task.id, workspaceId } : "skip", + ); + const taskEvents = useQuery( + convexApi.database.listTaskEvents, + workspaceId ? { taskId: task.id } : "skip", + ); - return () => { - source.close(); - }; - }, [task.id, workspaceId, liveTask.status]); + const liveTask = liveTaskData ?? task; + const liveStdout = useMemo(() => { + const stdoutLines = (taskEvents ?? []) + .filter((event) => event.type === "task.stdout") + .map((event) => String((event.payload as Record)?.line ?? "")); + if (stdoutLines.length > 0) { + return stdoutLines.join("\n"); + } + return liveTask.stdout ?? ""; + }, [taskEvents, liveTask.stdout]); + + const liveStderr = useMemo(() => { + const stderrLines = (taskEvents ?? []) + .filter((event) => event.type === "task.stderr") + .map((event) => String((event.payload as Record)?.line ?? "")); + if (stderrLines.length > 0) { + return stderrLines.join("\n"); + } + return liveTask.stderr ?? ""; + }, [taskEvents, liveTask.stderr]); const duration = liveTask.completedAt && liveTask.startedAt @@ -384,14 +354,11 @@ export function TasksView() { const searchParams = useSearchParams(); const selectedId = searchParams.get("selected"); - const { - data: tasks, - loading: tasksLoading, - refresh, - } = usePoll({ - fetcher: () => api.listTasks(context!.workspaceId), - enabled: !!context, - }); + const tasks = useQuery( + convexApi.database.listTasks, + context ? { workspaceId: context.workspaceId } : "skip", + ); + const tasksLoading = !!context && tasks === undefined; const selectedTask = tasks?.find((t) => t.id === selectedId); @@ -420,22 +387,12 @@ export function TasksView() { - - + />
{/* Left: composer + list */}
- + diff --git a/executor/apps/web/src/components/tools-view.tsx b/executor/apps/web/src/components/tools-view.tsx index d6443bff0..411b09dff 100644 --- a/executor/apps/web/src/components/tools-view.tsx +++ b/executor/apps/web/src/components/tools-view.tsx @@ -42,8 +42,9 @@ import { Skeleton } from "@/components/ui/skeleton"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { PageHeader } from "@/components/page-header"; import { useSession } from "@/lib/session-context"; -import { usePoll } from "@/hooks/use-poll"; -import * as api from "@/lib/api"; +import { useWorkspaceTools } from "@/hooks/use-workspace-tools"; +import { useMutation, useQuery } from "convex/react"; +import { convexApi } from "@/lib/convex-api"; import type { ToolSourceRecord, ToolDescriptor } from "@/lib/types"; import { parse as parseDomain } from "tldts"; import { toast } from "sonner"; @@ -258,6 +259,7 @@ function AddSourceDialog({ existingSourceNames: Set; }) { const { context } = useSession(); + const upsertToolSource = useMutation(convexApi.database.upsertToolSource); const [open, setOpen] = useState(false); const [presetsOpen, setPresetsOpen] = useState(false); const [type, setType] = useState<"mcp" | "openapi" | "graphql">("mcp"); @@ -301,22 +303,13 @@ function AddSourceDialog({ config: Record, ) => { if (!context) return; - const result = await api.upsertToolSource({ + await upsertToolSource({ workspaceId: context.workspaceId, name: sourceName, type: sourceType, config, }); - const warnings = (result as unknown as Record) - .warnings as string[] | undefined; - if (warnings && warnings.length > 0) { - toast.warning(`Source "${sourceName}" saved but had issues`, { - description: warnings.join("\n"), - duration: 10000, - }); - } else { - toast.success(`Source "${sourceName}" added`); - } + toast.success(`Source "${sourceName}" added`); }; const handlePresetAdd = async (preset: ApiPreset) => { @@ -546,13 +539,14 @@ function SourceCard({ onDeleted: () => void; }) { const { context } = useSession(); + const deleteToolSource = useMutation(convexApi.database.deleteToolSource); const [deleting, setDeleting] = useState(false); const handleDelete = async () => { if (!context) return; setDeleting(true); try { - await api.deleteToolSource(context.workspaceId, source.id); + await deleteToolSource({ workspaceId: context.workspaceId, sourceId: source.id }); toast.success(`Removed "${source.name}"`); onDeleted(); } catch (err) { @@ -683,35 +677,13 @@ function ToolInventory({ tools }: { tools: ToolDescriptor[] }) { export function ToolsView() { const { context, loading: sessionLoading } = useSession(); - const { - data: sources, - loading: sourcesLoading, - refresh: refreshSources, - } = usePoll({ - fetcher: () => api.listToolSources(context!.workspaceId), - enabled: !!context, - interval: 10000, - }); - - const { - data: tools, - loading: toolsLoading, - refresh: refreshTools, - } = usePoll({ - fetcher: () => - api.listToolsForContext({ - workspaceId: context!.workspaceId, - actorId: context!.actorId, - clientId: context!.clientId, - }), - enabled: !!context, - interval: 10000, - }); - - const refreshAll = () => { - refreshSources(); - refreshTools(); - }; + const sources = useQuery( + convexApi.database.listToolSources, + context ? { workspaceId: context.workspaceId } : "skip", + ); + const sourcesLoading = !!context && sources === undefined; + + const { tools, loading: toolsLoading, refresh: refreshInventory } = useWorkspaceTools(context ?? null); if (sessionLoading) { return ( @@ -732,7 +704,7 @@ export function ToolsView() { variant="outline" size="sm" className="h-8 text-xs" - onClick={refreshAll} + onClick={() => void refreshInventory()} > Refresh @@ -767,7 +739,7 @@ export function ToolsView() { Tool Sources - s.name))} /> + void refreshInventory()} existingSourceNames={new Set((sources ?? []).map(s => s.name))} />
@@ -792,7 +764,7 @@ export function ToolsView() { ) : (
{sources.map((s) => ( - + void refreshInventory()} /> ))}
)} diff --git a/executor/apps/web/src/hooks/use-poll.ts b/executor/apps/web/src/hooks/use-poll.ts deleted file mode 100644 index 9f87b943b..000000000 --- a/executor/apps/web/src/hooks/use-poll.ts +++ /dev/null @@ -1,53 +0,0 @@ -"use client"; - -import { useEffect, useState, useCallback, useRef } from "react"; - -interface UsePollOptions { - fetcher: () => Promise; - interval?: number; - enabled?: boolean; -} - -interface UsePollResult { - data: T | null; - loading: boolean; - error: string | null; - refresh: () => Promise; -} - -export function usePoll({ - fetcher, - interval = 4000, - enabled = true, -}: UsePollOptions): UsePollResult { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const fetcherRef = useRef(fetcher); - fetcherRef.current = fetcher; - - const refresh = useCallback(async () => { - try { - const result = await fetcherRef.current(); - setData(result); - setError(null); - } catch (err) { - setError(err instanceof Error ? err.message : "Fetch failed"); - } finally { - setLoading(false); - } - }, []); - - useEffect(() => { - if (!enabled) { - setLoading(false); - return; - } - - refresh(); - const timer = setInterval(refresh, interval); - return () => clearInterval(timer); - }, [enabled, interval, refresh]); - - return { data, loading, error, refresh }; -} diff --git a/executor/apps/web/src/hooks/use-workspace-tools.ts b/executor/apps/web/src/hooks/use-workspace-tools.ts new file mode 100644 index 000000000..7bee63bf0 --- /dev/null +++ b/executor/apps/web/src/hooks/use-workspace-tools.ts @@ -0,0 +1,48 @@ +"use client"; + +import { useCallback, useEffect } from "react"; +import { useQuery } from "convex/react"; +import { convexApi } from "../lib/convex-api"; +import * as api from "../lib/api"; + +interface WorkspaceContext { + workspaceId: string; + actorId?: string; + clientId?: string; +} + +export function useWorkspaceTools(context: WorkspaceContext | null) { + const tools = useQuery( + convexApi.database.listWorkspaceToolsForContext, + context + ? { + workspaceId: context.workspaceId, + actorId: context.actorId, + clientId: context.clientId, + } + : "skip", + ); + + const refresh = useCallback(async () => { + if (!context) return; + try { + await api.listToolsForContext({ + workspaceId: context.workspaceId, + actorId: context.actorId, + clientId: context.clientId, + }); + } catch { + // best effort warm-up for server-side tool discovery sync + } + }, [context]); + + useEffect(() => { + void refresh(); + }, [refresh]); + + return { + tools, + loading: !!context && tools === undefined, + refresh, + }; +} diff --git a/executor/apps/web/src/lib/api.ts b/executor/apps/web/src/lib/api.ts index 7adc78d90..de625c8f9 100644 --- a/executor/apps/web/src/lib/api.ts +++ b/executor/apps/web/src/lib/api.ts @@ -13,7 +13,6 @@ import type { ToolSourceRecord, AccessPolicyRecord, CredentialDescriptor, - TaskEventRecord, } from "./types"; const BASE = ""; @@ -220,30 +219,3 @@ export async function upsertCredential(request: { }); return json(res); } - -// ── SSE ── - -export function subscribeToTaskEvents( - taskId: string, - workspaceId: string, - onEvent: ( - eventName: TaskEventRecord["eventName"], - event: TaskEventRecord, - ) => void, -): EventSource { - const source = new EventSource( - `${BASE}/api/tasks/${encodeURIComponent(taskId)}/events?workspaceId=${encodeURIComponent(workspaceId)}`, - ); - - source.addEventListener("task", (event) => { - const message = event as MessageEvent; - onEvent("task", JSON.parse(message.data) as TaskEventRecord); - }); - - source.addEventListener("approval", (event) => { - const message = event as MessageEvent; - onEvent("approval", JSON.parse(message.data) as TaskEventRecord); - }); - - return source; -} diff --git a/executor/apps/web/src/lib/convex-api.ts b/executor/apps/web/src/lib/convex-api.ts new file mode 100644 index 000000000..d2995cbbc --- /dev/null +++ b/executor/apps/web/src/lib/convex-api.ts @@ -0,0 +1,3 @@ +import { api } from "../../../../convex/_generated/api"; + +export const convexApi = api; diff --git a/executor/apps/web/src/lib/convex-provider.tsx b/executor/apps/web/src/lib/convex-provider.tsx new file mode 100644 index 000000000..03dce4f35 --- /dev/null +++ b/executor/apps/web/src/lib/convex-provider.tsx @@ -0,0 +1,13 @@ +"use client"; + +import { ConvexProvider, ConvexReactClient } from "convex/react"; +import type { ReactNode } from "react"; + +const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL ?? "http://127.0.0.1:3210"; +const convexClient = new ConvexReactClient(convexUrl, { + unsavedChangesWarning: false, +}); + +export function AppConvexProvider({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/executor/apps/web/src/lib/session-context.tsx b/executor/apps/web/src/lib/session-context.tsx index bc9171893..44a06d8af 100644 --- a/executor/apps/web/src/lib/session-context.tsx +++ b/executor/apps/web/src/lib/session-context.tsx @@ -8,7 +8,8 @@ import { useCallback, type ReactNode, } from "react"; -import { bootstrapAnonymousContext } from "./api"; +import { useMutation } from "convex/react"; +import { convexApi } from "@/lib/convex-api"; import type { AnonymousContext } from "./types"; interface SessionState { @@ -28,6 +29,7 @@ const SessionContext = createContext({ const SESSION_KEY = "executor_session_id"; export function SessionProvider({ children }: { children: ReactNode }) { + const bootstrapAnonymousSession = useMutation(convexApi.database.bootstrapAnonymousSession); const [context, setContext] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -36,7 +38,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { setLoading(true); setError(null); try { - const ctx = await bootstrapAnonymousContext(sessionId); + const ctx = await bootstrapAnonymousSession({ sessionId }); localStorage.setItem(SESSION_KEY, ctx.sessionId); setContext(ctx); } catch (err) { @@ -44,7 +46,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { } finally { setLoading(false); } - }, []); + }, [bootstrapAnonymousSession]); useEffect(() => { const stored = localStorage.getItem(SESSION_KEY); diff --git a/executor/bun.lock b/executor/bun.lock index e3e33e21d..7b75693b1 100644 --- a/executor/bun.lock +++ b/executor/bun.lock @@ -32,6 +32,7 @@ "@monaco-editor/react": "^4.7.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "convex": "latest", "lucide-react": "^0.563.0", "monaco-editor": "^0.55.1", "next": "16.1.6", diff --git a/executor/convex/database.ts b/executor/convex/database.ts index 2d194c4f2..4a8a37404 100644 --- a/executor/convex/database.ts +++ b/executor/convex/database.ts @@ -123,6 +123,32 @@ function mapTaskEvent(doc: any) { }; } +function mapWorkspaceTool(doc: any) { + return { + path: doc.path, + description: doc.description, + approval: doc.approval, + source: doc.source, + argsType: doc.argsType, + returnsType: doc.returnsType, + }; +} + +function matchesToolPath(pattern: string, toolPath: string): boolean { + const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*"); + const regex = new RegExp(`^${escaped}$`); + return regex.test(toolPath); +} + +function policySpecificity(policy: any, actorId?: string, clientId?: string): number { + let score = 0; + if (policy.actorId && actorId && policy.actorId === actorId) score += 4; + if (policy.clientId && clientId && policy.clientId === clientId) score += 2; + score += Math.max(1, String(policy.toolPathPattern ?? "").replace(/\*/g, "").length); + score += Number(policy.priority ?? 0); + return score; +} + async function getTaskDoc(ctx: any, taskId: string) { return await ctx.db.query("tasks").withIndex("by_task_id", (q: any) => q.eq("taskId", taskId)).unique(); } @@ -194,6 +220,37 @@ export const listTasks = query({ }, }); +export const listQueuedTaskIds = query({ + args: { limit: v.optional(v.number()) }, + handler: async (ctx, args) => { + const docs = await ctx.db + .query("tasks") + .withIndex("by_status_created", (q: any) => q.eq("status", "queued")) + .order("asc") + .take(args.limit ?? 20); + + return docs.map((doc: any) => doc.taskId); + }, +}); + +export const listRuntimeTargets = query({ + args: {}, + handler: async () => { + return [ + { + id: "local-bun", + label: "Local JS Runtime", + description: "Runs generated code in-process using Bun", + }, + { + id: "vercel-sandbox", + label: "Vercel Sandbox Runtime", + description: "Executes generated code in Vercel Sandbox VMs", + }, + ]; + }, +}); + export const getTaskInWorkspace = query({ args: { taskId: v.string(), workspaceId: v.string() }, handler: async (ctx, args) => { @@ -209,7 +266,7 @@ export const markTaskRunning = mutation({ args: { taskId: v.string() }, handler: async (ctx, args) => { const doc = await getTaskDoc(ctx, args.taskId); - if (!doc) { + if (!doc || doc.status !== "queued") { return null; } @@ -706,6 +763,99 @@ export const listToolSources = query({ }, }); +export const syncWorkspaceTools = mutation({ + args: { + workspaceId: v.string(), + tools: v.array( + v.object({ + path: v.string(), + description: v.string(), + approval: v.string(), + source: v.optional(v.string()), + argsType: v.optional(v.string()), + returnsType: v.optional(v.string()), + }), + ), + }, + handler: async (ctx, args) => { + const existing = await ctx.db + .query("workspaceTools") + .withIndex("by_workspace_updated", (q: any) => q.eq("workspaceId", args.workspaceId)) + .collect(); + + for (const doc of existing) { + await ctx.db.delete(doc._id); + } + + const now = Date.now(); + for (const tool of args.tools) { + await ctx.db.insert("workspaceTools", { + workspaceId: args.workspaceId, + path: tool.path, + description: tool.description, + approval: tool.approval, + source: tool.source, + argsType: tool.argsType, + returnsType: tool.returnsType, + updatedAt: now, + }); + } + + return true; + }, +}); + +export const listWorkspaceToolsForContext = query({ + args: { + workspaceId: v.string(), + actorId: v.optional(v.string()), + clientId: v.optional(v.string()), + }, + handler: async (ctx, args) => { + const [tools, policies] = await Promise.all([ + ctx.db + .query("workspaceTools") + .withIndex("by_workspace_path", (q: any) => q.eq("workspaceId", args.workspaceId)) + .collect(), + ctx.db + .query("accessPolicies") + .withIndex("by_workspace_created", (q: any) => q.eq("workspaceId", args.workspaceId)) + .collect(), + ]); + + return tools + .map((toolDoc: any) => { + const baseDecision = toolDoc.approval === "required" ? "require_approval" : "allow"; + const candidates = policies + .filter((policy: any) => { + const policyActorId = optionalFromNormalized(policy.actorId); + const policyClientId = optionalFromNormalized(policy.clientId); + if (policyActorId && policyActorId !== args.actorId) return false; + if (policyClientId && policyClientId !== args.clientId) return false; + return matchesToolPath(policy.toolPathPattern, toolDoc.path); + }) + .sort((a: any, b: any) => { + const bScore = policySpecificity(b, args.actorId, args.clientId); + const aScore = policySpecificity(a, args.actorId, args.clientId); + return bScore - aScore; + }); + + const decision = candidates[0]?.decision ?? baseDecision; + if (decision === "deny") { + return null; + } + + const tool = mapWorkspaceTool(toolDoc); + return { + ...tool, + approval: decision === "require_approval" ? "required" : "auto", + }; + }) + .filter((tool: any) => tool !== null) + .sort((a: any, b: any) => a.path.localeCompare(b.path)); + }, +}); + export const deleteToolSource = mutation({ args: { workspaceId: v.string(), sourceId: v.string() }, handler: async (ctx, args) => { diff --git a/executor/convex/schema.ts b/executor/convex/schema.ts index 45e32e131..aadff77d1 100644 --- a/executor/convex/schema.ts +++ b/executor/convex/schema.ts @@ -22,7 +22,8 @@ export default defineSchema({ completedAt: v.optional(v.number()), }) .index("by_task_id", ["taskId"]) - .index("by_workspace_created", ["workspaceId", "createdAt"]), + .index("by_workspace_created", ["workspaceId", "createdAt"]) + .index("by_status_created", ["status", "createdAt"]), approvals: defineTable({ approvalId: v.string(), @@ -105,4 +106,17 @@ export default defineSchema({ }) .index("by_session_id", ["sessionId"]) .index("by_workspace_actor", ["workspaceId", "actorId"]), + + workspaceTools: defineTable({ + workspaceId: v.string(), + path: v.string(), + description: v.string(), + approval: v.string(), + source: v.optional(v.string()), + argsType: v.optional(v.string()), + returnsType: v.optional(v.string()), + updatedAt: v.number(), + }) + .index("by_workspace_updated", ["workspaceId", "updatedAt"]) + .index("by_workspace_path", ["workspaceId", "path"]), }); diff --git a/executor/package.json b/executor/package.json index d7b689669..cf330a0e9 100644 --- a/executor/package.json +++ b/executor/package.json @@ -8,9 +8,10 @@ ], "scripts": { "dev": "bun run --cwd apps/server dev", + "dev:worker": "bun run --cwd apps/server worker", "dev:convex": "bunx convex dev --local", "dev:web": "bun run --cwd apps/web dev", - "dev:all": "bun run dev:convex & bun run dev & bun run dev:web", + "dev:all": "bun run dev:convex & bun run dev & bun run dev:worker & bun run dev:web", "start": "bun run --cwd apps/server start", "convex:codegen": "bunx convex codegen", "test": "bun test", From 9783c8d7ce6fdd87277e656f8bbdeacc0525975f Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sat, 7 Feb 2026 20:04:20 -0800 Subject: [PATCH 05/34] Sync tool inventory via worker watchers Track workspace tool-source updates in Convex and have the worker refresh cached tool inventory so the web UI stays live without calling /api/tools for warmup. --- executor/apps/server/src/database.ts | 5 +++ executor/apps/server/src/service.ts | 8 ++++ executor/apps/server/src/worker.ts | 41 +++++++++++++++++++ .../apps/web/src/components/tools-view.tsx | 13 +----- .../apps/web/src/hooks/use-workspace-tools.ts | 20 ++------- executor/convex/database.ts | 19 +++++++++ 6 files changed, 77 insertions(+), 29 deletions(-) diff --git a/executor/apps/server/src/database.ts b/executor/apps/server/src/database.ts index f21f83879..9e0ddc3aa 100644 --- a/executor/apps/server/src/database.ts +++ b/executor/apps/server/src/database.ts @@ -45,6 +45,7 @@ type QueryName = | "database:listCredentials" | "database:resolveCredential" | "database:listToolSources" + | "database:listToolSourceWorkspaceUpdates" | "database:listWorkspaceToolsForContext" | "database:listTaskEvents"; @@ -210,6 +211,10 @@ export class ExecutorDatabase { return await this.query("database:listToolSources", { workspaceId }); } + async listToolSourceWorkspaceUpdates(): Promise> { + return await this.query("database:listToolSourceWorkspaceUpdates", {}); + } + async syncWorkspaceTools(params: { workspaceId: string; tools: ToolDescriptor[]; diff --git a/executor/apps/server/src/service.ts b/executor/apps/server/src/service.ts index 85194f630..74afab786 100644 --- a/executor/apps/server/src/service.ts +++ b/executor/apps/server/src/service.ts @@ -213,6 +213,14 @@ export class ExecutorService { return await this.db.listToolSources(workspaceId); } + async listToolSourceWorkspaceUpdates(): Promise> { + return await this.db.listToolSourceWorkspaceUpdates(); + } + + async refreshWorkspaceTools(workspaceId: string): Promise { + await this.getWorkspaceTools(workspaceId); + } + async upsertToolSource(input: { id?: string; workspaceId: string; diff --git a/executor/apps/server/src/worker.ts b/executor/apps/server/src/worker.ts index 01926b4ae..671f70b5f 100644 --- a/executor/apps/server/src/worker.ts +++ b/executor/apps/server/src/worker.ts @@ -51,6 +51,7 @@ const convexClient = new ConvexClient(convexUrl, { }); let draining = false; +let syncingToolInventories = false; async function drainQueue(trigger: string): Promise { if (draining) { @@ -78,6 +79,30 @@ async function drainQueue(trigger: string): Promise { } } +async function syncWorkspaceToolInventories(trigger: string): Promise { + if (syncingToolInventories) { + return; + } + + syncingToolInventories = true; + try { + const updates = await service.listToolSourceWorkspaceUpdates(); + if (updates.length === 0) { + return; + } + + console.log(`[worker] ${trigger}: syncing tool inventories for ${updates.length} workspace(s)`); + for (const update of updates) { + await service.refreshWorkspaceTools(update.workspaceId); + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(`[worker] tool inventory sync failed: ${message}`); + } finally { + syncingToolInventories = false; + } +} + const queueSubscription = convexClient.onUpdate( api.database.listQueuedTaskIds, { limit: 1 }, @@ -91,15 +116,31 @@ const queueSubscription = convexClient.onUpdate( }, ); +const toolSourceSubscription = convexClient.onUpdate( + api.database.listToolSourceWorkspaceUpdates, + {}, + (updates) => { + if (updates.length > 0) { + void syncWorkspaceToolInventories("onUpdate"); + } + }, + (error) => { + console.warn(`[worker] tool source watcher error: ${error.message}`); + }, +); + const interval = setInterval(() => { void drainQueue("interval"); + void syncWorkspaceToolInventories("interval"); }, pollMs); await drainQueue("startup"); +await syncWorkspaceToolInventories("startup"); function shutdown(signal: string): void { console.log(`[worker] received ${signal}, shutting down...`); queueSubscription.unsubscribe(); + toolSourceSubscription.unsubscribe(); clearInterval(interval); void convexClient.close(); process.exit(0); diff --git a/executor/apps/web/src/components/tools-view.tsx b/executor/apps/web/src/components/tools-view.tsx index 411b09dff..72a99bea8 100644 --- a/executor/apps/web/src/components/tools-view.tsx +++ b/executor/apps/web/src/components/tools-view.tsx @@ -5,7 +5,6 @@ import { Wrench, Plus, Trash2, - RefreshCw, ShieldCheck, Globe, Server, @@ -699,17 +698,7 @@ export function ToolsView() { - - + /> diff --git a/executor/apps/web/src/hooks/use-workspace-tools.ts b/executor/apps/web/src/hooks/use-workspace-tools.ts index 7bee63bf0..de51e2640 100644 --- a/executor/apps/web/src/hooks/use-workspace-tools.ts +++ b/executor/apps/web/src/hooks/use-workspace-tools.ts @@ -1,9 +1,8 @@ "use client"; -import { useCallback, useEffect } from "react"; +import { useCallback } from "react"; import { useQuery } from "convex/react"; import { convexApi } from "../lib/convex-api"; -import * as api from "../lib/api"; interface WorkspaceContext { workspaceId: string; @@ -24,21 +23,8 @@ export function useWorkspaceTools(context: WorkspaceContext | null) { ); const refresh = useCallback(async () => { - if (!context) return; - try { - await api.listToolsForContext({ - workspaceId: context.workspaceId, - actorId: context.actorId, - clientId: context.clientId, - }); - } catch { - // best effort warm-up for server-side tool discovery sync - } - }, [context]); - - useEffect(() => { - void refresh(); - }, [refresh]); + // Convex queries are live; manual refresh is not required. + }, []); return { tools, diff --git a/executor/convex/database.ts b/executor/convex/database.ts index 4a8a37404..49a888f95 100644 --- a/executor/convex/database.ts +++ b/executor/convex/database.ts @@ -763,6 +763,25 @@ export const listToolSources = query({ }, }); +export const listToolSourceWorkspaceUpdates = query({ + args: {}, + handler: async (ctx) => { + const docs = await ctx.db.query("toolSources").collect(); + const byWorkspace = new Map(); + + for (const doc of docs) { + const existing = byWorkspace.get(doc.workspaceId) ?? 0; + if (doc.updatedAt > existing) { + byWorkspace.set(doc.workspaceId, doc.updatedAt); + } + } + + return [...byWorkspace.entries()] + .map(([workspaceId, updatedAt]) => ({ workspaceId, updatedAt })) + .sort((a, b) => a.workspaceId.localeCompare(b.workspaceId)); + }, +}); + export const syncWorkspaceTools = mutation({ args: { workspaceId: v.string(), From 70e652d4b068f96c8dd7e5af79569aa669b55f74 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sat, 7 Feb 2026 20:45:54 -0800 Subject: [PATCH 06/34] Add convex-test dependency and implement access policy management - Added `convex-test` as a development dependency in `package.json` and `bun.lock`. - Introduced access policy management in `service.test.ts`, including methods for listing and upserting access policies. - Enhanced runtime execution handling in `runtime-core.ts` and `vercel-sandbox-runtime.ts` to support new policy checks and improved error handling for script execution timeouts. --- .../in-process-execution-adapter.test.ts | 78 +++++ .../server/src/runtimes/runtime-core.test.ts | 319 ++++++++++++++++++ .../apps/server/src/runtimes/runtime-core.ts | 21 +- .../src/runtimes/vercel-sandbox-runtime.ts | 17 +- executor/apps/server/src/service.test.ts | 117 ++++++- executor/bun.lock | 3 + executor/convex/database.test.ts | 138 ++++++++ executor/package.json | 1 + 8 files changed, 671 insertions(+), 23 deletions(-) create mode 100644 executor/apps/server/src/adapters/in-process-execution-adapter.test.ts create mode 100644 executor/apps/server/src/runtimes/runtime-core.test.ts create mode 100644 executor/convex/database.test.ts diff --git a/executor/apps/server/src/adapters/in-process-execution-adapter.test.ts b/executor/apps/server/src/adapters/in-process-execution-adapter.test.ts new file mode 100644 index 000000000..5d204d8cb --- /dev/null +++ b/executor/apps/server/src/adapters/in-process-execution-adapter.test.ts @@ -0,0 +1,78 @@ +import { expect, test } from "bun:test"; +import { APPROVAL_DENIED_PREFIX } from "../execution-constants"; +import { InProcessExecutionAdapter } from "./in-process-execution-adapter"; + +test("returns run mismatch without invoking tool", async () => { + let called = 0; + const adapter = new InProcessExecutionAdapter({ + runId: "run_expected", + invokeTool: async () => { + called += 1; + return { ok: true }; + }, + emitOutput: () => {}, + }); + + const result = await adapter.invokeTool({ + runId: "run_other", + callId: "call_1", + toolPath: "utils.echo", + input: {}, + }); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toContain("Run mismatch"); + } + expect(called).toBe(0); +}); + +test("maps approval denied errors to denied result", async () => { + const adapter = new InProcessExecutionAdapter({ + runId: "run_1", + invokeTool: async () => { + throw new Error(`${APPROVAL_DENIED_PREFIX}approval required`); + }, + emitOutput: () => {}, + }); + + const result = await adapter.invokeTool({ + runId: "run_1", + callId: "call_1", + toolPath: "admin.delete_data", + input: { id: "abc" }, + }); + + expect(result).toEqual({ + ok: false, + denied: true, + error: "approval required", + }); +}); + +test("emits output only for matching run id", () => { + const lines: string[] = []; + const adapter = new InProcessExecutionAdapter({ + runId: "run_1", + invokeTool: async () => null, + emitOutput: (event) => { + lines.push(`${event.stream}:${event.line}`); + }, + }); + + adapter.emitOutput({ + runId: "run_other", + stream: "stdout", + line: "ignored", + timestamp: Date.now(), + }); + + adapter.emitOutput({ + runId: "run_1", + stream: "stdout", + line: "accepted", + timestamp: Date.now(), + }); + + expect(lines).toEqual(["stdout:accepted"]); +}); diff --git a/executor/apps/server/src/runtimes/runtime-core.test.ts b/executor/apps/server/src/runtimes/runtime-core.test.ts new file mode 100644 index 000000000..60d21fbd1 --- /dev/null +++ b/executor/apps/server/src/runtimes/runtime-core.test.ts @@ -0,0 +1,319 @@ +import { expect, test } from "bun:test"; +import { runCodeWithAdapter } from "./runtime-core"; +import { loadExternalTools } from "../tool-sources"; +import type { + ExecutionAdapter, + RuntimeOutputEvent, + SandboxExecutionRequest, + ToolDefinition, + ToolRunContext, +} from "../types"; + +function request(code: string, timeoutMs = 1_000): SandboxExecutionRequest { + return { + taskId: `task_${crypto.randomUUID()}`, + code, + timeoutMs, + }; +} + +function createRuntimeAdapter( + tools: Map, + outputEvents: RuntimeOutputEvent[], +): ExecutionAdapter { + return { + async invokeTool(call) { + const tool = tools.get(call.toolPath); + if (!tool) { + return { + ok: false, + error: `Tool not found: ${call.toolPath}`, + }; + } + + try { + const context: ToolRunContext = { + taskId: call.runId, + workspaceId: "ws_test", + actorId: "actor_test", + clientId: "web", + isToolAllowed: () => true, + }; + const value = await tool.run(call.input, context); + return { ok: true, value }; + } catch (error) { + return { + ok: false, + error: error instanceof Error ? error.message : String(error), + }; + } + }, + emitOutput(event) { + outputEvents.push(event); + }, + }; +} + +test("executes tool calls and captures output", async () => { + const outputEvents: RuntimeOutputEvent[] = []; + const tools = new Map([ + [ + "utils.echo", + { + path: "utils.echo", + description: "Echo back input", + approval: "auto", + run: async (input) => ({ echoed: input }), + }, + ], + ]); + + const result = await runCodeWithAdapter( + request(` + const out = await tools.utils.echo({ message: "hi" }); + console.log("out", out.echoed.message); + return 42; + `), + createRuntimeAdapter(tools, outputEvents), + ); + + expect(result.status).toBe("completed"); + expect(result.stdout).toContain("out hi"); + expect(result.stdout).toContain("result: 42"); + expect(outputEvents.some((event) => event.stream === "stdout")).toBe(true); +}); + +test("returns denied when adapter marks tool call denied", async () => { + const adapter: ExecutionAdapter = { + async invokeTool() { + return { + ok: false, + denied: true, + error: "policy denied", + }; + }, + emitOutput() {}, + }; + + const result = await runCodeWithAdapter( + request(` + await tools.admin.delete_data({ id: "x" }); + `), + adapter, + ); + + expect(result.status).toBe("denied"); + expect(result.error).toBe("policy denied"); + expect(result.stderr).toContain("policy denied"); +}); + +test("times out long-running code", async () => { + const adapter: ExecutionAdapter = { + async invokeTool() { + return { ok: true, value: null }; + }, + emitOutput() {}, + }; + + const result = await runCodeWithAdapter( + request( + ` + await new Promise(() => {}); + `, + 25, + ), + adapter, + ); + + expect(result.status).toBe("timed_out"); + expect(result.error).toContain("timed out"); +}); + +test("sandboxed runner does not expose host globals", async () => { + const outputEvents: RuntimeOutputEvent[] = []; + const adapter = createRuntimeAdapter(new Map(), outputEvents); + + const result = await runCodeWithAdapter( + request(` + const checks = { + process: typeof process, + bun: typeof Bun, + fetch: typeof fetch, + fsEscape: [].constructor.constructor("return typeof process")(), + }; + console.log(JSON.stringify(checks)); + if (checks.process !== "undefined") throw new Error("process leaked"); + if (checks.bun !== "undefined") throw new Error("Bun leaked"); + if (checks.fetch !== "undefined") throw new Error("fetch leaked"); + if (checks.fsEscape !== "undefined") throw new Error("constructor escape leaked"); + return "ok"; + `), + adapter, + ); + + expect(result.status).toBe("completed"); + expect(result.stdout).toContain("\"process\":\"undefined\""); + expect(result.stdout).toContain("\"bun\":\"undefined\""); + expect(result.stdout).toContain("\"fetch\":\"undefined\""); + expect(result.stdout).toContain("\"fsEscape\":\"undefined\""); +}); + +test("runs openapi and graphql sourced tools through the runtime", async () => { + const server = Bun.serve({ + port: 0, + fetch: async (req) => { + const url = new URL(req.url); + + if (url.pathname === "/add" && req.method === "GET") { + const a = Number(url.searchParams.get("a") ?? "0"); + const b = Number(url.searchParams.get("b") ?? "0"); + return Response.json({ sum: a + b }); + } + + if (url.pathname === "/graphql" && req.method === "POST") { + const body = (await req.json()) as { query?: string; variables?: Record }; + const query = String(body.query ?? ""); + + if (query.includes("__schema")) { + return Response.json({ + data: { + __schema: { + queryType: { name: "Query" }, + mutationType: { name: "Mutation" }, + types: [ + { + kind: "OBJECT", + name: "Query", + fields: [ + { + name: "hello", + description: null, + args: [], + type: { kind: "SCALAR", name: "String", ofType: null }, + }, + ], + inputFields: null, + enumValues: null, + }, + { + kind: "OBJECT", + name: "Mutation", + fields: [ + { + name: "increment", + description: null, + args: [ + { + name: "value", + description: null, + defaultValue: null, + type: { + kind: "NON_NULL", + name: null, + ofType: { kind: "SCALAR", name: "Int", ofType: null }, + }, + }, + ], + type: { kind: "SCALAR", name: "Int", ofType: null }, + }, + ], + inputFields: null, + enumValues: null, + }, + { kind: "SCALAR", name: "String", fields: null, inputFields: null, enumValues: null }, + { kind: "SCALAR", name: "Int", fields: null, inputFields: null, enumValues: null }, + ], + }, + }, + }); + } + + if (query.includes("hello")) { + return Response.json({ data: { hello: "world" } }); + } + + if (query.includes("increment")) { + const value = Number(body.variables?.value ?? 0); + return Response.json({ data: { increment: value + 1 } }); + } + + return Response.json({ errors: [{ message: "Unknown query" }] }, { status: 400 }); + } + + return new Response("Not found", { status: 404 }); + }, + }); + + try { + const baseUrl = `http://127.0.0.1:${server.port}`; + const { tools } = await loadExternalTools([ + { + type: "openapi", + name: "calc", + baseUrl, + spec: { + openapi: "3.0.3", + info: { title: "Calc", version: "1.0.0" }, + paths: { + "/add": { + get: { + operationId: "addNumbers", + tags: ["math"], + parameters: [ + { name: "a", in: "query", required: true, schema: { type: "number" } }, + { name: "b", in: "query", required: true, schema: { type: "number" } }, + ], + responses: { + "200": { + description: "ok", + content: { + "application/json": { + schema: { + type: "object", + properties: { sum: { type: "number" } }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + type: "graphql", + name: "gql", + endpoint: `${baseUrl}/graphql`, + }, + ]); + + const toolMap = new Map(tools.map((tool) => [tool.path, tool])); + const outputEvents: RuntimeOutputEvent[] = []; + + const result = await runCodeWithAdapter( + request(` + const sum = await tools.calc.math.addnumbers({ a: 2, b: 5 }); + const hello = await tools.gql.query.hello({}); + const inc = await tools.gql.graphql({ + query: "mutation($value: Int!) { increment(value: $value) }", + variables: { value: 3 }, + }); + console.log("sum", sum.sum); + console.log("hello", hello.hello); + console.log("inc", inc.increment); + return sum.sum + inc.increment; + `), + createRuntimeAdapter(toolMap, outputEvents), + ); + + expect(result.status).toBe("completed"); + expect(result.stdout).toContain("sum 7"); + expect(result.stdout).toContain("hello world"); + expect(result.stdout).toContain("inc 4"); + expect(result.stdout).toContain("result: 11"); + expect(outputEvents.length).toBeGreaterThan(0); + } finally { + server.stop(true); + } +}); diff --git a/executor/apps/server/src/runtimes/runtime-core.ts b/executor/apps/server/src/runtimes/runtime-core.ts index ce11cc286..4d539bb53 100644 --- a/executor/apps/server/src/runtimes/runtime-core.ts +++ b/executor/apps/server/src/runtimes/runtime-core.ts @@ -1,4 +1,5 @@ import { APPROVAL_DENIED_PREFIX, TASK_TIMEOUT_MARKER } from "../execution-constants"; +import { Script, createContext } from "node:vm"; import type { ExecutionAdapter, SandboxExecutionRequest, @@ -70,9 +71,6 @@ export async function runCodeWithAdapter( const startedAt = Date.now(); const stdoutLines: string[] = []; const stderrLines: string[] = []; - const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor as new ( - ...args: string[] - ) => (...inputs: unknown[]) => Promise; const appendStdout = (line: string): void => { stdoutLines.push(line); @@ -106,11 +104,14 @@ export async function runCodeWithAdapter( error: (...args: unknown[]) => appendStderr(formatArgs(args)), }; - const runner = new AsyncFunction( - "tools", - "console", - `"use strict";\n${request.code}`, - ); + const context = createContext({ + tools, + console: consoleProxy, + setTimeout, + clearTimeout, + }); + + const runnerScript = new Script(`(async () => {\n"use strict";\n${request.code}\n})()`); let timeoutHandle: ReturnType | undefined; const timeoutPromise = new Promise((_resolve, reject) => { @@ -121,7 +122,7 @@ export async function runCodeWithAdapter( try { const value = await Promise.race([ - runner(tools, consoleProxy), + Promise.resolve(runnerScript.runInContext(context, { timeout: Math.max(1, request.timeoutMs) })), timeoutPromise, ]); @@ -138,7 +139,7 @@ export async function runCodeWithAdapter( }; } catch (error) { const message = error instanceof Error ? error.message : String(error); - if (message === TASK_TIMEOUT_MARKER) { + if (message === TASK_TIMEOUT_MARKER || message.includes("Script execution timed out")) { const timeoutMessage = `Execution timed out after ${request.timeoutMs}ms`; appendStderr(timeoutMessage); return { diff --git a/executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts b/executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts index c8dc9fdf9..6b5ffcff5 100644 --- a/executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts +++ b/executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts @@ -22,6 +22,7 @@ function buildRunnerScript(codeFilePath: string): string { return ` import { readFile } from "node:fs/promises"; import { randomUUID } from "node:crypto"; +import vm from "node:vm"; const RESULT_MARKER = ${JSON.stringify(RESULT_MARKER)}; const runId = process.env.EXECUTOR_RUN_ID; @@ -145,8 +146,13 @@ const consoleProxy = { error: (...args) => appendStderr(formatArgs(args)), }; -const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor; -const runner = new AsyncFunction("tools", "console", '"use strict";\\n' + userCode); +const context = vm.createContext({ + tools, + console: consoleProxy, + setTimeout, + clearTimeout, +}); +const runnerScript = new vm.Script("(async () => {\\n\"use strict\";\\n" + userCode + "\\n})()"); const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error("TASK_TIMEOUT")), requestTimeoutMs); @@ -154,7 +160,10 @@ const timeoutPromise = new Promise((_, reject) => { let result; try { - const value = await Promise.race([runner(tools, consoleProxy), timeoutPromise]); + const value = await Promise.race([ + Promise.resolve(runnerScript.runInContext(context, { timeout: Math.max(1, requestTimeoutMs) })), + timeoutPromise, + ]); if (value !== undefined) { appendStdout("result: " + formatArgs([value])); } @@ -169,7 +178,7 @@ try { } catch (error) { const message = error instanceof Error ? error.message : String(error); - if (message === "TASK_TIMEOUT") { + if (message === "TASK_TIMEOUT" || message.includes("Script execution timed out")) { const timeoutMessage = "Execution timed out after " + requestTimeoutMs + "ms"; appendStderr(timeoutMessage); result = { diff --git a/executor/apps/server/src/service.test.ts b/executor/apps/server/src/service.test.ts index a5553d31f..1770f8878 100644 --- a/executor/apps/server/src/service.test.ts +++ b/executor/apps/server/src/service.test.ts @@ -21,6 +21,7 @@ class TestExecutorDatabase extends ExecutorDatabase { private readonly tasks = new Map(); private readonly approvals = new Map(); private readonly events = new Map(); + private readonly policies = new Map(); constructor() { super("http://127.0.0.1:3210"); @@ -217,7 +218,43 @@ class TestExecutorDatabase extends ExecutorDatabase { } async listAccessPolicies(_workspaceId: string): Promise { - return []; + return [...this.policies.values()] + .filter((policy) => policy.workspaceId === _workspaceId) + .sort((a, b) => { + if (a.priority !== b.priority) { + return b.priority - a.priority; + } + return a.createdAt - b.createdAt; + }); + } + + async upsertAccessPolicy(params: { + id?: string; + workspaceId: string; + actorId?: string; + clientId?: string; + toolPathPattern: string; + decision: "allow" | "require_approval" | "deny"; + priority?: number; + }): Promise { + const now = Date.now(); + const id = params.id ?? `policy_${crypto.randomUUID()}`; + const existing = this.policies.get(id); + + const record: AccessPolicyRecord = { + id, + workspaceId: params.workspaceId, + actorId: params.actorId, + clientId: params.clientId, + toolPathPattern: params.toolPathPattern, + decision: params.decision, + priority: params.priority ?? 100, + createdAt: existing?.createdAt ?? now, + updatedAt: now, + }; + + this.policies.set(id, record); + return record; } async resolveCredential(): Promise { @@ -254,6 +291,11 @@ class InlineToolRuntime implements SandboxRuntime { readonly label = "Inline"; readonly description = "Calls one tool then exits"; + constructor( + private readonly toolPath = "admin.delete_data", + private readonly input: unknown = { key: "abc" }, + ) {} + async run( request: SandboxExecutionRequest, adapter: ExecutionAdapter, @@ -262,8 +304,8 @@ class InlineToolRuntime implements SandboxRuntime { const result = await adapter.invokeTool({ runId: request.taskId, callId: "call_inline_1", - toolPath: "admin.delete_data", - input: { key: "abc" }, + toolPath: this.toolPath, + input: this.input, }); if (!result.ok) { @@ -302,6 +344,21 @@ const tools: ToolDefinition[] = [ }, ]; +async function waitForTaskStatus( + service: ExecutorService, + taskId: string, + expected: TaskRecord["status"], + timeoutMs = 2_000, +): Promise { + let task = await service.getTask(taskId); + const waitUntil = Date.now() + timeoutMs; + while (task?.status !== expected && Date.now() < waitUntil) { + await Bun.sleep(25); + task = await service.getTask(taskId); + } + return task; +} + test("tool-level approval gates individual function call", async () => { const service = new ExecutorService( new TestExecutorDatabase(), @@ -326,13 +383,55 @@ test("tool-level approval gates individual function call", async () => { const resolved = await service.resolveApproval("ws_test", approvals[0]!.id, "approved", "test-user"); expect(resolved).toBeTruthy(); - let task = await service.getTask(created.task.id); - const waitUntil = Date.now() + 2_000; - while (task?.status !== "completed" && Date.now() < waitUntil) { - await Bun.sleep(25); - task = await service.getTask(created.task.id); - } + const task = await waitForTaskStatus(service, created.task.id, "completed"); expect(task?.status).toBe("completed"); expect((await service.listApprovals("ws_test", "approved")).length).toBe(1); }); + +test("policy deny blocks tool call without creating approval", async () => { + const service = new ExecutorService( + new TestExecutorDatabase(), + new TaskEventHub(), + [new InlineToolRuntime()], + tools, + ); + + await service.upsertAccessPolicy({ + workspaceId: "ws_deny", + toolPathPattern: "admin.delete_data", + decision: "deny", + priority: 500, + }); + + const created = await service.createTask({ + code: "unused", + runtimeId: "inline", + workspaceId: "ws_deny", + actorId: "actor_test", + }); + + const task = await waitForTaskStatus(service, created.task.id, "denied"); + expect(task?.status).toBe("denied"); + expect((await service.listPendingApprovals("ws_deny")).length).toBe(0); +}); + +test("unknown tool path fails task execution", async () => { + const service = new ExecutorService( + new TestExecutorDatabase(), + new TaskEventHub(), + [new InlineToolRuntime("admin.missing_tool")], + tools, + ); + + const created = await service.createTask({ + code: "unused", + runtimeId: "inline", + workspaceId: "ws_unknown", + actorId: "actor_test", + }); + + const task = await waitForTaskStatus(service, created.task.id, "failed"); + expect(task?.status).toBe("failed"); + expect(task?.error).toContain("Unknown tool"); +}); diff --git a/executor/bun.lock b/executor/bun.lock index 7b75693b1..c212ab3bc 100644 --- a/executor/bun.lock +++ b/executor/bun.lock @@ -9,6 +9,7 @@ }, "devDependencies": { "@types/bun": "latest", + "convex-test": "^0.0.41", "typescript": "latest", }, }, @@ -738,6 +739,8 @@ "convex": ["convex@1.31.7", "", { "dependencies": { "esbuild": "0.27.0", "prettier": "^3.0.0" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react", "react"], "bin": { "convex": "bin/main.js" } }, "sha512-PtNMe1mAIOvA8Yz100QTOaIdgt2rIuWqencVXrb4McdhxBHZ8IJ1eXTnrgCC9HydyilGT1pOn+KNqT14mqn9fQ=="], + "convex-test": ["convex-test@0.0.41", "", { "peerDependencies": { "convex": "^1.16.4" } }, "sha512-GPHeYFOi70n7UtW0eCEQFVhzl/+m8PvbWkDCbKpHLybI1MrScf4sVpGeM0cC2qmtxiduxa2nLPbehPalhh9oyQ=="], + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], diff --git a/executor/convex/database.test.ts b/executor/convex/database.test.ts new file mode 100644 index 000000000..0c57bee12 --- /dev/null +++ b/executor/convex/database.test.ts @@ -0,0 +1,138 @@ +import { expect, test } from "bun:test"; +import { convexTest } from "convex-test"; +import { api } from "./_generated/api"; +import schema from "./schema"; + +function setup() { + return convexTest(schema, { + "./database.ts": () => import("./database"), + "./_generated/api.js": () => import("./_generated/api.js"), + }); +} + +test("task lifecycle supports queue, run, and complete", async () => { + const t = setup(); + + const created = await t.mutation(api.database.createTask, { + id: "task_1", + code: "console.log('hello')", + runtimeId: "local-bun", + workspaceId: "ws_1", + actorId: "actor_1", + clientId: "web", + }); + + expect(created.id).toBe("task_1"); + expect(created.status).toBe("queued"); + + const queued = await t.query(api.database.listQueuedTaskIds, { limit: 10 }); + expect(queued).toEqual(["task_1"]); + + const running = await t.mutation(api.database.markTaskRunning, { taskId: "task_1" }); + expect(running?.status).toBe("running"); + + const secondRun = await t.mutation(api.database.markTaskRunning, { taskId: "task_1" }); + expect(secondRun).toBeNull(); + + const finished = await t.mutation(api.database.markTaskFinished, { + taskId: "task_1", + status: "completed", + stdout: "ok", + stderr: "", + exitCode: 0, + }); + expect(finished?.status).toBe("completed"); + + const queuedAfter = await t.query(api.database.listQueuedTaskIds, { limit: 10 }); + expect(queuedAfter).toEqual([]); +}); + +test("approval lifecycle tracks pending and resolution", async () => { + const t = setup(); + + await t.mutation(api.database.createTask, { + id: "task_2", + code: "await tools.admin.delete_data({ id: 'x' })", + runtimeId: "local-bun", + workspaceId: "ws_2", + actorId: "actor_2", + clientId: "web", + }); + + const createdApproval = await t.mutation(api.database.createApproval, { + id: "approval_1", + taskId: "task_2", + toolPath: "admin.delete_data", + input: { id: "x" }, + }); + expect(createdApproval.status).toBe("pending"); + + const pending = await t.query(api.database.listPendingApprovals, { workspaceId: "ws_2" }); + expect(pending.length).toBe(1); + expect(pending[0]?.task.id).toBe("task_2"); + + const resolved = await t.mutation(api.database.resolveApproval, { + approvalId: "approval_1", + decision: "approved", + reviewerId: "reviewer_1", + }); + expect(resolved?.status).toBe("approved"); + + const pendingAfter = await t.query(api.database.listPendingApprovals, { workspaceId: "ws_2" }); + expect(pendingAfter).toEqual([]); +}); + +test("workspace tool inventory applies policy decisions by context", async () => { + const t = setup(); + + await t.mutation(api.database.syncWorkspaceTools, { + workspaceId: "ws_tools", + tools: [ + { + path: "utils.get_time", + description: "Read current time", + approval: "auto", + }, + { + path: "admin.delete_data", + description: "Delete data", + approval: "required", + }, + ], + }); + + await t.mutation(api.database.upsertAccessPolicy, { + workspaceId: "ws_tools", + toolPathPattern: "admin.*", + decision: "deny", + priority: 100, + }); + + await t.mutation(api.database.upsertAccessPolicy, { + workspaceId: "ws_tools", + actorId: "actor_allow", + toolPathPattern: "admin.delete_data", + decision: "require_approval", + priority: 200, + }); + + const actorAllowTools = await t.query(api.database.listWorkspaceToolsForContext, { + workspaceId: "ws_tools", + actorId: "actor_allow", + clientId: "web", + }); + + const actorDenyTools = await t.query(api.database.listWorkspaceToolsForContext, { + workspaceId: "ws_tools", + actorId: "actor_other", + clientId: "web", + }); + + const actorAllow = actorAllowTools.filter((tool): tool is NonNullable => tool !== null); + const actorDeny = actorDenyTools.filter((tool): tool is NonNullable => tool !== null); + + expect(actorAllow.map((tool) => tool.path)).toEqual(["admin.delete_data", "utils.get_time"]); + expect(actorAllow.find((tool) => tool.path === "admin.delete_data")?.approval).toBe("required"); + + expect(actorDeny.map((tool) => tool.path)).toEqual(["utils.get_time"]); +}); diff --git a/executor/package.json b/executor/package.json index cf330a0e9..714e6780c 100644 --- a/executor/package.json +++ b/executor/package.json @@ -22,6 +22,7 @@ }, "devDependencies": { "@types/bun": "latest", + "convex-test": "^0.0.41", "typescript": "latest" } } From fe78c877ca75619f693237691d24388d7288b940 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sat, 7 Feb 2026 20:49:47 -0800 Subject: [PATCH 07/34] Enhance sandbox security and testing capabilities - Updated tests in `runtime-core.test.ts` to improve checks against global constructor and eval escapes, ensuring that sensitive host globals are not exposed. - Modified `runCodeWithAdapter` in `runtime-core.ts` and `vercel-sandbox-runtime.ts` to utilize a sandboxed context with stricter code generation settings, enhancing security during code execution. - Added new tests to verify that prototype pollution does not leak to the host environment, further strengthening the sandbox's isolation features. --- .../server/src/runtimes/runtime-core.test.ts | 73 ++++++++++++++++++- .../apps/server/src/runtimes/runtime-core.ts | 8 +- .../src/runtimes/vercel-sandbox-runtime.ts | 8 +- 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/executor/apps/server/src/runtimes/runtime-core.test.ts b/executor/apps/server/src/runtimes/runtime-core.test.ts index 60d21fbd1..023bcdc6a 100644 --- a/executor/apps/server/src/runtimes/runtime-core.test.ts +++ b/executor/apps/server/src/runtimes/runtime-core.test.ts @@ -135,17 +135,26 @@ test("sandboxed runner does not expose host globals", async () => { const result = await runCodeWithAdapter( request(` + let fsEscape = "unknown"; + try { + fsEscape = [].constructor.constructor("return typeof process")(); + } catch { + fsEscape = "blocked"; + } + const checks = { process: typeof process, bun: typeof Bun, fetch: typeof fetch, - fsEscape: [].constructor.constructor("return typeof process")(), + fsEscape, }; console.log(JSON.stringify(checks)); if (checks.process !== "undefined") throw new Error("process leaked"); if (checks.bun !== "undefined") throw new Error("Bun leaked"); if (checks.fetch !== "undefined") throw new Error("fetch leaked"); - if (checks.fsEscape !== "undefined") throw new Error("constructor escape leaked"); + if (checks.fsEscape !== "undefined" && checks.fsEscape !== "blocked") { + throw new Error("constructor escape leaked"); + } return "ok"; `), adapter, @@ -155,7 +164,65 @@ test("sandboxed runner does not expose host globals", async () => { expect(result.stdout).toContain("\"process\":\"undefined\""); expect(result.stdout).toContain("\"bun\":\"undefined\""); expect(result.stdout).toContain("\"fetch\":\"undefined\""); - expect(result.stdout).toContain("\"fsEscape\":\"undefined\""); + expect(result.stdout).toMatch(/"fsEscape":"(undefined|blocked)"/); +}); + +test("sandbox blocks global constructor and eval escapes", async () => { + const adapter = createRuntimeAdapter(new Map(), []); + + const result = await runCodeWithAdapter( + request(` + let ctorEscape = "unknown"; + let evalEscape = "unknown"; + let functionEscape = "unknown"; + + try { + ctorEscape = globalThis.constructor.constructor("return typeof process")(); + } catch { + ctorEscape = "blocked"; + } + + try { + evalEscape = eval("typeof process"); + } catch { + evalEscape = "blocked"; + } + + try { + functionEscape = Function("return typeof process")(); + } catch { + functionEscape = "blocked"; + } + + console.log(JSON.stringify({ ctorEscape, evalEscape, functionEscape })); + if (ctorEscape !== "undefined" && ctorEscape !== "blocked") throw new Error("ctor escape"); + if (evalEscape !== "undefined" && evalEscape !== "blocked") throw new Error("eval escape"); + if (functionEscape !== "undefined" && functionEscape !== "blocked") throw new Error("Function escape"); + `), + adapter, + ); + + expect(result.status).toBe("completed"); + expect(result.stdout).toMatch(/"ctorEscape":"(undefined|blocked)"/); + expect(result.stdout).toMatch(/"evalEscape":"(undefined|blocked)"/); + expect(result.stdout).toMatch(/"functionEscape":"(undefined|blocked)"/); +}); + +test("sandbox prototype pollution does not leak to host", async () => { + const marker = `__pwned_${crypto.randomUUID().replace(/-/g, "")}`; + expect(({} as Record)[marker]).toBeUndefined(); + + const adapter = createRuntimeAdapter(new Map(), []); + const result = await runCodeWithAdapter( + request(` + Object.prototype[${JSON.stringify(marker)}] = "yes"; + return "done"; + `), + adapter, + ); + + expect(result.status).toBe("completed"); + expect(({} as Record)[marker]).toBeUndefined(); }); test("runs openapi and graphql sourced tools through the runtime", async () => { diff --git a/executor/apps/server/src/runtimes/runtime-core.ts b/executor/apps/server/src/runtimes/runtime-core.ts index 4d539bb53..0dca4c229 100644 --- a/executor/apps/server/src/runtimes/runtime-core.ts +++ b/executor/apps/server/src/runtimes/runtime-core.ts @@ -104,12 +104,18 @@ export async function runCodeWithAdapter( error: (...args: unknown[]) => appendStderr(formatArgs(args)), }; - const context = createContext({ + const sandbox = Object.assign(Object.create(null), { tools, console: consoleProxy, setTimeout, clearTimeout, }); + const context = createContext(sandbox, { + codeGeneration: { + strings: false, + wasm: false, + }, + }); const runnerScript = new Script(`(async () => {\n"use strict";\n${request.code}\n})()`); diff --git a/executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts b/executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts index 6b5ffcff5..e6a17077d 100644 --- a/executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts +++ b/executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts @@ -146,12 +146,18 @@ const consoleProxy = { error: (...args) => appendStderr(formatArgs(args)), }; -const context = vm.createContext({ +const sandbox = Object.assign(Object.create(null), { tools, console: consoleProxy, setTimeout, clearTimeout, }); +const context = vm.createContext(sandbox, { + codeGeneration: { + strings: false, + wasm: false, + }, +}); const runnerScript = new vm.Script("(async () => {\\n\"use strict\";\\n" + userCode + "\\n})()"); const timeoutPromise = new Promise((_, reject) => { From 7641ff9ba103585ae95f2af288e3bbf0f20bde60 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sat, 7 Feb 2026 21:06:18 -0800 Subject: [PATCH 08/34] Expose executor run_code over MCP Add a /mcp endpoint with a run_code tool so external MCP clients can trigger executor runs, and extend MCP source config/UI with transport and query parameter support for anon scoping. --- executor/README.md | 33 ++- executor/apps/server/package.json | 3 +- executor/apps/server/src/index.ts | 6 + executor/apps/server/src/mcp-server.test.ts | 152 ++++++++++++ executor/apps/server/src/mcp-server.ts | 230 ++++++++++++++++++ executor/apps/server/src/service.ts | 25 ++ executor/apps/server/src/tool-sources.ts | 18 +- .../apps/web/src/components/tools-view.tsx | 53 +++- executor/bun.lock | 1 + executor/packages/client/src/index.ts | 2 +- 10 files changed, 514 insertions(+), 9 deletions(-) create mode 100644 executor/apps/server/src/mcp-server.test.ts create mode 100644 executor/apps/server/src/mcp-server.ts diff --git a/executor/README.md b/executor/README.md index a50a8c291..b346ccf3b 100644 --- a/executor/README.md +++ b/executor/README.md @@ -49,7 +49,7 @@ The browser persists `sessionId` and reuses it to maintain the same anonymous wo Legacy unscoped task/approval reads are disabled. Requests must include workspace context. -## External Tool Sources (MCP + OpenAPI) +## External Tool Sources (MCP + OpenAPI + GraphQL) You can load callable tools automatically from MCP servers and OpenAPI specs via env config: @@ -76,7 +76,26 @@ Example: ] ``` -OpenAPI tools are generated as namespaced callables (`tools...`), and MCP tools are generated as (`tools..`). +OpenAPI tools are generated as namespaced callables (`tools...`), GraphQL sources expose `tools..graphql` plus discoverable pseudo-tools, and MCP tools are generated as (`tools..`). + +For MCP sources, optional config keys: + +- `transport`: `"streamable-http"` or `"sse"` (default auto-detect) +- `queryParams`: object of query-string params appended to the MCP endpoint URL (useful for anonymous user scoping before OAuth auth provider integration) + +Example MCP config with query params: + +```json +{ + "type": "mcp", + "name": "my-mcp", + "url": "https://mcp.example.com/mcp", + "transport": "streamable-http", + "queryParams": { + "userId": "anon_123" + } +} +``` OpenAPI auth modes in source config: @@ -94,6 +113,16 @@ Tasks should include `workspaceId`, `actorId`, and optional `clientId` so policy The web UI supports adding MCP/OpenAPI sources per workspace and viewing discovered workspace tool inventory. +## MCP Endpoint (`run_code`) + +The executor server also exposes an MCP endpoint at `/mcp` with a `run_code` tool. + +- tool name: `run_code` +- required input: `code` +- optional: `runtimeId`, `timeoutMs`, `workspaceId`, `actorId`, `clientId`, `sessionId`, `waitForResult`, `resultTimeoutMs`, `metadata` + +If `workspaceId` / `actorId` are omitted, executor bootstraps anonymous context (optionally keyed by `sessionId`). + ## Vercel Sandbox Runtime This repo includes a `vercel-sandbox` runtime that runs generated code in Vercel Sandbox VMs while keeping the same `await tools.*(...)` flow. diff --git a/executor/apps/server/package.json b/executor/apps/server/package.json index ffa067163..158075907 100644 --- a/executor/apps/server/package.json +++ b/executor/apps/server/package.json @@ -6,7 +6,8 @@ "@apidevtools/swagger-parser": "^12.1.0", "@modelcontextprotocol/sdk": "^1.26.0", "@vercel/sandbox": "latest", - "convex": "latest" + "convex": "latest", + "zod": "^4.3.6" }, "scripts": { "dev": "bun --hot src/index.ts", diff --git a/executor/apps/server/src/index.ts b/executor/apps/server/src/index.ts index 1a75debc5..bede8a495 100644 --- a/executor/apps/server/src/index.ts +++ b/executor/apps/server/src/index.ts @@ -1,5 +1,6 @@ import { ExecutorDatabase } from "./database"; import { TaskEventHub } from "./events"; +import { handleMcpRequest } from "./mcp-server"; import { LocalBunRuntime } from "./runtimes/local-bun-runtime"; import { VercelSandboxRuntime } from "./runtimes/vercel-sandbox-runtime"; import { ExecutorService, getTaskTerminalState } from "./service"; @@ -181,6 +182,11 @@ async function createTaskEventsResponse(taskId: string): Promise { const server = Bun.serve({ port, routes: { + "/mcp": { + POST: async (request) => await handleMcpRequest(service, request), + GET: async (request) => await handleMcpRequest(service, request), + DELETE: async (request) => await handleMcpRequest(service, request), + }, "/api/health": { GET: () => json({ ok: true, tools: service.getBaseToolCount() }), }, diff --git a/executor/apps/server/src/mcp-server.test.ts b/executor/apps/server/src/mcp-server.test.ts new file mode 100644 index 000000000..c317dacb1 --- /dev/null +++ b/executor/apps/server/src/mcp-server.test.ts @@ -0,0 +1,152 @@ +import { expect, test } from "bun:test"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import { handleMcpRequest } from "./mcp-server"; +import type { AnonymousContext, CreateTaskInput, TaskRecord } from "./types"; + +class FakeMcpService { + private readonly tasks = new Map(); + private readonly sessions = new Map(); + + async createTask(input: CreateTaskInput): Promise<{ task: TaskRecord }> { + const id = `task_${crypto.randomUUID()}`; + const now = Date.now(); + const queued: TaskRecord = { + id, + code: input.code, + runtimeId: input.runtimeId ?? "local-bun", + status: "queued", + timeoutMs: input.timeoutMs ?? 15_000, + metadata: input.metadata ?? {}, + workspaceId: input.workspaceId, + actorId: input.actorId, + clientId: input.clientId, + createdAt: now, + updatedAt: now, + }; + this.tasks.set(id, queued); + + queueMicrotask(() => { + const current = this.tasks.get(id); + if (!current) return; + this.tasks.set(id, { + ...current, + status: "completed", + startedAt: current.createdAt + 1, + completedAt: current.createdAt + 2, + updatedAt: current.createdAt + 2, + exitCode: 0, + stdout: `ran:${input.code.slice(0, 20)}`, + stderr: "", + }); + }); + + return { task: queued }; + } + + async getTask(taskId: string, workspaceId?: string): Promise { + const task = this.tasks.get(taskId) ?? null; + if (!task) return null; + if (workspaceId && task.workspaceId !== workspaceId) return null; + return task; + } + + async bootstrapAnonymousContext(sessionId?: string): Promise { + if (sessionId && this.sessions.has(sessionId)) { + const existing = this.sessions.get(sessionId)!; + const updated = { ...existing, lastSeenAt: Date.now() }; + this.sessions.set(sessionId, updated); + return updated; + } + + const now = Date.now(); + const context: AnonymousContext = { + sessionId: sessionId ?? `anon_session_${crypto.randomUUID()}`, + workspaceId: `ws_${crypto.randomUUID()}`, + actorId: `anon_${crypto.randomUUID()}`, + clientId: "mcp", + createdAt: now, + lastSeenAt: now, + }; + this.sessions.set(context.sessionId, context); + return context; + } +} + +async function withMcpClient( + service: FakeMcpService, + run: (client: Client) => Promise, +): Promise { + const server = Bun.serve({ + port: 0, + fetch: (request) => { + const url = new URL(request.url); + if (url.pathname === "/mcp") { + return handleMcpRequest(service, request); + } + return new Response("Not found", { status: 404 }); + }, + }); + + const transport = new StreamableHTTPClientTransport(new URL(`http://127.0.0.1:${server.port}/mcp`)); + const client = new Client({ name: "executor-mcp-test", version: "1.0.0" }, { capabilities: {} }); + + try { + await client.connect(transport); + return await run(client); + } finally { + await transport.close().catch(() => {}); + await client.close().catch(() => {}); + server.stop(true); + } +} + +test("run_code MCP tool returns terminal task result", async () => { + const service = new FakeMcpService(); + + await withMcpClient(service, async (client) => { + const result = (await client.callTool({ + name: "run_code", + arguments: { + code: "console.log('hello from mcp')", + workspaceId: "ws_test", + actorId: "actor_test", + clientId: "assistant", + }, + })) as { + isError?: boolean; + content: Array<{ type: string; text?: string }>; + }; + + expect(result.isError).toBeUndefined(); + const text = result.content.find((part) => part.type === "text"); + expect(text?.type).toBe("text"); + if (text?.type === "text") { + expect(text.text).toContain("status: completed"); + expect(text.text).toContain("ran:console.log('hello f"); + } + }); +}); + +test("run_code MCP tool bootstraps anonymous context when workspace is omitted", async () => { + const service = new FakeMcpService(); + + await withMcpClient(service, async (client) => { + const result = (await client.callTool({ + name: "run_code", + arguments: { + code: "console.log('anon')", + sessionId: "mcp_session_test", + }, + })) as { + isError?: boolean; + structuredContent?: Record; + }; + + expect(result.isError).toBeUndefined(); + const structured = result.structuredContent; + expect(typeof structured?.workspaceId).toBe("string"); + expect(typeof structured?.actorId).toBe("string"); + expect(structured?.sessionId).toBe("mcp_session_test"); + }); +}); diff --git a/executor/apps/server/src/mcp-server.ts b/executor/apps/server/src/mcp-server.ts new file mode 100644 index 000000000..9ddb77959 --- /dev/null +++ b/executor/apps/server/src/mcp-server.ts @@ -0,0 +1,230 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js"; +import { z } from "zod"; +import { getTaskTerminalState } from "./service"; +import type { AnonymousContext, CreateTaskInput, TaskRecord } from "./types"; + +interface McpExecutorService { + createTask(input: CreateTaskInput): Promise<{ task: TaskRecord }>; + getTask(taskId: string, workspaceId?: string): Promise; + bootstrapAnonymousContext(sessionId?: string): Promise; +} + +const RUN_CODE_INPUT = { + code: z.string().min(1), + timeoutMs: z.number().int().min(1).max(600_000).optional(), + runtimeId: z.string().optional(), + metadata: z.record(z.string(), z.any()).optional(), + workspaceId: z.string().optional(), + actorId: z.string().optional(), + clientId: z.string().optional(), + sessionId: z.string().optional(), + waitForResult: z.boolean().optional(), + resultTimeoutMs: z.number().int().min(100).max(900_000).optional(), +} as const; + +type RunCodeInput = { + code: string; + timeoutMs?: number; + runtimeId?: string; + metadata?: Record; + workspaceId?: string; + actorId?: string; + clientId?: string; + sessionId?: string; + waitForResult?: boolean; + resultTimeoutMs?: number; +}; + +function asCodeBlock(language: string, value: string): string { + return `\n\n\`\`\`${language}\n${value}\n\`\`\``; +} + +function textContent(text: string): { type: "text"; text: string } { + return { type: "text", text }; +} + +function summarizeTask(task: TaskRecord): string { + const lines = [ + `taskId: ${task.id}`, + `status: ${task.status}`, + `runtimeId: ${task.runtimeId}`, + ]; + + if (task.exitCode !== undefined) { + lines.push(`exitCode: ${task.exitCode}`); + } + + if (task.error) { + lines.push(`error: ${task.error}`); + } + + let text = lines.join("\n"); + if (task.stdout && task.stdout.trim()) { + text += asCodeBlock("text", task.stdout); + } + if (task.stderr && task.stderr.trim()) { + text += asCodeBlock("text", task.stderr); + } + return text; +} + +async function waitForTerminalTask( + service: McpExecutorService, + taskId: string, + workspaceId: string, + waitTimeoutMs: number, +): Promise { + const deadline = Date.now() + waitTimeoutMs; + while (Date.now() < deadline) { + const task = await service.getTask(taskId, workspaceId); + if (!task) { + return null; + } + if (getTaskTerminalState(task.status)) { + return task; + } + await Bun.sleep(300); + } + return await service.getTask(taskId, workspaceId); +} + +async function resolveExecutionContext( + service: McpExecutorService, + input: RunCodeInput, + mcpSessionId?: string, +): Promise<{ workspaceId: string; actorId: string; clientId?: string; sessionId?: string }> { + if (input.workspaceId || input.actorId) { + if (!input.workspaceId || !input.actorId) { + throw new Error("workspaceId and actorId must be provided together"); + } + return { + workspaceId: input.workspaceId, + actorId: input.actorId, + clientId: input.clientId, + sessionId: input.sessionId, + }; + } + + const seededSessionId = input.sessionId ?? (mcpSessionId ? `mcp_${mcpSessionId}` : undefined); + const anonymous = await service.bootstrapAnonymousContext(seededSessionId); + return { + workspaceId: anonymous.workspaceId, + actorId: anonymous.actorId, + clientId: input.clientId ?? anonymous.clientId, + sessionId: anonymous.sessionId, + }; +} + +function createRunCodeTool(service: McpExecutorService) { + return async ( + input: RunCodeInput, + extra: { sessionId?: string }, + ): Promise<{ + content: Array<{ type: "text"; text: string }>; + structuredContent?: Record; + isError?: boolean; + }> => { + const context = await resolveExecutionContext(service, input, extra.sessionId); + + const created = await service.createTask({ + code: input.code, + timeoutMs: input.timeoutMs, + runtimeId: input.runtimeId, + metadata: input.metadata, + workspaceId: context.workspaceId, + actorId: context.actorId, + clientId: context.clientId, + }); + + const waitForResult = input.waitForResult ?? true; + if (!waitForResult) { + return { + content: [textContent(`Queued task ${created.task.id}`)], + structuredContent: { + taskId: created.task.id, + status: created.task.status, + workspaceId: context.workspaceId, + actorId: context.actorId, + sessionId: context.sessionId, + }, + }; + } + + const waitTimeoutMs = input.resultTimeoutMs ?? Math.max((input.timeoutMs ?? created.task.timeoutMs) + 10_000, 15_000); + const task = await waitForTerminalTask(service, created.task.id, context.workspaceId, waitTimeoutMs); + + if (!task) { + return { + content: [textContent(`Task ${created.task.id} not found while waiting for result`)], + isError: true, + }; + } + + if (!getTaskTerminalState(task.status)) { + return { + content: [textContent(`Task ${task.id} is still ${task.status}`)], + structuredContent: { + taskId: task.id, + status: task.status, + workspaceId: context.workspaceId, + actorId: context.actorId, + sessionId: context.sessionId, + }, + }; + } + + const isError = task.status !== "completed"; + return { + content: [textContent(summarizeTask(task))], + structuredContent: { + taskId: task.id, + status: task.status, + runtimeId: task.runtimeId, + exitCode: task.exitCode, + error: task.error, + stdout: task.stdout, + stderr: task.stderr, + workspaceId: context.workspaceId, + actorId: context.actorId, + sessionId: context.sessionId, + }, + ...(isError ? { isError: true } : {}), + }; + }; +} + +function createMcpServer(service: McpExecutorService): McpServer { + const mcp = new McpServer( + { name: "executor", version: "0.1.0" }, + { capabilities: { tools: {} } }, + ); + + mcp.registerTool( + "run_code", + { + description: + "Run JavaScript/TypeScript code in the executor runtime. Defaults to waiting for terminal result and returns stdout/stderr.", + inputSchema: RUN_CODE_INPUT, + }, + createRunCodeTool(service), + ); + + return mcp; +} + +export async function handleMcpRequest(service: McpExecutorService, request: Request): Promise { + const transport = new WebStandardStreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true, + }); + const mcp = createMcpServer(service); + + try { + await mcp.connect(transport); + return await transport.handleRequest(request); + } finally { + await transport.close().catch(() => {}); + await mcp.close().catch(() => {}); + } +} diff --git a/executor/apps/server/src/service.ts b/executor/apps/server/src/service.ts index 74afab786..be1bfa5bf 100644 --- a/executor/apps/server/src/service.ts +++ b/executor/apps/server/src/service.ts @@ -86,6 +86,31 @@ function normalizeExternalToolSource(raw: { if (typeof merged.url !== "string" || merged.url.trim().length === 0) { throw new Error(`MCP source '${raw.name}' missing url`); } + + if ( + merged.transport !== undefined && + merged.transport !== "sse" && + merged.transport !== "streamable-http" + ) { + throw new Error(`MCP source '${raw.name}' has invalid transport (expected 'sse' or 'streamable-http')`); + } + + if (merged.queryParams !== undefined) { + const queryParams = merged.queryParams; + if (!queryParams || typeof queryParams !== "object" || Array.isArray(queryParams)) { + throw new Error(`MCP source '${raw.name}' queryParams must be an object`); + } + + for (const [key, value] of Object.entries(queryParams as Record)) { + if (typeof key !== "string" || key.trim().length === 0) { + throw new Error(`MCP source '${raw.name}' queryParams contains an invalid key`); + } + if (typeof value !== "string") { + throw new Error(`MCP source '${raw.name}' queryParams values must be strings`); + } + } + } + return merged as unknown as ExternalToolSourceConfig; } diff --git a/executor/apps/server/src/tool-sources.ts b/executor/apps/server/src/tool-sources.ts index a2eac9ba6..7fee8d142 100644 --- a/executor/apps/server/src/tool-sources.ts +++ b/executor/apps/server/src/tool-sources.ts @@ -11,6 +11,7 @@ export interface McpToolSourceConfig { name: string; url: string; transport?: "sse" | "streamable-http"; + queryParams?: Record; defaultApproval?: ToolApprovalMode; overrides?: Record; } @@ -113,9 +114,16 @@ function jsonSchemaTypeHint(schema: unknown, depth = 0): string { async function connectMcp( url: string, + queryParams: Record | undefined, preferredTransport?: "sse" | "streamable-http", ): Promise<{ client: Client; close: () => Promise }> { const endpoint = new URL(url); + if (queryParams) { + for (const [key, value] of Object.entries(queryParams)) { + if (!key.trim()) continue; + endpoint.searchParams.set(key, value); + } + } const client = new Client( { name: "executor-tool-loader", version: "0.1.0" }, { capabilities: {} }, @@ -141,7 +149,13 @@ async function connectMcp( } async function loadMcpTools(config: McpToolSourceConfig): Promise { - let connection = await connectMcp(config.url, config.transport); + const queryParams = config.queryParams + ? Object.fromEntries( + Object.entries(config.queryParams).map(([key, value]) => [key, String(value)]), + ) + : undefined; + + let connection = await connectMcp(config.url, queryParams, config.transport); async function callToolWithReconnect(name: string, input: Record): Promise { try { @@ -158,7 +172,7 @@ async function loadMcpTools(config: McpToolSourceConfig): Promise("auto"); + const [mcpActorQueryParamKey, setMcpActorQueryParamKey] = useState("userId"); const [submitting, setSubmitting] = useState(false); const [addingPreset, setAddingPreset] = useState(null); @@ -286,6 +288,8 @@ function AddSourceDialog({ setName(""); setEndpoint(""); setBaseUrl(""); + setMcpTransport("auto"); + setMcpActorQueryParamKey("userId"); setNameManuallyEdited(false); setPresetsOpen(false); setAddingPreset(null); @@ -341,7 +345,13 @@ function AddSourceDialog({ try { const config: Record = type === "mcp" - ? { url: endpoint } + ? { + url: endpoint, + ...(mcpTransport !== "auto" ? { transport: mcpTransport } : {}), + ...(mcpActorQueryParamKey.trim() && context.actorId + ? { queryParams: { [mcpActorQueryParamKey.trim()]: context.actorId } } + : {}), + } : type === "graphql" ? { endpoint: endpoint } : { spec: endpoint, ...(baseUrl ? { baseUrl } : {}) }; @@ -378,7 +388,7 @@ function AddSourceDialog({ setMcpTransport(v as "auto" | "streamable-http" | "sse")} + > + + + + + + Auto (streamable, then SSE) + + + Streamable HTTP + + + SSE + + + +
+
+ + setMcpActorQueryParamKey(e.target.value)} + placeholder="userId" + className="h-8 text-xs font-mono bg-background" + /> +
+ + )}
) : ( diff --git a/executor/bun.lock b/executor/bun.lock index c212ab3bc..ae72e971e 100644 --- a/executor/bun.lock +++ b/executor/bun.lock @@ -20,6 +20,7 @@ "@modelcontextprotocol/sdk": "^1.26.0", "@vercel/sandbox": "latest", "convex": "latest", + "zod": "^4.3.6", }, "devDependencies": { "@types/bun": "latest", diff --git a/executor/packages/client/src/index.ts b/executor/packages/client/src/index.ts index 9528d6331..d67486f3e 100644 --- a/executor/packages/client/src/index.ts +++ b/executor/packages/client/src/index.ts @@ -132,7 +132,7 @@ export async function upsertToolSource( id?: string; workspaceId: string; name: string; - type: "mcp" | "openapi"; + type: "mcp" | "openapi" | "graphql"; config: Record; enabled?: boolean; }, From 80503a4cf25f4944c11a9ae9cdcbbcf7f7228cb5 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sat, 7 Feb 2026 21:25:20 -0800 Subject: [PATCH 09/34] Replace hand-rolled contracts/client with Elysia + Eden Treaty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate executor server from raw Bun.serve() to Elysia with typed route schemas, enabling Eden Treaty to infer types directly from the server definition. This eliminates the need for separate contracts and client packages — the server is the contract. - Rewrite executor server index.ts using Elysia typed routes - Replace assistant adapter with 8-line Eden Treaty client - Delete executor/packages/contracts and executor/packages/client - Delete dead web app api.ts (migrated to Convex) - Remove tsconfig path aliases for deleted packages --- assistant/bun.lock | 72 ++ .../agent-executor-adapter/package.json | 4 + .../agent-executor-adapter/src/index.test.ts | 87 ++ .../agent-executor-adapter/src/index.ts | 60 +- .../agent-executor-adapter/tsconfig.json | 14 + executor/apps/server/package.json | 2 + executor/apps/server/src/index.ts | 772 +++++++++--------- executor/apps/web/src/lib/api.ts | 221 ----- executor/bun.lock | 49 +- executor/packages/client/package.json | 11 - executor/packages/client/src/index.ts | 258 ------ executor/packages/contracts/package.json | 8 - executor/packages/contracts/src/index.ts | 154 ---- executor/tsconfig.json | 10 +- 14 files changed, 602 insertions(+), 1120 deletions(-) create mode 100644 assistant/bun.lock create mode 100644 assistant/packages/agent-executor-adapter/src/index.test.ts create mode 100644 assistant/packages/agent-executor-adapter/tsconfig.json delete mode 100644 executor/apps/web/src/lib/api.ts delete mode 100644 executor/packages/client/package.json delete mode 100644 executor/packages/client/src/index.ts delete mode 100644 executor/packages/contracts/package.json delete mode 100644 executor/packages/contracts/src/index.ts diff --git a/assistant/bun.lock b/assistant/bun.lock new file mode 100644 index 000000000..6bdc003a1 --- /dev/null +++ b/assistant/bun.lock @@ -0,0 +1,72 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "assistant-monorepo", + "devDependencies": { + "@types/bun": "latest", + "typescript": "latest", + }, + }, + "packages/agent-executor-adapter": { + "name": "@assistant/agent-executor-adapter", + "dependencies": { + "@elysiajs/eden": "^1.4.6", + "elysia": "^1.4.22", + }, + "devDependencies": { + "@types/bun": "latest", + }, + }, + }, + "packages": { + "@assistant/agent-executor-adapter": ["@assistant/agent-executor-adapter@workspace:packages/agent-executor-adapter"], + + "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], + + "@elysiajs/eden": ["@elysiajs/eden@1.4.6", "", { "peerDependencies": { "elysia": ">=1.4.19" } }, "sha512-Tsa4NwXEWg/u73vWiYZQ3L5/ecgZSxqiEjYwpS+4qBKXeTZqZKl2hcgHJSVBL+InEDMi35Xugct7qyAXE5oM4Q=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.34.48", "", {}, "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA=="], + + "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], + + "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + + "@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="], + + "@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="], + + "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], + + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "elysia": ["elysia@1.4.22", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.6", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Q90VCb1RVFxnFaRV0FDoSylESQQLWgLHFmWciQJdX9h3b2cSasji9KWEUvaJuy/L9ciAGg4RAhUVfsXHg5K2RQ=="], + + "exact-mirror": ["exact-mirror@0.2.6", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-7s059UIx9/tnOKSySzUk5cPGkoILhTE4p6ncf6uIPaQ+9aRBQzQjc9+q85l51+oZ+P6aBxh084pD0CzBQPcFUA=="], + + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], + + "file-type": ["file-type@21.3.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + + "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], + + "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + } +} diff --git a/assistant/packages/agent-executor-adapter/package.json b/assistant/packages/agent-executor-adapter/package.json index 92b9773bc..2c9ec5c60 100644 --- a/assistant/packages/agent-executor-adapter/package.json +++ b/assistant/packages/agent-executor-adapter/package.json @@ -8,6 +8,10 @@ "scripts": { "test": "bun test src/" }, + "dependencies": { + "@elysiajs/eden": "^1.4.6", + "elysia": "^1.4.22" + }, "devDependencies": { "@types/bun": "latest" } diff --git a/assistant/packages/agent-executor-adapter/src/index.test.ts b/assistant/packages/agent-executor-adapter/src/index.test.ts new file mode 100644 index 000000000..da677ec32 --- /dev/null +++ b/assistant/packages/agent-executor-adapter/src/index.test.ts @@ -0,0 +1,87 @@ +import { expect, test } from "bun:test"; +import { Elysia, t } from "elysia"; +import { treaty } from "@elysiajs/eden"; + +// Minimal Elysia server that mimics the executor's task creation route +function createTestServer() { + return new Elysia() + .get("/api/health", () => ({ ok: true as const, tools: 5 })) + .post("/api/tasks", ({ body }) => ({ + taskId: "task_test_1", + status: "queued" as const, + }), { + body: t.Object({ + code: t.String(), + timeoutMs: t.Optional(t.Number()), + runtimeId: t.Optional(t.String()), + metadata: t.Optional(t.Record(t.String(), t.Unknown())), + workspaceId: t.String(), + actorId: t.String(), + clientId: t.Optional(t.String()), + }), + }) + .post("/api/auth/anonymous/bootstrap", ({ body }) => ({ + sessionId: body.sessionId ?? "sess_test", + workspaceId: "ws_anon", + actorId: "actor_anon", + clientId: "client_anon", + createdAt: Date.now(), + lastSeenAt: Date.now(), + }), { + body: t.Object({ + sessionId: t.Optional(t.String()), + }), + }) + .listen(0); +} + +test("Eden Treaty client can create a task with full type safety", async () => { + const server = createTestServer(); + const client = treaty(`http://127.0.0.1:${server.server!.port}`); + + try { + const { data, error } = await client.api.tasks.post({ + code: "console.log('hello')", + workspaceId: "ws_test", + actorId: "actor_test", + }); + + expect(error).toBeNull(); + expect(data!.taskId).toBe("task_test_1"); + expect(data!.status).toBe("queued"); + } finally { + server.stop(true); + } +}); + +test("Eden Treaty client can check health", async () => { + const server = createTestServer(); + const client = treaty(`http://127.0.0.1:${server.server!.port}`); + + try { + const { data, error } = await client.api.health.get(); + + expect(error).toBeNull(); + expect(data!.ok).toBe(true); + expect(data!.tools).toBe(5); + } finally { + server.stop(true); + } +}); + +test("Eden Treaty client can bootstrap anonymous context", async () => { + const server = createTestServer(); + const client = treaty(`http://127.0.0.1:${server.server!.port}`); + + try { + const { data, error } = await client.api.auth.anonymous.bootstrap.post({ + sessionId: "my-session", + }); + + expect(error).toBeNull(); + expect(data!.workspaceId).toBe("ws_anon"); + expect(data!.sessionId).toBe("my-session"); + } finally { + server.stop(true); + } +}); diff --git a/assistant/packages/agent-executor-adapter/src/index.ts b/assistant/packages/agent-executor-adapter/src/index.ts index c57b942a1..c0c979fb1 100644 --- a/assistant/packages/agent-executor-adapter/src/index.ts +++ b/assistant/packages/agent-executor-adapter/src/index.ts @@ -1,58 +1,8 @@ -export interface RunCodeRequest { - code: string; - timeoutMs?: number; - runtimeId?: string; -} - -export interface RunCodeResponse { - taskId: string; -} - -export async function runCode( - executorBaseUrl: string, - request: RunCodeRequest, -): Promise { - const response = await fetch(`${executorBaseUrl.replace(/\/$/, "")}/api/tasks`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(request), - }); - - if (!response.ok) { - throw new Error(`Failed to create executor task: HTTP ${response.status}`); - } +import { treaty } from "@elysiajs/eden"; +import type { App } from "@executor/server/src/index"; - const payload = (await response.json()) as { taskId: string }; - return { taskId: payload.taskId }; +export function createExecutorClient(baseUrl: string) { + return treaty(baseUrl); } -export function subscribeToTaskEvents( - executorBaseUrl: string, - taskId: string, - onMessage: (eventType: string, data: unknown) => void, -): EventSource { - const streamUrl = `${executorBaseUrl.replace(/\/$/, "")}/api/tasks/${encodeURIComponent(taskId)}/events`; - const source = new EventSource(streamUrl); - - source.onmessage = (event) => { - let data: unknown = event.data; - try { - data = JSON.parse(event.data); - } catch { - // Keep raw message if JSON parse fails. - } - onMessage("message", data); - }; - - source.addEventListener("task", (event) => { - const message = event as MessageEvent; - onMessage("task", JSON.parse(message.data)); - }); - - source.addEventListener("approval", (event) => { - const message = event as MessageEvent; - onMessage("approval", JSON.parse(message.data)); - }); - - return source; -} +export type ExecutorClient = ReturnType; diff --git a/assistant/packages/agent-executor-adapter/tsconfig.json b/assistant/packages/agent-executor-adapter/tsconfig.json new file mode 100644 index 000000000..daa7d617d --- /dev/null +++ b/assistant/packages/agent-executor-adapter/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "paths": { + "@executor/server/*": ["../../../executor/apps/server/*"] + } + }, + "include": ["src"] +} diff --git a/executor/apps/server/package.json b/executor/apps/server/package.json index 158075907..2c884fc56 100644 --- a/executor/apps/server/package.json +++ b/executor/apps/server/package.json @@ -4,9 +4,11 @@ "type": "module", "dependencies": { "@apidevtools/swagger-parser": "^12.1.0", + "@elysiajs/eden": "^1.4.6", "@modelcontextprotocol/sdk": "^1.26.0", "@vercel/sandbox": "latest", "convex": "latest", + "elysia": "^1.4.22", "zod": "^4.3.6" }, "scripts": { diff --git a/executor/apps/server/src/index.ts b/executor/apps/server/src/index.ts index bede8a495..cd0c47134 100644 --- a/executor/apps/server/src/index.ts +++ b/executor/apps/server/src/index.ts @@ -1,3 +1,4 @@ +import { Elysia, t } from "elysia"; import { ExecutorDatabase } from "./database"; import { TaskEventHub } from "./events"; import { handleMcpRequest } from "./mcp-server"; @@ -9,15 +10,13 @@ import { loadExternalTools, parseToolSourcesFromEnv } from "./tool-sources"; import { DEFAULT_TOOLS } from "./tools"; import type { ApprovalStatus, - CredentialScope, - CreateTaskInput, - PendingApprovalRecord, - PolicyDecision, TaskStatus, ToolCallRequest, RuntimeOutputEvent, } from "./types"; +// ── Bootstrap ── + const port = Number(Bun.env.PORT ?? "4001"); const autoFunnelEnabled = Bun.env.EXECUTOR_AUTO_TAILSCALE_FUNNEL !== "0"; const explicitInternalBaseUrl = Bun.env.EXECUTOR_INTERNAL_BASE_URL ?? Bun.env.EXECUTOR_PUBLIC_BASE_URL; @@ -65,51 +64,9 @@ const service = new ExecutorService(new ExecutorDatabase(), new TaskEventHub(), autoExecuteTasks: Bun.env.EXECUTOR_SERVER_AUTO_EXECUTE === "1", }); -const jsonHeaders = { - "content-type": "application/json; charset=utf-8", - "access-control-allow-origin": "*", - "access-control-allow-methods": "GET,POST,DELETE,OPTIONS", - "access-control-allow-headers": "content-type", -}; - -function json(data: unknown, status = 200): Response { - return new Response(JSON.stringify(data), { - status, - headers: jsonHeaders, - }); -} - -function error(status: number, message: string): Response { - return json({ error: message }, status); -} - -function isInternalAuthorized(request: Request): boolean { - if (!internalToken) { - return true; - } - - const header = request.headers.get("authorization"); - if (!header || !header.startsWith("Bearer ")) { - return false; - } - - return header.slice("Bearer ".length) === internalToken; -} - -async function parseBody(request: Request): Promise { - try { - return (await request.json()) as T; - } catch { - return null; - } -} - -async function createTaskEventsResponse(taskId: string): Promise { - const task = await service.getTask(taskId); - if (!task) { - return error(404, "Task not found"); - } +// ── SSE helper ── +function createTaskEventsResponse(taskId: string): Response | Promise { const encoder = new TextEncoder(); let unsubscribe: (() => void) | undefined; let keepalive: ReturnType | undefined; @@ -117,6 +74,12 @@ async function createTaskEventsResponse(taskId: string): Promise { const stream = new ReadableStream({ start(controller) { void (async () => { + const task = await service.getTask(taskId); + if (!task) { + controller.close(); + return; + } + const replay = await service.listTaskEvents(taskId); for (const event of replay) { const frame = `event: ${event.eventName}\ndata: ${JSON.stringify(event)}\n\n`; @@ -179,359 +142,392 @@ async function createTaskEventsResponse(taskId: string): Promise { }); } -const server = Bun.serve({ - port, - routes: { - "/mcp": { - POST: async (request) => await handleMcpRequest(service, request), - GET: async (request) => await handleMcpRequest(service, request), - DELETE: async (request) => await handleMcpRequest(service, request), - }, - "/api/health": { - GET: () => json({ ok: true, tools: service.getBaseToolCount() }), - }, - "/api/auth/anonymous/bootstrap": { - POST: async (request) => { - const body = await parseBody<{ sessionId?: string }>(request); - const context = await service.bootstrapAnonymousContext(body?.sessionId); - return json(context, 201); - }, - }, - "/api/runtime-targets": { - GET: () => json(service.listRuntimes()), - }, - "/api/tools": { - GET: async (request) => { - const query = new URL(request.url).searchParams; - const workspaceId = query.get("workspaceId"); - const actorId = query.get("actorId") ?? undefined; - const clientId = query.get("clientId") ?? undefined; - - if (!workspaceId) { - return error(400, "workspaceId is required"); - } - - return json(await service.listTools({ workspaceId, actorId, clientId })); - }, - }, - "/api/tool-sources": { - GET: async (request) => { - const workspaceId = new URL(request.url).searchParams.get("workspaceId"); - if (!workspaceId) { - return error(400, "workspaceId is required"); - } - return json(await service.listToolSources(workspaceId)); - }, - POST: async (request) => { - const body = await parseBody<{ - id?: string; - workspaceId?: string; - name?: string; - type?: "mcp" | "openapi" | "graphql"; - config?: Record; - enabled?: boolean; - }>(request); - - if (!body || !body.workspaceId || !body.name || !body.type || !body.config) { - return error(400, "workspaceId, name, type, and config are required"); - } - - if (body.type !== "mcp" && body.type !== "openapi" && body.type !== "graphql") { - return error(400, "type must be 'mcp', 'openapi', or 'graphql'"); - } - - try { - const source = await service.upsertToolSource({ - id: body.id, - workspaceId: body.workspaceId, - name: body.name, - type: body.type, - config: body.config, - enabled: body.enabled, - }); - return json(source, body.id ? 200 : 201); - } catch (cause) { - return error(400, cause instanceof Error ? cause.message : String(cause)); - } - }, - }, - "/api/tool-sources/:sourceId": { - DELETE: async (request) => { - const workspaceId = new URL(request.url).searchParams.get("workspaceId"); - if (!workspaceId) { - return error(400, "workspaceId is required"); - } - - const deleted = await service.deleteToolSource(workspaceId, request.params.sourceId); - if (!deleted) { - return error(404, "Tool source not found"); - } - return json({ ok: true }); - }, - }, - "/api/tasks": { - GET: async (request) => { - const workspaceId = new URL(request.url).searchParams.get("workspaceId"); - if (!workspaceId) { - return error(400, "workspaceId is required"); - } - return json(await service.listTasks(workspaceId)); - }, - POST: async (request) => { - const body = await parseBody(request); - if (!body) { - return error(400, "Invalid JSON body"); - } - - try { - const created = await service.createTask(body); - return json({ taskId: created.task.id, status: created.task.status }, 201); - } catch (cause) { - return error(400, cause instanceof Error ? cause.message : String(cause)); - } - }, - }, - "/api/tasks/:taskId": { - GET: async (request) => { - const workspaceId = new URL(request.url).searchParams.get("workspaceId"); - if (!workspaceId) { - return error(400, "workspaceId is required"); - } - - const task = await service.getTask(request.params.taskId, workspaceId); - if (!task) { - return error(404, "Task not found"); - } - return json(task); - }, - }, - "/api/tasks/:taskId/events": { - GET: async (request) => { - const workspaceId = new URL(request.url).searchParams.get("workspaceId"); - if (!workspaceId) { - return error(400, "workspaceId is required"); - } - - const task = await service.getTask(request.params.taskId, workspaceId); - if (!task) { - return error(404, "Task not found"); - } - return await createTaskEventsResponse(request.params.taskId); - }, - }, - "/api/approvals": { - GET: async (request) => { - const query = new URL(request.url).searchParams; - const workspaceId = query.get("workspaceId"); - if (!workspaceId) { - return error(400, "workspaceId is required"); - } - const status = query.get("status") as ApprovalStatus | null; - - if (status === "pending") { - return json((await service.listPendingApprovals(workspaceId)) satisfies PendingApprovalRecord[]); - } - - if (status && status !== "approved" && status !== "denied") { - return error(400, "Invalid approval status"); - } +// ── Internal auth guard ── - return json(await service.listApprovals(workspaceId, status ?? undefined)); - }, - }, - "/api/approvals/:approvalId": { - POST: async (request) => { - const body = await parseBody<{ - workspaceId?: string; - decision?: "approved" | "denied"; - reviewerId?: string; - reason?: string; - }>(request); - - if (!body || !body.workspaceId || (body.decision !== "approved" && body.decision !== "denied")) { - return error(400, "workspaceId and decision are required"); - } - - const resolved = await service.resolveApproval( - body.workspaceId, - request.params.approvalId, - body.decision, - body.reviewerId, - body.reason, - ); - - if (!resolved) { - return error(404, "Approval not found or already resolved"); - } - - return json(resolved); - }, - }, - "/api/policies": { - GET: async (request) => { - const query = new URL(request.url).searchParams; - const workspaceId = query.get("workspaceId"); - if (!workspaceId) { - return error(400, "workspaceId is required"); - } - return json(await service.listAccessPolicies(workspaceId)); - }, - POST: async (request) => { - const body = await parseBody<{ - id?: string; - workspaceId?: string; - actorId?: string; - clientId?: string; - toolPathPattern?: string; - decision?: PolicyDecision; - priority?: number; - }>(request); - - if (!body || !body.workspaceId || !body.toolPathPattern || !body.decision) { - return error(400, "workspaceId, toolPathPattern, and decision are required"); - } - - if (body.decision !== "allow" && body.decision !== "require_approval" && body.decision !== "deny") { - return error(400, "Invalid decision"); - } - - return json(await service.upsertAccessPolicy({ - id: body.id, - workspaceId: body.workspaceId, - actorId: body.actorId, - clientId: body.clientId, - toolPathPattern: body.toolPathPattern, - decision: body.decision, - priority: body.priority, - }), body.id ? 200 : 201); - }, - }, - "/api/credentials": { - GET: async (request) => { - const query = new URL(request.url).searchParams; - const workspaceId = query.get("workspaceId"); - if (!workspaceId) { - return error(400, "workspaceId is required"); - } - return json(await service.listCredentials(workspaceId)); - }, - POST: async (request) => { - const body = await parseBody<{ - id?: string; - workspaceId?: string; - sourceKey?: string; - scope?: CredentialScope; - actorId?: string; - secretJson?: Record; - }>(request); - - if (!body || !body.workspaceId || !body.sourceKey || !body.scope || !body.secretJson) { - return error(400, "workspaceId, sourceKey, scope, and secretJson are required"); - } - - if (body.scope !== "workspace" && body.scope !== "actor") { - return error(400, "scope must be 'workspace' or 'actor'"); - } - - if (body.scope === "actor" && (!body.actorId || body.actorId.trim().length === 0)) { - return error(400, "actorId is required for actor-scoped credential"); - } +function isInternalAuthorized(request: Request): boolean { + if (!internalToken) return true; + const header = request.headers.get("authorization"); + if (!header || !header.startsWith("Bearer ")) return false; + return header.slice("Bearer ".length) === internalToken; +} - const credential = await service.upsertCredential({ - id: body.id, - workspaceId: body.workspaceId, - sourceKey: body.sourceKey, - scope: body.scope, - actorId: body.actorId, - secretJson: body.secretJson, - }); +// ── Elysia app ── + +const app = new Elysia() + // CORS + .onRequest(({ set }) => { + set.headers["access-control-allow-origin"] = "*"; + set.headers["access-control-allow-methods"] = "GET,POST,DELETE,OPTIONS"; + set.headers["access-control-allow-headers"] = "content-type,authorization"; + }) + .options("/*", () => new Response(null, { status: 204 })) + + // ── MCP (raw protocol passthrough) ── + .post("/mcp", async ({ request }) => await handleMcpRequest(service, request)) + .get("/mcp", async ({ request }) => await handleMcpRequest(service, request)) + .delete("/mcp", async ({ request }) => await handleMcpRequest(service, request)) + + // ── Health ── + .get("/api/health", () => ({ + ok: true as const, + tools: service.getBaseToolCount(), + })) + + // ── Auth ── + .post("/api/auth/anonymous/bootstrap", async ({ body }) => { + return await service.bootstrapAnonymousContext(body.sessionId); + }, { + body: t.Object({ + sessionId: t.Optional(t.String()), + }), + }) + + // ── Runtime Targets ── + .get("/api/runtime-targets", () => service.listRuntimes()) + + // ── Tools ── + .get("/api/tools", async ({ query, set }) => { + if (!query.workspaceId) { + set.status = 400; + return { error: "workspaceId is required" }; + } + return await service.listTools({ + workspaceId: query.workspaceId, + actorId: query.actorId, + clientId: query.clientId, + }); + }, { + query: t.Object({ + workspaceId: t.String(), + actorId: t.Optional(t.String()), + clientId: t.Optional(t.String()), + }), + }) + + // ── Tool Sources ── + .get("/api/tool-sources", async ({ query, set }) => { + if (!query.workspaceId) { + set.status = 400; + return { error: "workspaceId is required" }; + } + return await service.listToolSources(query.workspaceId); + }, { + query: t.Object({ + workspaceId: t.String(), + }), + }) + + .post("/api/tool-sources", async ({ body, set }) => { + if (!body.workspaceId || !body.name || !body.type || !body.config) { + set.status = 400; + return { error: "workspaceId, name, type, and config are required" }; + } + try { + return await service.upsertToolSource({ + id: body.id, + workspaceId: body.workspaceId, + name: body.name, + type: body.type, + config: body.config, + enabled: body.enabled, + }); + } catch (cause) { + set.status = 400; + return { error: cause instanceof Error ? cause.message : String(cause) }; + } + }, { + body: t.Object({ + id: t.Optional(t.String()), + workspaceId: t.String(), + name: t.String(), + type: t.Union([t.Literal("mcp"), t.Literal("openapi"), t.Literal("graphql")]), + config: t.Record(t.String(), t.Unknown()), + enabled: t.Optional(t.Boolean()), + }), + }) + + .delete("/api/tool-sources/:sourceId", async ({ params, query, set }) => { + if (!query.workspaceId) { + set.status = 400; + return { error: "workspaceId is required" }; + } + const deleted = await service.deleteToolSource(query.workspaceId, params.sourceId); + if (!deleted) { + set.status = 404; + return { error: "Tool source not found" }; + } + return { ok: true as const }; + }, { + params: t.Object({ sourceId: t.String() }), + query: t.Object({ workspaceId: t.String() }), + }) + + // ── Tasks ── + .get("/api/tasks", async ({ query, set }) => { + if (!query.workspaceId) { + set.status = 400; + return { error: "workspaceId is required" }; + } + return await service.listTasks(query.workspaceId); + }, { + query: t.Object({ workspaceId: t.String() }), + }) + + .post("/api/tasks", async ({ body, set }) => { + try { + const created = await service.createTask(body); + return { taskId: created.task.id, status: created.task.status }; + } catch (cause) { + set.status = 400; + return { error: cause instanceof Error ? cause.message : String(cause) }; + } + }, { + body: t.Object({ + code: t.String(), + timeoutMs: t.Optional(t.Number()), + runtimeId: t.Optional(t.String()), + metadata: t.Optional(t.Record(t.String(), t.Unknown())), + workspaceId: t.String(), + actorId: t.String(), + clientId: t.Optional(t.String()), + }), + }) + + .get("/api/tasks/:taskId", async ({ params, query, set }) => { + if (!query.workspaceId) { + set.status = 400; + return { error: "workspaceId is required" }; + } + const task = await service.getTask(params.taskId, query.workspaceId); + if (!task) { + set.status = 404; + return { error: "Task not found" }; + } + return task; + }, { + params: t.Object({ taskId: t.String() }), + query: t.Object({ workspaceId: t.String() }), + }) + + .get("/api/tasks/:taskId/events", async ({ params, query, set }) => { + if (!query.workspaceId) { + set.status = 400; + return { error: "workspaceId is required" }; + } + const task = await service.getTask(params.taskId, query.workspaceId); + if (!task) { + set.status = 404; + return { error: "Task not found" }; + } + return createTaskEventsResponse(params.taskId); + }, { + params: t.Object({ taskId: t.String() }), + query: t.Object({ workspaceId: t.String() }), + }) + + // ── Approvals ── + .get("/api/approvals", async ({ query, set }) => { + if (!query.workspaceId) { + set.status = 400; + return { error: "workspaceId is required" }; + } + const status = query.status as ApprovalStatus | undefined; - return json({ - id: credential.id, - workspaceId: credential.workspaceId, - sourceKey: credential.sourceKey, - scope: credential.scope, - actorId: credential.actorId, - hasSecret: true, - }, body.id ? 200 : 201); - }, - }, - "/internal/runs/:runId/tool-call": { - POST: async (request) => { - if (!isInternalAuthorized(request)) { - return error(401, "Unauthorized internal call"); - } + if (status === "pending") { + return await service.listPendingApprovals(query.workspaceId); + } - const body = await parseBody<{ - callId?: string; - toolPath?: string; - input?: unknown; - }>(request); + if (status && status !== "approved" && status !== "denied") { + set.status = 400; + return { error: "Invalid approval status" }; + } - if (!body || !body.callId || !body.toolPath) { - return error(400, "callId and toolPath are required"); - } + return await service.listApprovals(query.workspaceId, status ?? undefined); + }, { + query: t.Object({ + workspaceId: t.String(), + status: t.Optional(t.String()), + }), + }) + + .post("/api/approvals/:approvalId", async ({ params, body, set }) => { + if (!body.workspaceId || (body.decision !== "approved" && body.decision !== "denied")) { + set.status = 400; + return { error: "workspaceId and decision are required" }; + } - const call: ToolCallRequest = { - runId: request.params.runId, - callId: body.callId, - toolPath: body.toolPath, - input: body.input, - }; + const resolved = await service.resolveApproval( + body.workspaceId, + params.approvalId, + body.decision, + body.reviewerId, + body.reason, + ); + + if (!resolved) { + set.status = 404; + return { error: "Approval not found or already resolved" }; + } + return resolved; + }, { + params: t.Object({ approvalId: t.String() }), + body: t.Object({ + workspaceId: t.String(), + decision: t.Union([t.Literal("approved"), t.Literal("denied")]), + reviewerId: t.Optional(t.String()), + reason: t.Optional(t.String()), + }), + }) + + // ── Policies ── + .get("/api/policies", async ({ query, set }) => { + if (!query.workspaceId) { + set.status = 400; + return { error: "workspaceId is required" }; + } + return await service.listAccessPolicies(query.workspaceId); + }, { + query: t.Object({ workspaceId: t.String() }), + }) + + .post("/api/policies", async ({ body, set }) => { + if (!body.workspaceId || !body.toolPathPattern || !body.decision) { + set.status = 400; + return { error: "workspaceId, toolPathPattern, and decision are required" }; + } + return await service.upsertAccessPolicy({ + id: body.id, + workspaceId: body.workspaceId, + actorId: body.actorId, + clientId: body.clientId, + toolPathPattern: body.toolPathPattern, + decision: body.decision, + priority: body.priority, + }); + }, { + body: t.Object({ + id: t.Optional(t.String()), + workspaceId: t.String(), + actorId: t.Optional(t.String()), + clientId: t.Optional(t.String()), + toolPathPattern: t.String(), + decision: t.Union([t.Literal("allow"), t.Literal("require_approval"), t.Literal("deny")]), + priority: t.Optional(t.Number()), + }), + }) + + // ── Credentials ── + .get("/api/credentials", async ({ query, set }) => { + if (!query.workspaceId) { + set.status = 400; + return { error: "workspaceId is required" }; + } + return await service.listCredentials(query.workspaceId); + }, { + query: t.Object({ workspaceId: t.String() }), + }) + + .post("/api/credentials", async ({ body, set }) => { + if (!body.workspaceId || !body.sourceKey || !body.scope || !body.secretJson) { + set.status = 400; + return { error: "workspaceId, sourceKey, scope, and secretJson are required" }; + } + if (body.scope === "actor" && (!body.actorId || body.actorId.trim().length === 0)) { + set.status = 400; + return { error: "actorId is required for actor-scoped credential" }; + } - return json(await service.handleExternalToolCall(call)); - }, - }, - "/internal/runs/:runId/output": { - POST: async (request) => { - if (!isInternalAuthorized(request)) { - return error(401, "Unauthorized internal call"); - } + const credential = await service.upsertCredential({ + id: body.id, + workspaceId: body.workspaceId, + sourceKey: body.sourceKey, + scope: body.scope, + actorId: body.actorId, + secretJson: body.secretJson, + }); + + return { + id: credential.id, + workspaceId: credential.workspaceId, + sourceKey: credential.sourceKey, + scope: credential.scope, + actorId: credential.actorId, + hasSecret: true as const, + }; + }, { + body: t.Object({ + id: t.Optional(t.String()), + workspaceId: t.String(), + sourceKey: t.String(), + scope: t.Union([t.Literal("workspace"), t.Literal("actor")]), + actorId: t.Optional(t.String()), + secretJson: t.Record(t.String(), t.Unknown()), + }), + }) + + // ── Internal endpoints (bearer auth protected) ── + .post("/internal/runs/:runId/tool-call", async ({ params, body, request, set }) => { + if (!isInternalAuthorized(request)) { + set.status = 401; + return { error: "Unauthorized internal call" }; + } + if (!body.callId || !body.toolPath) { + set.status = 400; + return { error: "callId and toolPath are required" }; + } - const body = await parseBody<{ - stream?: "stdout" | "stderr"; - line?: string; - timestamp?: number; - }>(request); + const call: ToolCallRequest = { + runId: params.runId, + callId: body.callId, + toolPath: body.toolPath, + input: body.input, + }; + + return await service.handleExternalToolCall(call); + }, { + params: t.Object({ runId: t.String() }), + body: t.Object({ + callId: t.String(), + toolPath: t.String(), + input: t.Optional(t.Unknown()), + }), + }) + + .post("/internal/runs/:runId/output", async ({ params, body, request, set }) => { + if (!isInternalAuthorized(request)) { + set.status = 401; + return { error: "Unauthorized internal call" }; + } - if (!body || (body.stream !== "stdout" && body.stream !== "stderr") || typeof body.line !== "string") { - return error(400, "stream and line are required"); - } + const event: RuntimeOutputEvent = { + runId: params.runId, + stream: body.stream, + line: body.line, + timestamp: typeof body.timestamp === "number" ? body.timestamp : Date.now(), + }; + + const task = await service.getTask(event.runId); + if (!task) { + set.status = 404; + return { error: `Run not found: ${event.runId}` }; + } - const event: RuntimeOutputEvent = { - runId: request.params.runId, - stream: body.stream, - line: body.line, - timestamp: typeof body.timestamp === "number" ? body.timestamp : Date.now(), - }; + await service.appendRuntimeOutput(event); + return { ok: true as const }; + }, { + params: t.Object({ runId: t.String() }), + body: t.Object({ + stream: t.Union([t.Literal("stdout"), t.Literal("stderr")]), + line: t.String(), + timestamp: t.Optional(t.Number()), + }), + }) - const task = await service.getTask(event.runId); - if (!task) { - return error(404, `Run not found: ${event.runId}`); - } + .listen(port); - await service.appendRuntimeOutput(event); - return json({ ok: true }); - }, - }, - }, - fetch(request) { - if (request.method === "OPTIONS") { - return new Response(null, { status: 204, headers: jsonHeaders }); - } - return new Response("Not found", { status: 404 }); - }, - error(cause) { - console.error("executor server error", cause); - return error(500, "Internal server error"); - }, - development: { - hmr: true, - console: true, - }, -}); +export type App = typeof app; -console.log(`executor server listening on http://localhost:${server.port}`); +console.log(`executor server listening on http://localhost:${app.server!.port}`); console.log(`[executor] internal callback base: ${internalBaseUrl} (${internalBaseSource})`); console.log(`[executor] internal callback auth token enabled: yes`); console.log(`[executor] tools loaded: ${tools.length} (${externalTools.length} external)`); diff --git a/executor/apps/web/src/lib/api.ts b/executor/apps/web/src/lib/api.ts deleted file mode 100644 index de625c8f9..000000000 --- a/executor/apps/web/src/lib/api.ts +++ /dev/null @@ -1,221 +0,0 @@ -// Thin API client that calls through the Next.js rewrite proxy (/api/* -> backend) - -import type { - AnonymousContext, - TaskRecord, - CreateTaskRequest, - CreateTaskResponse, - ApprovalRecord, - PendingApprovalRecord, - ResolveApprovalRequest, - RuntimeTargetDescriptor, - ToolDescriptor, - ToolSourceRecord, - AccessPolicyRecord, - CredentialDescriptor, -} from "./types"; - -const BASE = ""; - -async function json(res: Response): Promise { - if (!res.ok) { - const text = await res.text(); - throw new Error(`${res.status} ${res.statusText}: ${text}`); - } - return res.json() as Promise; -} - -// ── Auth ── - -export async function bootstrapAnonymousContext( - sessionId?: string, -): Promise { - const res = await fetch(`${BASE}/api/auth/anonymous/bootstrap`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ sessionId }), - }); - return json(res); -} - -// ── Tasks ── - -export async function createTask( - request: CreateTaskRequest, -): Promise { - const res = await fetch(`${BASE}/api/tasks`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(request), - }); - return json(res); -} - -export async function listTasks(workspaceId: string): Promise { - const res = await fetch( - `${BASE}/api/tasks?workspaceId=${encodeURIComponent(workspaceId)}`, - ); - return json(res); -} - -export async function getTask( - taskId: string, - workspaceId: string, -): Promise { - const res = await fetch( - `${BASE}/api/tasks/${encodeURIComponent(taskId)}?workspaceId=${encodeURIComponent(workspaceId)}`, - ); - return json(res); -} - -// ── Approvals ── - -export async function listPendingApprovals( - workspaceId: string, -): Promise { - const res = await fetch( - `${BASE}/api/approvals?workspaceId=${encodeURIComponent(workspaceId)}&status=pending`, - ); - return json(res); -} - -export async function listApprovals( - workspaceId: string, - status?: string, -): Promise { - const params = new URLSearchParams({ workspaceId }); - if (status) params.set("status", status); - const res = await fetch(`${BASE}/api/approvals?${params.toString()}`); - return json(res); -} - -export async function resolveApproval( - approvalId: string, - request: ResolveApprovalRequest, -): Promise<{ approval: ApprovalRecord; task: TaskRecord }> { - const res = await fetch( - `${BASE}/api/approvals/${encodeURIComponent(approvalId)}`, - { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(request), - }, - ); - return json<{ approval: ApprovalRecord; task: TaskRecord }>(res); -} - -// ── Runtimes ── - -export async function listRuntimeTargets(): Promise< - RuntimeTargetDescriptor[] -> { - const res = await fetch(`${BASE}/api/runtime-targets`); - return json(res); -} - -// ── Tools ── - -export async function listToolsForContext(context: { - workspaceId: string; - actorId?: string; - clientId?: string; -}): Promise { - const params = new URLSearchParams({ workspaceId: context.workspaceId }); - if (context.actorId) params.set("actorId", context.actorId); - if (context.clientId) params.set("clientId", context.clientId); - const res = await fetch(`${BASE}/api/tools?${params.toString()}`); - return json(res); -} - -// ── Tool Sources ── - -export async function listToolSources( - workspaceId: string, -): Promise { - const res = await fetch( - `${BASE}/api/tool-sources?workspaceId=${encodeURIComponent(workspaceId)}`, - ); - return json(res); -} - -export async function upsertToolSource(request: { - id?: string; - workspaceId: string; - name: string; - type: "mcp" | "openapi" | "graphql"; - config: Record; - enabled?: boolean; -}): Promise { - const res = await fetch(`${BASE}/api/tool-sources`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(request), - }); - return json(res); -} - -export async function deleteToolSource( - workspaceId: string, - sourceId: string, -): Promise<{ ok: boolean }> { - const res = await fetch( - `${BASE}/api/tool-sources/${encodeURIComponent(sourceId)}?workspaceId=${encodeURIComponent(workspaceId)}`, - { method: "DELETE" }, - ); - return json<{ ok: boolean }>(res); -} - -// ── Policies ── - -export async function listPolicies( - workspaceId: string, -): Promise { - const res = await fetch( - `${BASE}/api/policies?workspaceId=${encodeURIComponent(workspaceId)}`, - ); - return json(res); -} - -export async function upsertPolicy(request: { - id?: string; - workspaceId: string; - actorId?: string; - clientId?: string; - toolPathPattern: string; - decision: string; - priority?: number; -}): Promise { - const res = await fetch(`${BASE}/api/policies`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(request), - }); - return json(res); -} - -// ── Credentials ── - -export async function listCredentials( - workspaceId: string, -): Promise { - const res = await fetch( - `${BASE}/api/credentials?workspaceId=${encodeURIComponent(workspaceId)}`, - ); - return json(res); -} - -export async function upsertCredential(request: { - id?: string; - workspaceId: string; - sourceKey: string; - scope: string; - actorId?: string; - secretJson: Record; -}): Promise { - const res = await fetch(`${BASE}/api/credentials`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(request), - }); - return json(res); -} diff --git a/executor/bun.lock b/executor/bun.lock index ae72e971e..7b43c61e9 100644 --- a/executor/bun.lock +++ b/executor/bun.lock @@ -17,9 +17,11 @@ "name": "@executor/server", "dependencies": { "@apidevtools/swagger-parser": "^12.1.0", + "@elysiajs/eden": "^1.4.6", "@modelcontextprotocol/sdk": "^1.26.0", "@vercel/sandbox": "latest", "convex": "latest", + "elysia": "^1.4.22", "zod": "^4.3.6", }, "devDependencies": { @@ -59,15 +61,6 @@ "typescript": "^5", }, }, - "packages/client": { - "name": "@executor/client", - "dependencies": { - "@executor/contracts": "workspace:*", - }, - }, - "packages/contracts": { - "name": "@executor/contracts", - }, }, "trustedDependencies": [ "sharp", @@ -142,10 +135,14 @@ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], + "@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.52.0", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.2", "which": "^4.0.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-CaQcc8JvtzQhUSm9877b6V4Tb7HCotkcyud9X2YwdqtQKwgljkMRwU96fVYKnzN3V0Hj74oP7Es+vZ0mS+Aa1w=="], "@ecies/ciphers": ["@ecies/ciphers@0.2.5", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A=="], + "@elysiajs/eden": ["@elysiajs/eden@1.4.6", "", { "peerDependencies": { "elysia": ">=1.4.19" } }, "sha512-Tsa4NwXEWg/u73vWiYZQ3L5/ecgZSxqiEjYwpS+4qBKXeTZqZKl2hcgHJSVBL+InEDMi35Xugct7qyAXE5oM4Q=="], + "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], @@ -222,10 +219,6 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], - "@executor/client": ["@executor/client@workspace:packages/client"], - - "@executor/contracts": ["@executor/contracts@workspace:packages/contracts"], - "@executor/server": ["@executor/server@workspace:apps/server"], "@executor/web": ["@executor/web@workspace:apps/web"], @@ -496,6 +489,8 @@ "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + "@sinclair/typebox": ["@sinclair/typebox@0.34.48", "", {}, "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA=="], + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], @@ -530,6 +525,10 @@ "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "postcss": "^8.4.41", "tailwindcss": "4.1.18" } }, "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g=="], + "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], + + "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + "@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="], "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], @@ -742,7 +741,7 @@ "convex-test": ["convex-test@0.0.41", "", { "peerDependencies": { "convex": "^1.16.4" } }, "sha512-GPHeYFOi70n7UtW0eCEQFVhzl/+m8PvbWkDCbKpHLybI1MrScf4sVpGeM0cC2qmtxiduxa2nLPbehPalhh9oyQ=="], - "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], @@ -806,6 +805,8 @@ "electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="], + "elysia": ["elysia@1.4.22", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.6", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Q90VCb1RVFxnFaRV0FDoSylESQQLWgLHFmWciQJdX9h3b2cSasji9KWEUvaJuy/L9ciAGg4RAhUVfsXHg5K2RQ=="], + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], @@ -882,12 +883,16 @@ "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], + "exact-mirror": ["exact-mirror@0.2.6", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-7s059UIx9/tnOKSySzUk5cPGkoILhTE4p6ncf6uIPaQ+9aRBQzQjc9+q85l51+oZ+P6aBxh084pD0CzBQPcFUA=="], + "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="], "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], "express-rate-limit": ["express-rate-limit@8.2.1", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g=="], + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], @@ -910,6 +915,8 @@ "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + "file-type": ["file-type@21.3.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], @@ -1004,6 +1011,8 @@ "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -1188,6 +1197,8 @@ "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + "memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="], + "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], @@ -1466,6 +1477,8 @@ "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], + "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -1498,6 +1511,8 @@ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="], + "tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="], "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], @@ -1528,6 +1543,8 @@ "typescript-eslint": ["typescript-eslint@8.54.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/parser": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/utils": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ=="], + "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], + "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], "undici": ["undici@7.21.0", "", {}, "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg=="], @@ -1660,6 +1677,8 @@ "eslint-plugin-react/resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="], + "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "is-bun-module/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], @@ -1670,8 +1689,6 @@ "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "msw/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], - "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], diff --git a/executor/packages/client/package.json b/executor/packages/client/package.json deleted file mode 100644 index f98d11f2e..000000000 --- a/executor/packages/client/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@executor/client", - "private": true, - "type": "module", - "dependencies": { - "@executor/contracts": "workspace:*" - }, - "exports": { - ".": "./src/index.ts" - } -} diff --git a/executor/packages/client/src/index.ts b/executor/packages/client/src/index.ts deleted file mode 100644 index d67486f3e..000000000 --- a/executor/packages/client/src/index.ts +++ /dev/null @@ -1,258 +0,0 @@ -import type { - AccessPolicyRecord, - ApprovalRecord, - AnonymousContext, - CreateTaskRequest, - CreateTaskResponse, - CredentialDescriptor, - CredentialScope, - PendingApprovalRecord, - PolicyDecision, - ResolveApprovalRequest, - RuntimeTargetDescriptor, - TaskEventRecord, - TaskRecord, - ToolSourceRecord, - ToolDescriptor, -} from "@executor/contracts"; - -function withBase(baseUrl: string, path: string): string { - return `${baseUrl.replace(/\/$/, "")}${path}`; -} - -async function parseJson(response: Response): Promise { - if (!response.ok) { - const text = await response.text(); - throw new Error(`Request failed: ${response.status} ${response.statusText} - ${text}`); - } - return (await response.json()) as T; -} - -export async function createTask( - baseUrl: string, - request: CreateTaskRequest, -): Promise { - const response = await fetch(withBase(baseUrl, "/api/tasks"), { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(request), - }); - return parseJson(response); -} - -export async function listTasks(baseUrl: string): Promise { - throw new Error("listTasks now requires workspaceId. Use listTasksForWorkspace."); -} - -export async function listTasksForWorkspace(baseUrl: string, workspaceId: string): Promise { - const response = await fetch( - withBase(baseUrl, `/api/tasks?workspaceId=${encodeURIComponent(workspaceId)}`), - ); - return parseJson(response); -} - -export async function getTask(baseUrl: string, taskId: string, workspaceId: string): Promise { - const response = await fetch( - withBase( - baseUrl, - `/api/tasks/${encodeURIComponent(taskId)}?workspaceId=${encodeURIComponent(workspaceId)}`, - ), - ); - return parseJson(response); -} - -export async function listApprovals( - baseUrl: string, - workspaceId: string, - status?: ApprovalRecord["status"], -): Promise { - const search = status - ? `?workspaceId=${encodeURIComponent(workspaceId)}&status=${encodeURIComponent(status)}` - : `?workspaceId=${encodeURIComponent(workspaceId)}`; - const response = await fetch(withBase(baseUrl, `/api/approvals${search}`)); - return parseJson(response); -} - -export async function listPendingApprovals( - baseUrl: string, - workspaceId: string, -): Promise { - const response = await fetch( - withBase(baseUrl, `/api/approvals?workspaceId=${encodeURIComponent(workspaceId)}&status=pending`), - ); - return parseJson(response); -} - -export async function resolveApproval( - baseUrl: string, - approvalId: string, - request: ResolveApprovalRequest, -): Promise<{ approval: ApprovalRecord; task: TaskRecord }> { - const response = await fetch(withBase(baseUrl, `/api/approvals/${encodeURIComponent(approvalId)}`), { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(request), - }); - return parseJson<{ approval: ApprovalRecord; task: TaskRecord }>(response); -} - -export async function listRuntimeTargets(baseUrl: string): Promise { - const response = await fetch(withBase(baseUrl, "/api/runtime-targets")); - return parseJson(response); -} - -export async function listTools(baseUrl: string): Promise { - throw new Error("listTools now requires workspace context. Use listToolsForContext."); -} - -export async function listToolsForContext( - baseUrl: string, - context: { workspaceId: string; actorId?: string; clientId?: string }, -): Promise { - const params = new URLSearchParams({ workspaceId: context.workspaceId }); - if (context.actorId) params.set("actorId", context.actorId); - if (context.clientId) params.set("clientId", context.clientId); - const response = await fetch(withBase(baseUrl, `/api/tools?${params.toString()}`)); - return parseJson(response); -} - -export async function listToolSources( - baseUrl: string, - workspaceId: string, -): Promise { - const response = await fetch( - withBase(baseUrl, `/api/tool-sources?workspaceId=${encodeURIComponent(workspaceId)}`), - ); - return parseJson(response); -} - -export async function upsertToolSource( - baseUrl: string, - request: { - id?: string; - workspaceId: string; - name: string; - type: "mcp" | "openapi" | "graphql"; - config: Record; - enabled?: boolean; - }, -): Promise { - const response = await fetch(withBase(baseUrl, "/api/tool-sources"), { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(request), - }); - return parseJson(response); -} - -export async function deleteToolSource( - baseUrl: string, - workspaceId: string, - sourceId: string, -): Promise<{ ok: boolean }> { - const response = await fetch( - withBase( - baseUrl, - `/api/tool-sources/${encodeURIComponent(sourceId)}?workspaceId=${encodeURIComponent(workspaceId)}`, - ), - { method: "DELETE" }, - ); - return parseJson<{ ok: boolean }>(response); -} - -export async function bootstrapAnonymousContext( - baseUrl: string, - sessionId?: string, -): Promise { - const response = await fetch(withBase(baseUrl, "/api/auth/anonymous/bootstrap"), { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ sessionId }), - }); - return parseJson(response); -} - -export async function listPolicies( - baseUrl: string, - workspaceId: string, -): Promise { - const response = await fetch( - withBase(baseUrl, `/api/policies?workspaceId=${encodeURIComponent(workspaceId)}`), - ); - return parseJson(response); -} - -export async function upsertPolicy( - baseUrl: string, - request: { - id?: string; - workspaceId: string; - actorId?: string; - clientId?: string; - toolPathPattern: string; - decision: PolicyDecision; - priority?: number; - }, -): Promise { - const response = await fetch(withBase(baseUrl, "/api/policies"), { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(request), - }); - return parseJson(response); -} - -export async function listCredentials( - baseUrl: string, - workspaceId: string, -): Promise { - const response = await fetch( - withBase(baseUrl, `/api/credentials?workspaceId=${encodeURIComponent(workspaceId)}`), - ); - return parseJson(response); -} - -export async function upsertCredential( - baseUrl: string, - request: { - id?: string; - workspaceId: string; - sourceKey: string; - scope: CredentialScope; - actorId?: string; - secretJson: Record; - }, -): Promise { - const response = await fetch(withBase(baseUrl, "/api/credentials"), { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(request), - }); - return parseJson(response); -} - -export function subscribeToTaskEvents( - baseUrl: string, - taskId: string, - workspaceId: string, - onEvent: (eventName: TaskEventRecord["eventName"], event: TaskEventRecord) => void, -): EventSource { - const source = new EventSource( - withBase( - baseUrl, - `/api/tasks/${encodeURIComponent(taskId)}/events?workspaceId=${encodeURIComponent(workspaceId)}`, - ), - ); - - source.addEventListener("task", (event) => { - const message = event as MessageEvent; - onEvent("task", JSON.parse(message.data) as TaskEventRecord); - }); - - source.addEventListener("approval", (event) => { - const message = event as MessageEvent; - onEvent("approval", JSON.parse(message.data) as TaskEventRecord); - }); - - return source; -} diff --git a/executor/packages/contracts/package.json b/executor/packages/contracts/package.json deleted file mode 100644 index f8419a3d9..000000000 --- a/executor/packages/contracts/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@executor/contracts", - "private": true, - "type": "module", - "exports": { - ".": "./src/index.ts" - } -} diff --git a/executor/packages/contracts/src/index.ts b/executor/packages/contracts/src/index.ts deleted file mode 100644 index 47a3bcfe9..000000000 --- a/executor/packages/contracts/src/index.ts +++ /dev/null @@ -1,154 +0,0 @@ -export type TaskStatus = - | "queued" - | "running" - | "completed" - | "failed" - | "timed_out" - | "denied"; - -export type ApprovalStatus = "pending" | "approved" | "denied"; -export type ApprovalDecision = "approved" | "denied"; - -export interface CreateTaskRequest { - code: string; - runtimeId?: string; - timeoutMs?: number; - metadata?: Record; - workspaceId: string; - actorId: string; - clientId?: string; -} - -export interface CreateTaskResponse { - taskId: string; - status: TaskStatus; -} - -export interface TaskRecord { - id: string; - code: string; - runtimeId: string; - timeoutMs: number; - metadata: Record; - workspaceId: string; - actorId?: string; - clientId?: string; - status: TaskStatus; - createdAt: number; - updatedAt: number; - startedAt?: number; - completedAt?: number; - stdout?: string; - stderr?: string; - exitCode?: number; - error?: string; -} - -export interface ApprovalRecord { - id: string; - taskId: string; - toolPath: string; - input: unknown; - status: ApprovalStatus; - createdAt: number; - resolvedAt?: number; - reviewerId?: string; - reason?: string; -} - -export interface PendingApprovalRecord extends ApprovalRecord { - task: Pick; -} - -export interface ResolveApprovalRequest { - workspaceId: string; - decision: ApprovalDecision; - reviewerId?: string; - reason?: string; -} - -export interface AnonymousContext { - sessionId: string; - workspaceId: string; - actorId: string; - clientId: string; - createdAt: number; - lastSeenAt: number; -} - -export interface RuntimeTargetDescriptor { - id: string; - label: string; - description: string; -} - -export interface ToolDescriptor { - path: string; - description: string; - approval: "auto" | "required"; - source?: string; - argsType?: string; - returnsType?: string; -} - -export type PolicyDecision = "allow" | "require_approval" | "deny"; - -export interface AccessPolicyRecord { - id: string; - workspaceId: string; - actorId?: string; - clientId?: string; - toolPathPattern: string; - decision: PolicyDecision; - priority: number; - createdAt: number; - updatedAt: number; -} - -export type CredentialScope = "workspace" | "actor"; - -export interface CredentialDescriptor { - id: string; - workspaceId: string; - sourceKey: string; - scope: CredentialScope; - actorId?: string; - hasSecret: boolean; -} - -export type ToolSourceType = "mcp" | "openapi" | "graphql"; - -export interface ToolSourceRecord { - id: string; - workspaceId: string; - name: string; - type: ToolSourceType; - config: Record; - enabled: boolean; - createdAt: number; - updatedAt: number; -} - -export interface TaskEventRecord { - id: number; - taskId: string; - eventName: "task" | "approval"; - type: string; - payload: Record; - createdAt: number; -} - -export interface SandboxExecutionRequest { - taskId: string; - code: string; - timeoutMs: number; -} - -export interface SandboxExecutionResult { - status: "completed" | "failed" | "timed_out" | "denied"; - stdout: string; - stderr: string; - exitCode?: number; - error?: string; - durationMs: number; -} diff --git a/executor/tsconfig.json b/executor/tsconfig.json index 25e01f8e9..232201d56 100644 --- a/executor/tsconfig.json +++ b/executor/tsconfig.json @@ -13,15 +13,7 @@ "ESNext", "DOM" ], - "baseUrl": ".", - "paths": { - "@executor/contracts": [ - "packages/contracts/src/index.ts" - ], - "@executor/client": [ - "packages/client/src/index.ts" - ] - } + "baseUrl": "." }, "include": [ "apps/**/*.ts", From 7666cf777fdd8c80bc7946786fb02da49635c8a0 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:13:28 -0800 Subject: [PATCH 10/34] Add assistant agent, server, Discord bot, and dev orchestrator Introduces the assistant side of the system: - @assistant/core: agent loop (Claude via pi-ai) with run_code tool calling - @assistant/server: Elysia API with SSE task streaming and Eden Treaty client - @assistant/bot: Discord bot (Reacord) with /ask command, live approval buttons via Convex reactivity - Executor sync endpoint (POST /api/tasks/run) for blocking code execution - dev.ts multi-service orchestrator (bun dev starts everything) - Root workspace config for cross-package resolution --- .env.example | 25 + assistant/bun.lock | 633 +++++ assistant/package.json | 7 +- assistant/packages/bot/package.json | 23 + assistant/packages/bot/src/commands/ask.tsx | 59 + assistant/packages/bot/src/index.ts | 112 + .../packages/bot/src/views/task-message.tsx | 227 ++ assistant/packages/bot/tsconfig.json | 15 + assistant/packages/core/package.json | 17 + assistant/packages/core/src/agent.test.ts | 115 + assistant/packages/core/src/agent.ts | 203 ++ assistant/packages/core/src/events.ts | 46 + assistant/packages/core/src/index.ts | 5 + assistant/packages/core/src/model.ts | 200 ++ assistant/packages/server/package.json | 22 + assistant/packages/server/src/client.ts | 42 + assistant/packages/server/src/index.ts | 83 + assistant/packages/server/src/routes.ts | 184 ++ assistant/packages/server/src/state.ts | 96 + bun.lock | 2246 +++++++++++++++++ dev.ts | 145 ++ executor/apps/server/src/index.ts | 47 + package.json | 11 + 23 files changed, 4561 insertions(+), 2 deletions(-) create mode 100644 .env.example create mode 100644 assistant/packages/bot/package.json create mode 100644 assistant/packages/bot/src/commands/ask.tsx create mode 100644 assistant/packages/bot/src/index.ts create mode 100644 assistant/packages/bot/src/views/task-message.tsx create mode 100644 assistant/packages/bot/tsconfig.json create mode 100644 assistant/packages/core/package.json create mode 100644 assistant/packages/core/src/agent.test.ts create mode 100644 assistant/packages/core/src/agent.ts create mode 100644 assistant/packages/core/src/events.ts create mode 100644 assistant/packages/core/src/index.ts create mode 100644 assistant/packages/core/src/model.ts create mode 100644 assistant/packages/server/package.json create mode 100644 assistant/packages/server/src/client.ts create mode 100644 assistant/packages/server/src/index.ts create mode 100644 assistant/packages/server/src/routes.ts create mode 100644 assistant/packages/server/src/state.ts create mode 100644 bun.lock create mode 100644 dev.ts diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..15adc7848 --- /dev/null +++ b/.env.example @@ -0,0 +1,25 @@ +# Required for the assistant to call Claude +ANTHROPIC_API_KEY=sk-ant-... +# Or use Claude Max OAuth token instead: +# ANTHROPIC_OAUTH_TOKEN=... + +# Required for the Discord bot +DISCORD_BOT_TOKEN=... + +# Optional: executor URL (defaults to http://localhost:4001) +# EXECUTOR_URL=http://localhost:4001 + +# Optional: assistant server URL for the bot (defaults to http://localhost:3000) +# ASSISTANT_SERVER_URL=http://localhost:3000 + +# Optional: assistant server port (defaults to 3000) +# PORT=3000 + +# Optional: executor server port (defaults to 4001) +# Configured in executor/.env or as env var + +# Optional: tool source API keys +# POSTHOG_PERSONAL_API_KEY=... +# POSTHOG_PROJECT_ID=... +# OPENASSISTANT_GITHUB_TOKEN=... +# VERCEL_TOKEN=... diff --git a/assistant/bun.lock b/assistant/bun.lock index 6bdc003a1..8dcc0bca9 100644 --- a/assistant/bun.lock +++ b/assistant/bun.lock @@ -9,6 +9,21 @@ "typescript": "latest", }, }, + "legacy/openassistant/packages/reacord": { + "name": "@openassistant/reacord", + "dependencies": { + "@effect-atom/atom": "^0.4.11", + "@effect-atom/atom-react": "^0.4.4", + "discord.js": "^14.17.3", + "effect": "latest", + "react": "^19.0.0", + "react-reconciler": "^0.31.0", + }, + "devDependencies": { + "@types/react": "^19.0.0", + "@types/react-reconciler": "^0.31.0", + }, + }, "packages/agent-executor-adapter": { "name": "@assistant/agent-executor-adapter", "dependencies": { @@ -19,54 +34,672 @@ "@types/bun": "latest", }, }, + "packages/bot": { + "name": "@assistant/bot", + "dependencies": { + "@assistant/agent-executor-adapter": "workspace:*", + "@assistant/core": "workspace:*", + "@assistant/server": "workspace:*", + "@elysiajs/eden": "latest", + "@openassistant/reacord": "workspace:*", + "convex": "^1.31.7", + "discord.js": "^14.17.3", + "effect": "latest", + "react": "^19.0.0", + }, + "devDependencies": { + "@types/bun": "latest", + "@types/react": "^19.0.0", + }, + }, + "packages/core": { + "name": "@assistant/core", + "dependencies": { + "@assistant/agent-executor-adapter": "workspace:*", + "@mariozechner/pi-ai": "^0.52.6", + "@sinclair/typebox": "^0.34.48", + }, + "devDependencies": { + "@elysiajs/eden": "^1.4.6", + "elysia": "^1.4.22", + }, + }, + "packages/server": { + "name": "@assistant/server", + "dependencies": { + "@assistant/agent-executor-adapter": "workspace:*", + "@assistant/core": "workspace:*", + "@elysiajs/eden": "latest", + "elysia": "latest", + }, + "devDependencies": { + "@types/bun": "latest", + }, + }, }, "packages": { + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.73.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw=="], + "@assistant/agent-executor-adapter": ["@assistant/agent-executor-adapter@workspace:packages/agent-executor-adapter"], + "@assistant/bot": ["@assistant/bot@workspace:packages/bot"], + + "@assistant/core": ["@assistant/core@workspace:packages/core"], + + "@assistant/server": ["@assistant/server@workspace:packages/server"], + + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + + "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.985.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.7", "@aws-sdk/credential-provider-node": "^3.972.6", "@aws-sdk/eventstream-handler-node": "^3.972.5", "@aws-sdk/middleware-eventstream": "^3.972.3", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.7", "@aws-sdk/middleware-websocket": "^3.972.5", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/token-providers": "3.985.0", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.985.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.5", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.1", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.13", "@smithy/middleware-retry": "^4.4.30", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.9", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.29", "@smithy/util-defaults-mode-node": "^4.2.32", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-stream": "^4.5.11", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jkQ+G+b/6Z6gUsn8jNSjJsFVgxnA4HtyOjrpHfmp8nHWLRFTOIw3HfY2vAlDgg/uUJ7cezVG0/tmbwujFqX25A=="], + + "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.985.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.7", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.7", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.985.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.5", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.13", "@smithy/middleware-retry": "^4.4.30", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.9", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.29", "@smithy/util-defaults-mode-node": "^4.2.32", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-81J8iE8MuXhdbMfIz4sWFj64Pe41bFi/uqqmqOC5SlGv+kwoyLsyKS/rH2tW2t5buih4vTUxskRjxlqikTD4oQ=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.973.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.4", "@smithy/core": "^3.22.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wNZZQQNlJ+hzD49cKdo+PY6rsTDElO8yDImnrI69p2PLBa7QomeUKAJWYp9xnaR38nlHqWhMHZuYLCQ3oSX+xg=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.5", "", { "dependencies": { "@aws-sdk/core": "^3.973.7", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-LxJ9PEO4gKPXzkufvIESUysykPIdrV7+Ocb9yAhbhJLE4TiAYqbCVUE+VuKP1leGR1bBfjWjYgSV5MxprlX3mQ=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.7", "", { "dependencies": { "@aws-sdk/core": "^3.973.7", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.9", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.11", "tslib": "^2.6.2" } }, "sha512-L2uOGtvp2x3bTcxFTpSM+GkwFIPd8pHfGWO1764icMbo7e5xJh0nfhx1UwkXLnwvocTNEf8A7jISZLYjUSNaTg=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.5", "", { "dependencies": { "@aws-sdk/core": "^3.973.7", "@aws-sdk/credential-provider-env": "^3.972.5", "@aws-sdk/credential-provider-http": "^3.972.7", "@aws-sdk/credential-provider-login": "^3.972.5", "@aws-sdk/credential-provider-process": "^3.972.5", "@aws-sdk/credential-provider-sso": "^3.972.5", "@aws-sdk/credential-provider-web-identity": "^3.972.5", "@aws-sdk/nested-clients": "3.985.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-SdDTYE6jkARzOeL7+kudMIM4DaFnP5dZVeatzw849k4bSXDdErDS188bgeNzc/RA2WGrlEpsqHUKP6G7sVXhZg=="], + + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.5", "", { "dependencies": { "@aws-sdk/core": "^3.973.7", "@aws-sdk/nested-clients": "3.985.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-uYq1ILyTSI6ZDCMY5+vUsRM0SOCVI7kaW4wBrehVVkhAxC6y+e9rvGtnoZqCOWL1gKjTMouvsf4Ilhc5NCg1Aw=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.6", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.5", "@aws-sdk/credential-provider-http": "^3.972.7", "@aws-sdk/credential-provider-ini": "^3.972.5", "@aws-sdk/credential-provider-process": "^3.972.5", "@aws-sdk/credential-provider-sso": "^3.972.5", "@aws-sdk/credential-provider-web-identity": "^3.972.5", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DZ3CnAAtSVtVz+G+ogqecaErMLgzph4JH5nYbHoBMgBkwTUV+SUcjsjOJwdBJTHu3Dm6l5LBYekZoU2nDqQk2A=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.5", "", { "dependencies": { "@aws-sdk/core": "^3.973.7", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-HDKF3mVbLnuqGg6dMnzBf1VUOywE12/N286msI9YaK9mEIzdsGCtLTvrDhe3Up0R9/hGFbB+9l21/TwF5L1C6g=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.5", "", { "dependencies": { "@aws-sdk/client-sso": "3.985.0", "@aws-sdk/core": "^3.973.7", "@aws-sdk/token-providers": "3.985.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8urj3AoeNeQisjMmMBhFeiY2gxt6/7wQQbEGun0YV/OaOOiXrIudTIEYF8ZfD+NQI6X1FY5AkRsx6O/CaGiybA=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.5", "", { "dependencies": { "@aws-sdk/core": "^3.973.7", "@aws-sdk/nested-clients": "3.985.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-OK3cULuJl6c+RcDZfPpaK5o3deTOnKZbxm7pzhFNGA3fI2hF9yDih17fGRazJzGGWaDVlR9ejZrpDef4DJCEsw=="], + + "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/eventstream-codec": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-xEmd3dnyn83K6t4AJxBJA63wpEoCD45ERFG0XMTViD2E/Ohls9TLxjOWPb1PAxR9/46cKy/TImez1GoqP6xVNQ=="], + + "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-pbvZ6Ye/Ks6BAZPa3RhsNjHrvxU9li25PMhSdDpbX0jzdpKpAkIR65gXSNKmA/REnSdEMWSD4vKUW+5eMFzB6w=="], + + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="], + + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="], + + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="], + + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.7", "", { "dependencies": { "@aws-sdk/core": "^3.973.7", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.985.0", "@smithy/core": "^3.22.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-HUD+geASjXSCyL/DHPQc/Ua7JhldTcIglVAoCV8kiVm99IaFSlAbTvEnyhZwdE6bdFyTL+uIaWLaCFSRsglZBQ=="], + + "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-format-url": "^3.972.3", "@smithy/eventstream-codec": "^4.2.8", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-BN4A9K71WRIlpQ3+IYGdBC2wVyobZ95g6ZomodmJ8Te772GWo0iDk2Mv6JIHdr842tOTgi1b3npLIFDUS4hl4g=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.985.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.7", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.7", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.985.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.5", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.13", "@smithy/middleware-retry": "^4.4.30", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.9", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.29", "@smithy/util-defaults-mode-node": "^4.2.32", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-TsWwKzb/2WHafAY0CE7uXgLj0FmnkBTgfioG9HO+7z/zCPcl1+YU+i7dW4o0y+aFxFgxTMG+ExBQpqT/k2ao8g=="], + + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.985.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.7", "@aws-sdk/nested-clients": "3.985.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-+hwpHZyEq8k+9JL2PkE60V93v2kNhUIv7STFt+EAez1UJsJOQDhc5LpzEX66pNjclI5OTwBROs/DhJjC/BtMjQ=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + + "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.985.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-vth7UfGSUR3ljvaq8V4Rc62FsM7GUTH/myxPWkaEgOrprz1/Pc72EgTXxj+cPPPDAfHFIpjhkB7T7Td0RJx+BA=="], + + "@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-n7F2ycckcKFXa01vAsT/SJdjFHfKH9s96QHcs5gn8AaaigASICeME8WdUL9uBp8XV/OVwEt8+6gzn6KFUgQa8g=="], + + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.4", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog=="], + + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="], + + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.5", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.7", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-GsUDF+rXyxDZkkJxUsDxnA67FG+kc5W1dnloCFLl6fWzceevsCYzJpASBzT+BPjwUgREE6FngfJYYYMQUY5fZQ=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.4", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.4", "tslib": "^2.6.2" } }, "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q=="], + + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="], + + "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], + "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], + "@discordjs/builders": ["@discordjs/builders@1.13.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.33", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w=="], + + "@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="], + + "@discordjs/formatters": ["@discordjs/formatters@0.6.2", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ=="], + + "@discordjs/rest": ["@discordjs/rest@2.6.0", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w=="], + + "@discordjs/util": ["@discordjs/util@1.2.0", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg=="], + + "@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="], + + "@effect-atom/atom": ["@effect-atom/atom@0.4.13", "", { "peerDependencies": { "@effect/experimental": "^0.57.0", "@effect/platform": "^0.93.0", "@effect/rpc": "^0.72.1", "effect": "^3.19.0" } }, "sha512-wYCPDg/vN2IWTqxho7kTTSivh//V2TSAcdkQvLXMJWww8YzhcCGmCiDpT6cefyNXvHj+qZrqYB54mrUqG6EYCA=="], + + "@effect-atom/atom-react": ["@effect-atom/atom-react@0.4.6", "", { "dependencies": { "@effect-atom/atom": "^0.4.13" }, "peerDependencies": { "effect": "^3.19", "react": ">=18 <20", "scheduler": "*" } }, "sha512-ZvncuSQsCcUrsddD00JcKDMraPge0AiDMGiJqKc7ProvagCVrxB+Emy5uHM0l0SLGUx4X56bFE0d1tp9Z/Bf+g=="], + + "@effect/experimental": ["@effect/experimental@0.57.11", "", { "dependencies": { "uuid": "^11.0.3" }, "peerDependencies": { "@effect/platform": "^0.93.6", "effect": "^3.19.9", "ioredis": "^5", "lmdb": "^3" }, "optionalPeers": ["ioredis", "lmdb"] }, "sha512-M5uug3Drs/gyTHLfA+XzcIZQGUEV/Jn5yi1POki4oZswhpzNmsVTHl4THpxAordRKwa5lFvTSlsRP684YH7pSw=="], + + "@effect/platform": ["@effect/platform@0.93.8", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.12" } }, "sha512-xTEy6fyTy4ijmFC3afKgtvYtn/JyPoIov4ZSUWJZUv3VeOcUPNGrrqG6IJlWkXs3NhvSywKv7wc1kw3epCQVZw=="], + + "@effect/rpc": ["@effect/rpc@0.72.2", "", { "dependencies": { "msgpackr": "^1.11.4" }, "peerDependencies": { "@effect/platform": "^0.93.3", "effect": "^3.19.5" } }, "sha512-BmTXybXCOq96D2r9mvSW/YdiTQs5CStnd4II+lfVKrMr3pMNERKLZ2LG37Tfm4Sy3Q8ire6IVVKO/CN+VR0uQQ=="], + "@elysiajs/eden": ["@elysiajs/eden@1.4.6", "", { "peerDependencies": { "elysia": ">=1.4.19" } }, "sha512-Tsa4NwXEWg/u73vWiYZQ3L5/ecgZSxqiEjYwpS+4qBKXeTZqZKl2hcgHJSVBL+InEDMi35Xugct7qyAXE5oM4Q=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.0", "", { "os": "android", "cpu": "arm" }, "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.0", "", { "os": "android", "cpu": "arm64" }, "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.0", "", { "os": "android", "cpu": "x64" }, "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.0", "", { "os": "linux", "cpu": "x64" }, "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.0", "", { "os": "none", "cpu": "arm64" }, "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.0", "", { "os": "none", "cpu": "x64" }, "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.0", "", { "os": "none", "cpu": "arm64" }, "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.0", "", { "os": "win32", "cpu": "x64" }, "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg=="], + + "@google/genai": ["@google/genai@1.40.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-fhIww8smT0QYRX78qWOiz/nIQhHMF5wXOrlXvj33HBrz3vKDBb+wibLcEmTA+L9dmPD4KmfNr7UF3LDQVTXNjA=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@mariozechner/pi-ai": ["@mariozechner/pi-ai@0.52.8", "", { "dependencies": { "@anthropic-ai/sdk": "^0.73.0", "@aws-sdk/client-bedrock-runtime": "^3.983.0", "@google/genai": "^1.40.0", "@mistralai/mistralai": "1.10.0", "@sinclair/typebox": "^0.34.41", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "chalk": "^5.6.2", "openai": "6.10.0", "partial-json": "^0.1.7", "proxy-agent": "^6.5.0", "undici": "^7.19.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-+aFCUbKJcskDJhr9wPcMBTy0x/xWio5v1dkxRYXUBPWp+Zt9DSdT5Kmd/IIQ+a0TOZDF4ajt4GY/oAw37X7XTw=="], + + "@mistralai/mistralai": ["@mistralai/mistralai@1.10.0", "", { "dependencies": { "zod": "^3.20.0", "zod-to-json-schema": "^3.24.1" } }, "sha512-tdIgWs4Le8vpvPiUEWne6tK0qbVc+jMenujnvTqOjogrJUsCSQhus0tHTU1avDDh5//Rq2dFgP9mWRAdIEoBqg=="], + + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], + + "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="], + + "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="], + + "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], + + "@openassistant/reacord": ["@openassistant/reacord@workspace:legacy/openassistant/packages/reacord"], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + + "@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="], + + "@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="], + + "@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="], + "@sinclair/typebox": ["@sinclair/typebox@0.34.48", "", {}, "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA=="], + "@smithy/abort-controller": ["@smithy/abort-controller@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw=="], + + "@smithy/config-resolver": ["@smithy/config-resolver@4.4.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ=="], + + "@smithy/core": ["@smithy/core@3.22.1", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.11", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g=="], + + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw=="], + + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.8", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw=="], + + "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.8", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw=="], + + "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ=="], + + "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.8", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A=="], + + "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.8", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ=="], + + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA=="], + + "@smithy/hash-node": ["@smithy/hash-node@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA=="], + + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ=="], + + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A=="], + + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.13", "", { "dependencies": { "@smithy/core": "^3.22.1", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w=="], + + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.30", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg=="], + + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ=="], + + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA=="], + + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.8", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg=="], + + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.9", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w=="], + + "@smithy/property-provider": ["@smithy/property-provider@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w=="], + + "@smithy/protocol-http": ["@smithy/protocol-http@5.3.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ=="], + + "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw=="], + + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA=="], + + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0" } }, "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ=="], + + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.3", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@5.3.8", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg=="], + + "@smithy/smithy-client": ["@smithy/smithy-client@4.11.2", "", { "dependencies": { "@smithy/core": "^3.22.1", "@smithy/middleware-endpoint": "^4.4.13", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.11", "tslib": "^2.6.2" } }, "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A=="], + + "@smithy/types": ["@smithy/types@4.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw=="], + + "@smithy/url-parser": ["@smithy/url-parser@4.2.8", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA=="], + + "@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], + + "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], + + "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="], + + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], + + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.29", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q=="], + + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.32", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q=="], + + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw=="], + + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@smithy/util-middleware": ["@smithy/util-middleware@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A=="], + + "@smithy/util-retry": ["@smithy/util-retry@4.2.8", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg=="], + + "@smithy/util-stream": ["@smithy/util-stream@4.5.11", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.9", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA=="], + + "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + + "@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], + "@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="], "@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="], + "@types/react": ["@types/react@19.2.13", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ=="], + + "@types/react-reconciler": ["@types/react-reconciler@0.31.0", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-wa1KtV3SHKnWeLHfZFg/7nrPSe/Ejdn+qbanNX4V6i9I7C3ECKPfBnC1KM3ef58KFWEohLmTvoTH5XuEIKGX2g=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.7", "", {}, "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "basic-ftp": ["basic-ftp@5.1.0", "", {}, "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw=="], + + "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + + "bowser": ["bowser@2.13.1", "", {}, "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw=="], + + "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "convex": ["convex@1.31.7", "", { "dependencies": { "esbuild": "0.27.0", "prettier": "^3.0.0" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react", "react"], "bin": { "convex": "bin/main.js" } }, "sha512-PtNMe1mAIOvA8Yz100QTOaIdgt2rIuWqencVXrb4McdhxBHZ8IJ1eXTnrgCC9HydyilGT1pOn+KNqT14mqn9fQ=="], + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "discord-api-types": ["discord-api-types@0.38.38", "", {}, "sha512-7qcM5IeZrfb+LXW07HvoI5L+j4PQeMZXEkSm1htHAHh4Y9JSMXBWjy/r7zmUCOj4F7zNjMcm7IMWr131MT2h0Q=="], + + "discord.js": ["discord.js@14.25.1", "", { "dependencies": { "@discordjs/builders": "^1.13.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.33", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + + "effect": ["effect@3.19.16", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-7+XC3vGrbAhCHd8LTFHvnZjRpZKZ8YHRZqJTkpNoxcJ2mCyNs2SwI+6VkV/ij8Y3YW7wfBN4EbU06/F5+m/wkQ=="], + "elysia": ["elysia@1.4.22", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.6", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Q90VCb1RVFxnFaRV0FDoSylESQQLWgLHFmWciQJdX9h3b2cSasji9KWEUvaJuy/L9ciAGg4RAhUVfsXHg5K2RQ=="], + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "esbuild": ["esbuild@0.27.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", "@esbuild/android-arm64": "0.27.0", "@esbuild/android-x64": "0.27.0", "@esbuild/darwin-arm64": "0.27.0", "@esbuild/darwin-x64": "0.27.0", "@esbuild/freebsd-arm64": "0.27.0", "@esbuild/freebsd-x64": "0.27.0", "@esbuild/linux-arm": "0.27.0", "@esbuild/linux-arm64": "0.27.0", "@esbuild/linux-ia32": "0.27.0", "@esbuild/linux-loong64": "0.27.0", "@esbuild/linux-mips64el": "0.27.0", "@esbuild/linux-ppc64": "0.27.0", "@esbuild/linux-riscv64": "0.27.0", "@esbuild/linux-s390x": "0.27.0", "@esbuild/linux-x64": "0.27.0", "@esbuild/netbsd-arm64": "0.27.0", "@esbuild/netbsd-x64": "0.27.0", "@esbuild/openbsd-arm64": "0.27.0", "@esbuild/openbsd-x64": "0.27.0", "@esbuild/openharmony-arm64": "0.27.0", "@esbuild/sunos-x64": "0.27.0", "@esbuild/win32-arm64": "0.27.0", "@esbuild/win32-ia32": "0.27.0", "@esbuild/win32-x64": "0.27.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA=="], + + "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "exact-mirror": ["exact-mirror@0.2.6", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-7s059UIx9/tnOKSySzUk5cPGkoILhTE4p6ncf6uIPaQ+9aRBQzQjc9+q85l51+oZ+P6aBxh084pD0CzBQPcFUA=="], + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + + "fast-xml-parser": ["fast-xml-parser@5.3.4", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA=="], + + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + "file-type": ["file-type@21.3.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="], + "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + + "gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], + + "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], + + "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="], + + "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="], + + "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], + + "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + + "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], + + "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + + "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + + "lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], + + "magic-bytes.js": ["magic-bytes.js@1.13.0", "", {}, "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg=="], + "memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="], + "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "msgpackr": ["msgpackr@1.11.8", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA=="], + + "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], + + "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="], + + "netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="], + + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + + "openai": ["openai@6.10.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-ITxOGo7rO3XRMiKA5l7tQ43iNNu+iXGFAcf2t+aWVzzqRaS0i7m1K2BhxNdaveB+5eENhO0VY1FkiZzhBk4v3A=="], + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="], + + "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], + + "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + + "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], + + "react-reconciler": ["react-reconciler@0.31.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="], + "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], + + "ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], + "undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], + + "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], + + "@mariozechner/pi-ai/undici": ["undici@7.21.0", "", {}, "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg=="], + + "node-fetch/data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + + "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], } } diff --git a/assistant/package.json b/assistant/package.json index 91a1e6157..2711bd551 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -3,10 +3,13 @@ "private": true, "type": "module", "workspaces": [ - "packages/*" + "packages/*", + "legacy/openassistant/packages/reacord" ], "scripts": { - "test": "bun test packages/" + "test": "bun test packages/core/src/ packages/agent-executor-adapter/src/", + "dev:server": "bun run --filter @assistant/server dev", + "dev:bot": "bun run --filter @assistant/bot dev" }, "devDependencies": { "@types/bun": "latest", diff --git a/assistant/packages/bot/package.json b/assistant/packages/bot/package.json new file mode 100644 index 000000000..dd835c0bb --- /dev/null +++ b/assistant/packages/bot/package.json @@ -0,0 +1,23 @@ +{ + "name": "@assistant/bot", + "private": true, + "type": "module", + "scripts": { + "dev": "bun --hot src/index.ts", + "start": "bun src/index.ts" + }, + "dependencies": { + "@assistant/core": "workspace:*", + "@assistant/server": "workspace:*", + "@elysiajs/eden": "latest", + "@openassistant/reacord": "workspace:*", + "convex": "^1.31.7", + "discord.js": "^14.17.3", + "effect": "latest", + "react": "^19.0.0" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/react": "^19.0.0" + } +} diff --git a/assistant/packages/bot/src/commands/ask.tsx b/assistant/packages/bot/src/commands/ask.tsx new file mode 100644 index 000000000..1da98e568 --- /dev/null +++ b/assistant/packages/bot/src/commands/ask.tsx @@ -0,0 +1,59 @@ +/** + * /ask command handler + */ + +import type { ChatInputCommandInteraction, CommandInteraction } from "discord.js"; +import type { Client } from "@assistant/server/client"; +import { unwrap } from "@assistant/server/client"; +import type { Treaty } from "@elysiajs/eden"; +import type { ConvexReactClient } from "convex/react"; +import type { ReacordInstance } from "@openassistant/reacord"; +import { Effect, Runtime } from "effect"; +import { TaskMessage } from "../views/task-message"; + +interface AskCommandDeps { + readonly api: Client; + readonly executor: ReturnType; + readonly convex: ConvexReactClient; + readonly reacord: { + reply: (interaction: CommandInteraction, content: React.ReactNode) => Effect.Effect; + }; +} + +export async function handleAskCommand( + interaction: ChatInputCommandInteraction, + deps: AskCommandDeps, +): Promise { + const prompt = interaction.options.getString("prompt", true); + const requesterId = interaction.user.id; + + await interaction.deferReply(); + + let taskId: string; + let workspaceId: string; + try { + const data = await unwrap( + deps.api.api.tasks.post({ prompt, requesterId }), + ); + taskId = data.taskId; + workspaceId = data.workspaceId; + } catch (error) { + await interaction.editReply({ + content: `\u274c Failed to create task: ${error instanceof Error ? error.message : String(error)}`, + }); + return; + } + + await Runtime.runPromise(Runtime.defaultRuntime)( + deps.reacord.reply( + interaction, + , + ), + ); +} diff --git a/assistant/packages/bot/src/index.ts b/assistant/packages/bot/src/index.ts new file mode 100644 index 000000000..52fdd4398 --- /dev/null +++ b/assistant/packages/bot/src/index.ts @@ -0,0 +1,112 @@ +/** + * Assistant Discord Bot + * + * Connects to Discord, registers slash commands, and renders + * task results using Reacord + Convex reactivity. + */ + +import { + Client, + GatewayIntentBits, + REST, + Routes, + SlashCommandBuilder, +} from "discord.js"; +import { makeReacord } from "@openassistant/reacord"; +import { createClient } from "@assistant/server/client"; +import { treaty } from "@elysiajs/eden"; +import { ConvexReactClient } from "convex/react"; +import { handleAskCommand } from "./commands/ask"; + +// --------------------------------------------------------------------------- +// Config +// --------------------------------------------------------------------------- + +const DISCORD_TOKEN = Bun.env.DISCORD_BOT_TOKEN; +if (!DISCORD_TOKEN) { + throw new Error("DISCORD_BOT_TOKEN is required"); +} + +const SERVER_URL = Bun.env.ASSISTANT_SERVER_URL ?? "http://localhost:3000"; +const EXECUTOR_URL = Bun.env.EXECUTOR_URL ?? "http://localhost:4001"; +const CONVEX_URL = Bun.env.CONVEX_URL ?? "http://127.0.0.1:3210"; + +// --------------------------------------------------------------------------- +// Clients +// --------------------------------------------------------------------------- + +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + ], +}); + +const reacord = makeReacord(client, { maxInstances: 50 }); +const api = createClient(SERVER_URL); +const executor = treaty(EXECUTOR_URL); +const convex = new ConvexReactClient(CONVEX_URL, { + unsavedChangesWarning: false, +}); + +// --------------------------------------------------------------------------- +// Slash commands +// --------------------------------------------------------------------------- + +const commands = [ + new SlashCommandBuilder() + .setName("ask") + .setDescription("Ask the AI assistant to do something") + .addStringOption((opt) => + opt.setName("prompt").setDescription("What do you want the assistant to do?").setRequired(true), + ), + new SlashCommandBuilder() + .setName("clear") + .setDescription("Push chat history out of view"), +]; + +async function registerCommands() { + const rest = new REST().setToken(DISCORD_TOKEN!); + try { + console.log("Registering slash commands..."); + await rest.put(Routes.applicationCommands(client.user!.id), { + body: commands.map((c) => c.toJSON()), + }); + console.log("Slash commands registered."); + } catch (error) { + console.error("Failed to register slash commands:", error); + } +} + +// --------------------------------------------------------------------------- +// Command handler +// --------------------------------------------------------------------------- + +client.on("interactionCreate", async (interaction) => { + if (!interaction.isChatInputCommand()) return; + + switch (interaction.commandName) { + case "ask": + await handleAskCommand(interaction, { api, executor, convex, reacord }); + break; + case "clear": + await interaction.reply({ content: `_${"\n".repeat(50)}_` }); + break; + default: + await interaction.reply({ content: `Unknown command: ${interaction.commandName}` }); + } +}); + +// --------------------------------------------------------------------------- +// Start +// --------------------------------------------------------------------------- + +client.once("clientReady", async () => { + console.log(`Logged in as ${client.user?.tag}`); + await registerCommands(); + console.log(`Connected to server at ${SERVER_URL}`); + console.log(`Executor at ${EXECUTOR_URL}`); + console.log(`Convex at ${CONVEX_URL}`); +}); + +client.login(DISCORD_TOKEN); diff --git a/assistant/packages/bot/src/views/task-message.tsx b/assistant/packages/bot/src/views/task-message.tsx new file mode 100644 index 000000000..b59d6e4f3 --- /dev/null +++ b/assistant/packages/bot/src/views/task-message.tsx @@ -0,0 +1,227 @@ +/** + * TaskMessage — live-updating Discord message. + * + * - Polls assistant server for task status (agent_message, completed, failed) + * - Watches Convex for pending approvals (reactive, no polling) + * - Approval buttons resolve via executor REST API + */ + +import { useState, useEffect, useCallback } from "react"; +import { + Container, + TextDisplay, + Separator, + ActionRow, + Button, + Loading, + useInstance, +} from "@openassistant/reacord"; +// Executor client is just an Eden Treaty client — no separate adapter needed +import type { ConvexReactClient } from "convex/react"; +import { api as convexApi } from "@executor/convex/_generated/api"; + +// --------------------------------------------------------------------------- +// State +// --------------------------------------------------------------------------- + +interface PendingApproval { + readonly id: string; + readonly toolPath: string; + readonly input: unknown; +} + +interface TaskState { + readonly status: "running" | "completed" | "failed"; + readonly statusMessage: string; + readonly agentMessage: string | null; + readonly error: string | null; + readonly pendingApprovals: PendingApproval[]; +} + +const INITIAL_STATE: TaskState = { + status: "running", + statusMessage: "Thinking...", + agentMessage: null, + error: null, + pendingApprovals: [], +}; + +// --------------------------------------------------------------------------- +// Component +// --------------------------------------------------------------------------- + +export interface TaskMessageProps { + readonly taskId: string; + readonly prompt: string; + readonly workspaceId: string; + readonly executor: ReturnType; + readonly convex: ConvexReactClient; +} + +export function TaskMessage({ taskId, prompt, workspaceId, executor, convex }: TaskMessageProps) { + const instance = useInstance(); + const [state, setState] = useState(INITIAL_STATE); + + // Poll assistant server for task completion + useEffect(() => { + let cancelled = false; + + const poll = async () => { + while (!cancelled) { + try { + const resp = await fetch(`http://localhost:3000/api/tasks/${taskId}`); + if (resp.ok) { + const task = await resp.json() as { + status: string; + resultText?: string; + errorMessage?: string; + }; + + if (task.status === "completed" && task.resultText) { + setState((s) => ({ + ...s, + status: "completed", + statusMessage: "Completed", + agentMessage: task.resultText!, + })); + return; + } + + if (task.status === "failed") { + setState((s) => ({ + ...s, + status: "failed", + statusMessage: "Failed", + error: task.errorMessage ?? "Unknown error", + })); + return; + } + } + } catch { + // ignore + } + + await new Promise((r) => setTimeout(r, 1500)); + } + }; + + poll(); + return () => { cancelled = true; }; + }, [taskId]); + + // Watch Convex for pending approvals (reactive!) + useEffect(() => { + const watch = convex.watchQuery(convexApi.database.listPendingApprovals, { + workspaceId, + }); + + const unsubscribe = watch.onUpdate(() => { + const approvals = watch.localQueryResult(); + if (!approvals) return; + + setState((s) => ({ + ...s, + pendingApprovals: (approvals as any[]).map((a) => ({ + id: a.id, + toolPath: a.toolPath, + input: a.input, + })), + statusMessage: (approvals as any[]).length > 0 ? "Waiting for approval..." : s.statusMessage, + })); + }); + + return unsubscribe; + }, [convex, workspaceId]); + + // Deactivate once done + useEffect(() => { + if (state.status !== "running") { + const timer = setTimeout(() => instance.deactivate(), 5000); + return () => clearTimeout(timer); + } + }, [state.status]); + + const handleApproval = useCallback(async (approvalId: string, decision: "approved" | "denied") => { + try { + await executor.api.approvals({ approvalId }).post({ + workspaceId, + decision, + }); + } catch (err) { + console.error(`[approval ${approvalId}]`, err); + } + }, [executor, workspaceId]); + + const isDone = state.status !== "running"; + const accentColor = isDone + ? state.status === "completed" ? 0x57f287 : 0xed4245 + : 0x5865f2; + + return ( + + {`${state.status === "running" ? "\u23f3" : state.status === "completed" ? "\u2705" : "\u274c"} **${state.statusMessage}**`} + {`> ${prompt.length > 200 ? prompt.slice(0, 200) + "..." : prompt}`} + + {state.pendingApprovals.map((approval) => ( + handleApproval(approval.id, d)} + /> + ))} + + {state.error && ( + <> + + {`\u274c **Error:** ${state.error.slice(0, 500)}`} + + )} + + {state.agentMessage && ( + <> + + + {state.agentMessage.length > 1800 + ? state.agentMessage.slice(0, 1800) + "..." + : state.agentMessage} + + + )} + + {state.status === "running" && state.pendingApprovals.length === 0 && } + + ); +} + +// --------------------------------------------------------------------------- +// Approval section +// --------------------------------------------------------------------------- + +function ApprovalSection({ + approval, + onDecision, +}: { + approval: PendingApproval; + onDecision: (decision: "approved" | "denied") => void; +}) { + const [resolved, setResolved] = useState(false); + const toolName = approval.toolPath.split(".").pop() ?? approval.toolPath; + + const handle = (decision: "approved" | "denied") => { + setResolved(true); + onDecision(decision); + }; + + return ( + <> + + {`\u{1f6e1}\ufe0f **Approval Required:** \`${toolName}\``} + {!resolved && ( + +
@@ -800,7 +811,7 @@ export function ToolsView() { ) : (
{sources.map((s) => ( - void refreshInventory()} /> + ))}
)} diff --git a/executor/apps/web/src/hooks/use-workspace-tools.ts b/executor/apps/web/src/hooks/use-workspace-tools.ts index de51e2640..912dd9c5e 100644 --- a/executor/apps/web/src/hooks/use-workspace-tools.ts +++ b/executor/apps/web/src/hooks/use-workspace-tools.ts @@ -1,6 +1,5 @@ "use client"; -import { useCallback } from "react"; import { useQuery } from "convex/react"; import { convexApi } from "../lib/convex-api"; @@ -22,13 +21,8 @@ export function useWorkspaceTools(context: WorkspaceContext | null) { : "skip", ); - const refresh = useCallback(async () => { - // Convex queries are live; manual refresh is not required. - }, []); - return { tools, loading: !!context && tools === undefined, - refresh, }; } From 3a401ab21460c620b1803f7faa47ef70e0a6445d Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sun, 8 Feb 2026 00:57:58 -0800 Subject: [PATCH 22/34] Deduplicate utilities, wire up typed executor client, fix Convex types - Extract asRecord/describeError/asPayload to executor server utils.ts - Extract formatTime/formatTimeAgo to executor web lib/format.ts - Add sync comments to duplicated matchesToolPath/policySpecificity - Create typed executor client (assistant/packages/server/src/executor-client.ts) - Replace raw fetch() in assistant server with typed Eden client - Replace untyped treaty() in bot with typed createExecutorClient - Replace inline executor type annotations in bot commands - Replace all any types in convex/database.ts with proper Doc<>/QueryCtx types - Add explanatory comment for as-never casts in database wrapper --- assistant/packages/bot/src/commands/ask.tsx | 3 +- assistant/packages/bot/src/index.ts | 4 +- .../packages/bot/src/views/task-message.tsx | 3 +- assistant/packages/bot/tsconfig.json | 3 +- assistant/packages/server/package.json | 3 +- .../packages/server/src/executor-client.ts | 12 ++ assistant/packages/server/src/index.ts | 12 +- assistant/packages/server/tsconfig.json | 3 +- .../adapters/in-process-execution-adapter.ts | 5 +- executor/apps/server/src/service.ts | 17 +- executor/apps/server/src/tools.ts | 11 +- executor/apps/server/src/utils.ts | 29 +++ .../web/src/components/dashboard-view.tsx | 16 +- .../apps/web/src/components/tasks-view.tsx | 13 +- executor/apps/web/src/lib/format.ts | 20 +++ executor/convex/database.ts | 165 ++++++++++++------ 16 files changed, 204 insertions(+), 115 deletions(-) create mode 100644 assistant/packages/server/src/executor-client.ts create mode 100644 executor/apps/server/src/utils.ts create mode 100644 executor/apps/web/src/lib/format.ts diff --git a/assistant/packages/bot/src/commands/ask.tsx b/assistant/packages/bot/src/commands/ask.tsx index 28c704573..ed1398a7c 100644 --- a/assistant/packages/bot/src/commands/ask.tsx +++ b/assistant/packages/bot/src/commands/ask.tsx @@ -5,6 +5,7 @@ import type { ChatInputCommandInteraction, CommandInteraction } from "discord.js"; import type { Client } from "@assistant/server/client"; import { unwrap } from "@assistant/server/client"; +import type { ExecutorClient } from "@assistant/server/executor-client"; import type { ConvexReactClient } from "convex/react"; import { ConvexProvider } from "convex/react"; import type { ReacordInstance } from "@openassistant/reacord"; @@ -13,7 +14,7 @@ import { TaskMessage } from "../views/task-message"; interface AskCommandDeps { readonly api: Client; - readonly executor: ReturnType; + readonly executor: ExecutorClient; readonly convex: ConvexReactClient; readonly reacord: { reply: (interaction: CommandInteraction, content: React.ReactNode) => Effect.Effect; diff --git a/assistant/packages/bot/src/index.ts b/assistant/packages/bot/src/index.ts index 52fdd4398..3393d5058 100644 --- a/assistant/packages/bot/src/index.ts +++ b/assistant/packages/bot/src/index.ts @@ -14,7 +14,7 @@ import { } from "discord.js"; import { makeReacord } from "@openassistant/reacord"; import { createClient } from "@assistant/server/client"; -import { treaty } from "@elysiajs/eden"; +import { createExecutorClient } from "@assistant/server/executor-client"; import { ConvexReactClient } from "convex/react"; import { handleAskCommand } from "./commands/ask"; @@ -44,7 +44,7 @@ const client = new Client({ const reacord = makeReacord(client, { maxInstances: 50 }); const api = createClient(SERVER_URL); -const executor = treaty(EXECUTOR_URL); +const executor = createExecutorClient(EXECUTOR_URL); const convex = new ConvexReactClient(CONVEX_URL, { unsavedChangesWarning: false, }); diff --git a/assistant/packages/bot/src/views/task-message.tsx b/assistant/packages/bot/src/views/task-message.tsx index 6fa785524..28753e50b 100644 --- a/assistant/packages/bot/src/views/task-message.tsx +++ b/assistant/packages/bot/src/views/task-message.tsx @@ -18,6 +18,7 @@ import { } from "@openassistant/reacord"; import { useQuery } from "convex/react"; import { api } from "@executor/convex/_generated/api"; +import type { ExecutorClient } from "@assistant/server/executor-client"; // --------------------------------------------------------------------------- // Component @@ -27,7 +28,7 @@ export interface TaskMessageProps { readonly agentTaskId: string; readonly prompt: string; readonly workspaceId: string; - readonly executor: ReturnType; + readonly executor: ExecutorClient; } export function TaskMessage({ agentTaskId, prompt, workspaceId, executor }: TaskMessageProps) { diff --git a/assistant/packages/bot/tsconfig.json b/assistant/packages/bot/tsconfig.json index de261c5a1..f237651b6 100644 --- a/assistant/packages/bot/tsconfig.json +++ b/assistant/packages/bot/tsconfig.json @@ -8,7 +8,8 @@ "esModuleInterop": true, "skipLibCheck": true, "paths": { - "@executor/convex/*": ["../../../executor/convex/*"] + "@executor/convex/*": ["../../../executor/convex/*"], + "@executor/server/*": ["../../../executor/apps/server/*"] } }, "include": ["src"] diff --git a/assistant/packages/server/package.json b/assistant/packages/server/package.json index acb3768ed..4039434c5 100644 --- a/assistant/packages/server/package.json +++ b/assistant/packages/server/package.json @@ -4,7 +4,8 @@ "type": "module", "exports": { ".": "./src/index.ts", - "./client": "./src/client.ts" + "./client": "./src/client.ts", + "./executor-client": "./src/executor-client.ts" }, "scripts": { "dev": "bun --hot src/index.ts", diff --git a/assistant/packages/server/src/executor-client.ts b/assistant/packages/server/src/executor-client.ts new file mode 100644 index 000000000..a5bfcade4 --- /dev/null +++ b/assistant/packages/server/src/executor-client.ts @@ -0,0 +1,12 @@ +/** + * Typed Eden Treaty client for the executor server. + */ + +import { treaty } from "@elysiajs/eden"; +import type { App } from "@executor/server/src/index"; + +export function createExecutorClient(baseUrl: string) { + return treaty(baseUrl); +} + +export type ExecutorClient = ReturnType; diff --git a/assistant/packages/server/src/index.ts b/assistant/packages/server/src/index.ts index 9911ef106..76e741938 100644 --- a/assistant/packages/server/src/index.ts +++ b/assistant/packages/server/src/index.ts @@ -3,24 +3,20 @@ */ import { createApp } from "./routes"; +import { createExecutorClient } from "./executor-client"; const PORT = Number(Bun.env.PORT ?? 3000); const EXECUTOR_URL = Bun.env.EXECUTOR_URL ?? "http://localhost:4001"; const CONVEX_URL = Bun.env.CONVEX_URL ?? "http://127.0.0.1:3210"; // Bootstrap anonymous context on executor -const resp = await fetch(`${EXECUTOR_URL}/api/auth/anonymous/bootstrap`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: "{}", -}); +const executor = createExecutorClient(EXECUTOR_URL); +const { data: ctx, error: bootstrapError } = await executor.api.auth.anonymous.bootstrap.post({}); -if (!resp.ok) { +if (bootstrapError || !ctx) { console.error("Failed to bootstrap executor context. Is the executor running at", EXECUTOR_URL, "?"); process.exit(1); } - -const ctx = await resp.json() as { workspaceId: string; actorId: string; clientId: string }; console.log(`[assistant] executor context: workspace=${ctx.workspaceId} actor=${ctx.actorId}`); const contextLines: string[] = []; diff --git a/assistant/packages/server/tsconfig.json b/assistant/packages/server/tsconfig.json index d76ff0bfa..2d2bcf04f 100644 --- a/assistant/packages/server/tsconfig.json +++ b/assistant/packages/server/tsconfig.json @@ -7,7 +7,8 @@ "esModuleInterop": true, "skipLibCheck": true, "paths": { - "@executor/convex/*": ["../../../executor/convex/*"] + "@executor/convex/*": ["../../../executor/convex/*"], + "@executor/server/*": ["../../../executor/apps/server/*"] } }, "include": ["src"] diff --git a/executor/apps/server/src/adapters/in-process-execution-adapter.ts b/executor/apps/server/src/adapters/in-process-execution-adapter.ts index d492c36a4..78aa8b1d1 100644 --- a/executor/apps/server/src/adapters/in-process-execution-adapter.ts +++ b/executor/apps/server/src/adapters/in-process-execution-adapter.ts @@ -5,6 +5,7 @@ import type { ToolCallRequest, ToolCallResult, } from "../types"; +import { describeError } from "../utils"; interface InProcessExecutionAdapterOptions { runId: string; @@ -12,10 +13,6 @@ interface InProcessExecutionAdapterOptions { emitOutput: (event: RuntimeOutputEvent) => void; } -function describeError(error: unknown): string { - return error instanceof Error ? error.message : String(error); -} - export class InProcessExecutionAdapter implements ExecutionAdapter { constructor(private readonly options: InProcessExecutionAdapterOptions) {} diff --git a/executor/apps/server/src/service.ts b/executor/apps/server/src/service.ts index be1bfa5bf..e4966d61a 100644 --- a/executor/apps/server/src/service.ts +++ b/executor/apps/server/src/service.ts @@ -29,6 +29,7 @@ import type { CredentialRecord, ToolRunContext, } from "./types"; +import { asPayload, describeError } from "./utils"; function createTaskId(): string { return `task_${crypto.randomUUID()}`; @@ -38,23 +39,15 @@ function createApprovalId(): string { return `approval_${crypto.randomUUID()}`; } -function asPayload(value: unknown): Record { - if (value && typeof value === "object") { - return value as Record; - } - return { value }; -} - -function describeError(error: unknown): string { - return error instanceof Error ? error.message : String(error); -} - +// NOTE: Duplicated in convex/database.ts — these must be kept in sync. +// They can't share code because Convex functions run in a separate environment. function matchesToolPath(pattern: string, toolPath: string): boolean { const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*"); const regex = new RegExp(`^${escaped}$`); return regex.test(toolPath); } +// NOTE: Duplicated in convex/database.ts — these must be kept in sync. function policySpecificity(policy: AccessPolicyRecord, actorId?: string, clientId?: string): number { let score = 0; if (policy.actorId && actorId && policy.actorId === actorId) score += 4; @@ -326,7 +319,7 @@ export class ExecutorService { } getBaseToolCount(): number { - return [...this.baseTools.keys()].filter((path) => path !== "discover").length + 1; + return this.baseTools.size; } async createTask(input: CreateTaskInput): Promise<{ task: TaskRecord }> { diff --git a/executor/apps/server/src/tools.ts b/executor/apps/server/src/tools.ts index 0b5239bf7..11b6b964c 100644 --- a/executor/apps/server/src/tools.ts +++ b/executor/apps/server/src/tools.ts @@ -1,8 +1,5 @@ import type { ToolDefinition } from "./types"; - -function asObject(value: unknown): Record { - return value && typeof value === "object" ? (value as Record) : {}; -} +import { asRecord } from "./utils"; export const DEFAULT_TOOLS: ToolDefinition[] = [ { @@ -21,7 +18,7 @@ export const DEFAULT_TOOLS: ToolDefinition[] = [ approval: "auto", source: "local", run: async (input) => { - const payload = asObject(input); + const payload = asRecord(input); const a = Number(payload.a ?? 0); const b = Number(payload.b ?? 0); if (!Number.isFinite(a) || !Number.isFinite(b)) { @@ -36,7 +33,7 @@ export const DEFAULT_TOOLS: ToolDefinition[] = [ approval: "required", source: "local", run: async (input) => { - const payload = asObject(input); + const payload = asRecord(input); const channel = String(payload.channel ?? "general"); const message = String(payload.message ?? ""); if (!message.trim()) { @@ -55,7 +52,7 @@ export const DEFAULT_TOOLS: ToolDefinition[] = [ approval: "required", source: "local", run: async (input) => { - const payload = asObject(input); + const payload = asRecord(input); const key = String(payload.key ?? ""); if (!key.trim()) { throw new Error("admin.delete_data requires key"); diff --git a/executor/apps/server/src/utils.ts b/executor/apps/server/src/utils.ts new file mode 100644 index 000000000..4ef10f206 --- /dev/null +++ b/executor/apps/server/src/utils.ts @@ -0,0 +1,29 @@ +/** + * Safely cast an unknown value to a Record. + * Returns an empty object for nullish, non-object, or array values. + */ +export function asRecord(value: unknown): Record { + if (value && typeof value === "object" && !Array.isArray(value)) { + return value as Record; + } + return {}; +} + +/** + * Like `asRecord`, but for tool call payloads: wraps non-object values + * in `{ value }` instead of returning an empty object. Use this when + * the caller may have passed a bare primitive as input. + */ +export function asPayload(value: unknown): Record { + if (value && typeof value === "object") { + return value as Record; + } + return { value }; +} + +/** + * Extract a human-readable message from an unknown thrown value. + */ +export function describeError(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} diff --git a/executor/apps/web/src/components/dashboard-view.tsx b/executor/apps/web/src/components/dashboard-view.tsx index 80db85624..2adab7fbb 100644 --- a/executor/apps/web/src/components/dashboard-view.tsx +++ b/executor/apps/web/src/components/dashboard-view.tsx @@ -20,21 +20,7 @@ import { useWorkspaceTools } from "@/hooks/use-workspace-tools"; import { useQuery } from "convex/react"; import { convexApi } from "@/lib/convex-api"; import type { TaskRecord, PendingApprovalRecord } from "@/lib/types"; - -function formatTime(ts: number) { - return new Date(ts).toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }); -} - -function formatTimeAgo(ts: number) { - const diff = Date.now() - ts; - if (diff < 60_000) return `${Math.floor(diff / 1000)}s ago`; - if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`; - return `${Math.floor(diff / 3_600_000)}h ago`; -} +import { formatTime, formatTimeAgo } from "@/lib/format"; function StatCard({ label, diff --git a/executor/apps/web/src/components/tasks-view.tsx b/executor/apps/web/src/components/tasks-view.tsx index 70308d890..54f8d7d7b 100644 --- a/executor/apps/web/src/components/tasks-view.tsx +++ b/executor/apps/web/src/components/tasks-view.tsx @@ -33,6 +33,7 @@ import type { } from "@/lib/types"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; +import { formatTime } from "@/lib/format"; const DEFAULT_CODE = `// Example: call some tools const time = await tools.utils.get_time(); @@ -47,14 +48,6 @@ await tools.admin.send_announcement({ message: "Hello from executor!" });`; -function formatTime(ts: number) { - return new Date(ts).toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }); -} - function formatDate(ts: number) { return new Date(ts).toLocaleDateString([], { month: "short", @@ -139,12 +132,12 @@ function TaskComposer() {
-
+
diff --git a/executor/apps/web/src/lib/format.ts b/executor/apps/web/src/lib/format.ts new file mode 100644 index 000000000..59fa5b689 --- /dev/null +++ b/executor/apps/web/src/lib/format.ts @@ -0,0 +1,20 @@ +/** + * Format a timestamp as a locale time string (HH:MM:SS). + */ +export function formatTime(ts: number) { + return new Date(ts).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); +} + +/** + * Format a timestamp as a relative "ago" string (e.g. "5s ago", "3m ago"). + */ +export function formatTimeAgo(ts: number) { + const diff = Date.now() - ts; + if (diff < 60_000) return `${Math.floor(diff / 1000)}s ago`; + if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`; + return `${Math.floor(diff / 3_600_000)}h ago`; +} diff --git a/executor/convex/database.ts b/executor/convex/database.ts index 49568cf6a..abfcf5bca 100644 --- a/executor/convex/database.ts +++ b/executor/convex/database.ts @@ -1,4 +1,6 @@ import { v } from "convex/values"; +import type { Doc } from "./_generated/dataModel"; +import type { QueryCtx } from "./_generated/server"; import { mutation, query } from "./_generated/server"; const DEFAULT_TIMEOUT_MS = 15_000; @@ -18,6 +20,8 @@ function optionalFromNormalized(value?: string): string | undefined { return value; } +// NOTE: Canonical version lives in apps/server/src/utils.ts. +// Convex can't import from the server, so this is a local copy. function asRecord(value: unknown): Record { if (value && typeof value === "object" && !Array.isArray(value)) { return value as Record; @@ -25,7 +29,7 @@ function asRecord(value: unknown): Record { return {}; } -function mapTask(doc: any) { +function mapTask(doc: Doc<"tasks">) { return { id: doc.taskId, code: doc.code, @@ -47,7 +51,7 @@ function mapTask(doc: any) { }; } -function mapApproval(doc: any) { +function mapApproval(doc: Doc<"approvals">) { return { id: doc.approvalId, taskId: doc.taskId, @@ -61,7 +65,7 @@ function mapApproval(doc: any) { }; } -function mapPolicy(doc: any) { +function mapPolicy(doc: Doc<"accessPolicies">) { return { id: doc.policyId, workspaceId: doc.workspaceId, @@ -75,7 +79,7 @@ function mapPolicy(doc: any) { }; } -function mapCredential(doc: any) { +function mapCredential(doc: Doc<"sourceCredentials">) { return { id: doc.credentialId, workspaceId: doc.workspaceId, @@ -88,7 +92,7 @@ function mapCredential(doc: any) { }; } -function mapSource(doc: any) { +function mapSource(doc: Doc<"toolSources">) { return { id: doc.sourceId, workspaceId: doc.workspaceId, @@ -101,7 +105,7 @@ function mapSource(doc: any) { }; } -function mapAnonymousContext(doc: any) { +function mapAnonymousContext(doc: Doc<"anonymousSessions">) { return { sessionId: doc.sessionId, workspaceId: doc.workspaceId, @@ -112,7 +116,7 @@ function mapAnonymousContext(doc: any) { }; } -function mapTaskEvent(doc: any) { +function mapTaskEvent(doc: Doc<"taskEvents">) { return { id: doc.sequence, taskId: doc.taskId, @@ -123,7 +127,7 @@ function mapTaskEvent(doc: any) { }; } -function mapWorkspaceTool(doc: any) { +function mapWorkspaceTool(doc: Doc<"workspaceTools">) { return { path: doc.path, description: doc.description, @@ -134,13 +138,20 @@ function mapWorkspaceTool(doc: any) { }; } +// NOTE: Duplicated in apps/server/src/service.ts — these must be kept in sync. +// They can't share code because Convex functions run in a separate environment. function matchesToolPath(pattern: string, toolPath: string): boolean { const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*"); const regex = new RegExp(`^${escaped}$`); return regex.test(toolPath); } -function policySpecificity(policy: any, actorId?: string, clientId?: string): number { +// NOTE: Duplicated in apps/server/src/service.ts — these must be kept in sync. +function policySpecificity( + policy: Pick, "actorId" | "clientId" | "toolPathPattern" | "priority">, + actorId?: string, + clientId?: string, +): number { let score = 0; if (policy.actorId && actorId && policy.actorId === actorId) score += 4; if (policy.clientId && clientId && policy.clientId === clientId) score += 2; @@ -149,14 +160,14 @@ function policySpecificity(policy: any, actorId?: string, clientId?: string): nu return score; } -async function getTaskDoc(ctx: any, taskId: string) { - return await ctx.db.query("tasks").withIndex("by_task_id", (q: any) => q.eq("taskId", taskId)).unique(); +async function getTaskDoc(ctx: { db: QueryCtx["db"] }, taskId: string) { + return await ctx.db.query("tasks").withIndex("by_task_id", (q) => q.eq("taskId", taskId)).unique(); } -async function getApprovalDoc(ctx: any, approvalId: string) { +async function getApprovalDoc(ctx: { db: QueryCtx["db"] }, approvalId: string) { return await ctx.db .query("approvals") - .withIndex("by_approval_id", (q: any) => q.eq("approvalId", approvalId)) + .withIndex("by_approval_id", (q) => q.eq("approvalId", approvalId)) .unique(); } @@ -213,7 +224,7 @@ export const listTasks = query({ handler: async (ctx, args) => { const docs = await ctx.db .query("tasks") - .withIndex("by_workspace_created", (q: any) => q.eq("workspaceId", args.workspaceId)) + .withIndex("by_workspace_created", (q) => q.eq("workspaceId", args.workspaceId)) .order("desc") .take(500); return docs.map(mapTask); @@ -225,11 +236,11 @@ export const listQueuedTaskIds = query({ handler: async (ctx, args) => { const docs = await ctx.db .query("tasks") - .withIndex("by_status_created", (q: any) => q.eq("status", "queued")) + .withIndex("by_status_created", (q) => q.eq("status", "queued")) .order("asc") .take(args.limit ?? 20); - return docs.map((doc: any) => doc.taskId); + return docs.map((doc) => doc.taskId); }, }); @@ -365,10 +376,11 @@ export const listApprovals = query({ }, handler: async (ctx, args) => { if (args.status) { + const status = args.status; const docs = await ctx.db .query("approvals") - .withIndex("by_workspace_status_created", (q: any) => - q.eq("workspaceId", args.workspaceId).eq("status", args.status), + .withIndex("by_workspace_status_created", (q) => + q.eq("workspaceId", args.workspaceId).eq("status", status), ) .order("desc") .collect(); @@ -377,7 +389,7 @@ export const listApprovals = query({ const docs = await ctx.db .query("approvals") - .withIndex("by_workspace_created", (q: any) => q.eq("workspaceId", args.workspaceId)) + .withIndex("by_workspace_created", (q) => q.eq("workspaceId", args.workspaceId)) .order("desc") .take(500); return docs.map(mapApproval); @@ -389,13 +401,17 @@ export const listPendingApprovals = query({ handler: async (ctx, args) => { const docs = await ctx.db .query("approvals") - .withIndex("by_workspace_status_created", (q: any) => + .withIndex("by_workspace_status_created", (q) => q.eq("workspaceId", args.workspaceId).eq("status", "pending"), ) .order("asc") .collect(); - const results: Array = []; + const results: Array< + ReturnType & { + task: { id: string; status: string; runtimeId: string; timeoutMs: number; createdAt: number }; + } + > = []; for (const approval of docs) { const task = await getTaskDoc(ctx, approval.taskId); if (!task) { @@ -457,7 +473,7 @@ export const getApprovalInWorkspace = query({ // ── Agent Tasks ── -function mapAgentTask(doc: any) { +function mapAgentTask(doc: Doc<"agentTasks">) { return { id: doc.agentTaskId, prompt: doc.prompt, @@ -473,10 +489,10 @@ function mapAgentTask(doc: any) { }; } -async function getAgentTaskDoc(ctx: any, agentTaskId: string) { +async function getAgentTaskDoc(ctx: { db: QueryCtx["db"] }, agentTaskId: string) { return await ctx.db .query("agentTasks") - .withIndex("by_agent_task_id", (q: any) => q.eq("agentTaskId", agentTaskId)) + .withIndex("by_agent_task_id", (q) => q.eq("agentTaskId", agentTaskId)) .unique(); } @@ -545,21 +561,52 @@ export const updateAgentTask = mutation({ }, }); +// Default tools seeded into every new workspace so the editor has +// IntelliSense immediately (before the worker syncs external sources). +const DEFAULT_WORKSPACE_TOOLS = [ + { path: "utils.get_time", description: "Return current server time.", approval: "auto", source: "local" }, + { path: "math.add", description: "Add two numbers.", approval: "auto", source: "local" }, + { path: "admin.send_announcement", description: "Mock announcement sender that requires approval.", approval: "required", source: "local" }, + { path: "admin.delete_data", description: "Mock destructive operation that requires approval.", approval: "required", source: "local" }, + { path: "discover", description: "List all available tools and their descriptions.", approval: "auto", source: "local" }, +]; + export const bootstrapAnonymousSession = mutation({ args: { sessionId: v.optional(v.string()) }, handler: async (ctx, args) => { const now = Date.now(); if (args.sessionId) { + const sessionId = args.sessionId; const existing = await ctx.db .query("anonymousSessions") - .withIndex("by_session_id", (q: any) => q.eq("sessionId", args.sessionId)) + .withIndex("by_session_id", (q) => q.eq("sessionId", sessionId)) .unique(); if (existing) { await ctx.db.patch(existing._id, { lastSeenAt: now }); + + // Ensure the workspace has at least the default tools seeded. + // This handles existing sessions created before seeding was added. + const existingTools = await ctx.db + .query("workspaceTools") + .withIndex("by_workspace_path", (q: any) => q.eq("workspaceId", existing.workspaceId)) + .first(); + if (!existingTools) { + for (const tool of DEFAULT_WORKSPACE_TOOLS) { + await ctx.db.insert("workspaceTools", { + workspaceId: existing.workspaceId, + path: tool.path, + description: tool.description, + approval: tool.approval, + source: tool.source, + updatedAt: now, + }); + } + } + const refreshed = await ctx.db .query("anonymousSessions") - .withIndex("by_session_id", (q: any) => q.eq("sessionId", args.sessionId)) + .withIndex("by_session_id", (q) => q.eq("sessionId", sessionId)) .unique(); if (!refreshed) { throw new Error("Failed to refresh anonymous session"); @@ -582,9 +629,23 @@ export const bootstrapAnonymousSession = mutation({ lastSeenAt: now, }); + // Seed the workspace with default tools so the editor has IntelliSense + // immediately. The worker will overwrite these when it syncs external + // tool sources (if any are added later). + for (const tool of DEFAULT_WORKSPACE_TOOLS) { + await ctx.db.insert("workspaceTools", { + workspaceId, + path: tool.path, + description: tool.description, + approval: tool.approval, + source: tool.source, + updatedAt: now, + }); + } + const created = await ctx.db .query("anonymousSessions") - .withIndex("by_session_id", (q: any) => q.eq("sessionId", sessionId)) + .withIndex("by_session_id", (q) => q.eq("sessionId", sessionId)) .unique(); if (!created) { throw new Error("Failed to create anonymous session"); @@ -609,7 +670,7 @@ export const upsertAccessPolicy = mutation({ const policyId = args.id ?? `policy_${crypto.randomUUID()}`; const existing = await ctx.db .query("accessPolicies") - .withIndex("by_policy_id", (q: any) => q.eq("policyId", policyId)) + .withIndex("by_policy_id", (q) => q.eq("policyId", policyId)) .unique(); if (existing) { @@ -638,7 +699,7 @@ export const upsertAccessPolicy = mutation({ const updated = await ctx.db .query("accessPolicies") - .withIndex("by_policy_id", (q: any) => q.eq("policyId", policyId)) + .withIndex("by_policy_id", (q) => q.eq("policyId", policyId)) .unique(); if (!updated) { throw new Error(`Failed to read policy ${policyId}`); @@ -652,11 +713,11 @@ export const listAccessPolicies = query({ handler: async (ctx, args) => { const docs = await ctx.db .query("accessPolicies") - .withIndex("by_workspace_created", (q: any) => q.eq("workspaceId", args.workspaceId)) + .withIndex("by_workspace_created", (q) => q.eq("workspaceId", args.workspaceId)) .collect(); return docs - .sort((a: any, b: any) => { + .sort((a, b) => { if (a.priority !== b.priority) { return b.priority - a.priority; } @@ -681,7 +742,7 @@ export const upsertCredential = mutation({ const existing = await ctx.db .query("sourceCredentials") - .withIndex("by_workspace_source_scope_actor", (q: any) => + .withIndex("by_workspace_source_scope_actor", (q) => q .eq("workspaceId", args.workspaceId) .eq("sourceKey", args.sourceKey) @@ -710,7 +771,7 @@ export const upsertCredential = mutation({ const updated = await ctx.db .query("sourceCredentials") - .withIndex("by_workspace_source_scope_actor", (q: any) => + .withIndex("by_workspace_source_scope_actor", (q) => q .eq("workspaceId", args.workspaceId) .eq("sourceKey", args.sourceKey) @@ -732,7 +793,7 @@ export const listCredentials = query({ handler: async (ctx, args) => { const docs = await ctx.db .query("sourceCredentials") - .withIndex("by_workspace_created", (q: any) => q.eq("workspaceId", args.workspaceId)) + .withIndex("by_workspace_created", (q) => q.eq("workspaceId", args.workspaceId)) .order("desc") .collect(); return docs.map(mapCredential); @@ -755,7 +816,7 @@ export const resolveCredential = query({ const actorDoc = await ctx.db .query("sourceCredentials") - .withIndex("by_workspace_source_scope_actor", (q: any) => + .withIndex("by_workspace_source_scope_actor", (q) => q .eq("workspaceId", args.workspaceId) .eq("sourceKey", args.sourceKey) @@ -769,7 +830,7 @@ export const resolveCredential = query({ const workspaceDoc = await ctx.db .query("sourceCredentials") - .withIndex("by_workspace_source_scope_actor", (q: any) => + .withIndex("by_workspace_source_scope_actor", (q) => q .eq("workspaceId", args.workspaceId) .eq("sourceKey", args.sourceKey) @@ -796,12 +857,12 @@ export const upsertToolSource = mutation({ const sourceId = args.id ?? `src_${crypto.randomUUID()}`; const existing = await ctx.db .query("toolSources") - .withIndex("by_source_id", (q: any) => q.eq("sourceId", sourceId)) + .withIndex("by_source_id", (q) => q.eq("sourceId", sourceId)) .unique(); const conflict = await ctx.db .query("toolSources") - .withIndex("by_workspace_name", (q: any) => q.eq("workspaceId", args.workspaceId).eq("name", args.name)) + .withIndex("by_workspace_name", (q) => q.eq("workspaceId", args.workspaceId).eq("name", args.name)) .unique(); if (conflict && conflict.sourceId !== sourceId) { @@ -832,7 +893,7 @@ export const upsertToolSource = mutation({ const updated = await ctx.db .query("toolSources") - .withIndex("by_source_id", (q: any) => q.eq("sourceId", sourceId)) + .withIndex("by_source_id", (q) => q.eq("sourceId", sourceId)) .unique(); if (!updated) { throw new Error(`Failed to read tool source ${sourceId}`); @@ -846,7 +907,7 @@ export const listToolSources = query({ handler: async (ctx, args) => { const docs = await ctx.db .query("toolSources") - .withIndex("by_workspace_updated", (q: any) => q.eq("workspaceId", args.workspaceId)) + .withIndex("by_workspace_updated", (q) => q.eq("workspaceId", args.workspaceId)) .order("desc") .collect(); return docs.map(mapSource); @@ -889,7 +950,7 @@ export const syncWorkspaceTools = mutation({ handler: async (ctx, args) => { const existing = await ctx.db .query("workspaceTools") - .withIndex("by_workspace_updated", (q: any) => q.eq("workspaceId", args.workspaceId)) + .withIndex("by_workspace_updated", (q) => q.eq("workspaceId", args.workspaceId)) .collect(); for (const doc of existing) { @@ -924,26 +985,26 @@ export const listWorkspaceToolsForContext = query({ const [tools, policies] = await Promise.all([ ctx.db .query("workspaceTools") - .withIndex("by_workspace_path", (q: any) => q.eq("workspaceId", args.workspaceId)) + .withIndex("by_workspace_path", (q) => q.eq("workspaceId", args.workspaceId)) .collect(), ctx.db .query("accessPolicies") - .withIndex("by_workspace_created", (q: any) => q.eq("workspaceId", args.workspaceId)) + .withIndex("by_workspace_created", (q) => q.eq("workspaceId", args.workspaceId)) .collect(), ]); return tools - .map((toolDoc: any) => { + .map((toolDoc) => { const baseDecision = toolDoc.approval === "required" ? "require_approval" : "allow"; const candidates = policies - .filter((policy: any) => { + .filter((policy) => { const policyActorId = optionalFromNormalized(policy.actorId); const policyClientId = optionalFromNormalized(policy.clientId); if (policyActorId && policyActorId !== args.actorId) return false; if (policyClientId && policyClientId !== args.clientId) return false; return matchesToolPath(policy.toolPathPattern, toolDoc.path); }) - .sort((a: any, b: any) => { + .sort((a, b) => { const bScore = policySpecificity(b, args.actorId, args.clientId); const aScore = policySpecificity(a, args.actorId, args.clientId); return bScore - aScore; @@ -960,8 +1021,8 @@ export const listWorkspaceToolsForContext = query({ approval: decision === "require_approval" ? "required" : "auto", }; }) - .filter((tool: any) => tool !== null) - .sort((a: any, b: any) => a.path.localeCompare(b.path)); + .filter((tool): tool is NonNullable => tool !== null) + .sort((a, b) => a.path.localeCompare(b.path)); }, }); @@ -970,7 +1031,7 @@ export const deleteToolSource = mutation({ handler: async (ctx, args) => { const doc = await ctx.db .query("toolSources") - .withIndex("by_source_id", (q: any) => q.eq("sourceId", args.sourceId)) + .withIndex("by_source_id", (q) => q.eq("sourceId", args.sourceId)) .unique(); if (!doc || doc.workspaceId !== args.workspaceId) { @@ -997,7 +1058,7 @@ export const createTaskEvent = mutation({ const latest = await ctx.db .query("taskEvents") - .withIndex("by_task_sequence", (q: any) => q.eq("taskId", args.taskId)) + .withIndex("by_task_sequence", (q) => q.eq("taskId", args.taskId)) .order("desc") .first(); @@ -1015,7 +1076,7 @@ export const createTaskEvent = mutation({ const created = await ctx.db .query("taskEvents") - .withIndex("by_task_sequence", (q: any) => q.eq("taskId", args.taskId).eq("sequence", sequence)) + .withIndex("by_task_sequence", (q) => q.eq("taskId", args.taskId).eq("sequence", sequence)) .unique(); if (!created) { @@ -1031,7 +1092,7 @@ export const listTaskEvents = query({ handler: async (ctx, args) => { const docs = await ctx.db .query("taskEvents") - .withIndex("by_task_sequence", (q: any) => q.eq("taskId", args.taskId)) + .withIndex("by_task_sequence", (q) => q.eq("taskId", args.taskId)) .order("asc") .collect(); From 85da31ca48d74512a34354c4e9bbae30b6df817a Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sun, 8 Feb 2026 00:58:09 -0800 Subject: [PATCH 23/34] Clean up Elysia routes: deduplicate MCP handler, remove redundant validation - Extract MCP handler to shared function instead of copy-pasting for 3 HTTP methods - Remove redundant manual workspaceId/body validation (Elysia schema already validates) - Simplify getBaseToolCount() to use baseTools.size - Fix .gitignore wildcard patterns (_.log -> *.log) --- .gitignore | 4 +- executor/apps/server/src/database.ts | 10 +++ executor/apps/server/src/index.ts | 95 +++++----------------------- 3 files changed, 29 insertions(+), 80 deletions(-) diff --git a/.gitignore b/.gitignore index 734b03b35..3ba593815 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,8 @@ coverage # logs logs -_.log -report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json +*.log +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # dotenv environment variable files .env diff --git a/executor/apps/server/src/database.ts b/executor/apps/server/src/database.ts index 9e0ddc3aa..e0bc4636d 100644 --- a/executor/apps/server/src/database.ts +++ b/executor/apps/server/src/database.ts @@ -56,6 +56,16 @@ export class ExecutorDatabase { this.client = new ConvexHttpClient(convexUrl); } + // The `as never` casts below are intentional. ConvexHttpClient.mutation() and + // .query() expect the exact generated function reference type and its + // corresponding args type. Because we call them dynamically with string + // function names (e.g. "database:createTask") and generic arg objects, + // TypeScript cannot verify the relationship between name and args at the + // call site. Properly typing this would require a discriminated-union + // overload map for every Convex function, which adds significant + // complexity for little safety gain — the real type safety lives in the + // public method signatures of this class and the Convex handler + // definitions themselves. private async mutation, TResult>( name: MutationName, args: TArgs, diff --git a/executor/apps/server/src/index.ts b/executor/apps/server/src/index.ts index f3affcfe6..85e660a14 100644 --- a/executor/apps/server/src/index.ts +++ b/executor/apps/server/src/index.ts @@ -159,6 +159,14 @@ function parseMcpContext(url: URL): McpWorkspaceContext | undefined { return { workspaceId, actorId, clientId }; } +// ── MCP handler ── + +const handleMcp = async ({ request }: { request: Request }) => { + const url = new URL(request.url); + const context = parseMcpContext(url); + return await handleMcpRequest(service, request, context); +}; + // ── Elysia app ── const app = new Elysia() @@ -173,21 +181,9 @@ const app = new Elysia() // ── MCP (raw protocol passthrough) ── // Optional query params: ?workspaceId=...&actorId=... to bind workspace context. // When bound, run_code description includes sandbox tool inventory and input is simplified. - .post("/mcp", async ({ request }) => { - const url = new URL(request.url); - const context = parseMcpContext(url); - return await handleMcpRequest(service, request, context); - }) - .get("/mcp", async ({ request }) => { - const url = new URL(request.url); - const context = parseMcpContext(url); - return await handleMcpRequest(service, request, context); - }) - .delete("/mcp", async ({ request }) => { - const url = new URL(request.url); - const context = parseMcpContext(url); - return await handleMcpRequest(service, request, context); - }) + .post("/mcp", handleMcp) + .get("/mcp", handleMcp) + .delete("/mcp", handleMcp) // ── Health ── .get("/api/health", () => ({ @@ -208,11 +204,7 @@ const app = new Elysia() .get("/api/runtime-targets", () => service.listRuntimes()) // ── Tools ── - .get("/api/tools", async ({ query, set }) => { - if (!query.workspaceId) { - set.status = 400; - return { error: "workspaceId is required" }; - } + .get("/api/tools", async ({ query }) => { return await service.listTools({ workspaceId: query.workspaceId, actorId: query.actorId, @@ -227,11 +219,7 @@ const app = new Elysia() }) // ── Tool Sources ── - .get("/api/tool-sources", async ({ query, set }) => { - if (!query.workspaceId) { - set.status = 400; - return { error: "workspaceId is required" }; - } + .get("/api/tool-sources", async ({ query }) => { return await service.listToolSources(query.workspaceId); }, { query: t.Object({ @@ -240,10 +228,6 @@ const app = new Elysia() }) .post("/api/tool-sources", async ({ body, set }) => { - if (!body.workspaceId || !body.name || !body.type || !body.config) { - set.status = 400; - return { error: "workspaceId, name, type, and config are required" }; - } try { return await service.upsertToolSource({ id: body.id, @@ -269,10 +253,6 @@ const app = new Elysia() }) .delete("/api/tool-sources/:sourceId", async ({ params, query, set }) => { - if (!query.workspaceId) { - set.status = 400; - return { error: "workspaceId is required" }; - } const deleted = await service.deleteToolSource(query.workspaceId, params.sourceId); if (!deleted) { set.status = 404; @@ -285,11 +265,7 @@ const app = new Elysia() }) // ── Tasks ── - .get("/api/tasks", async ({ query, set }) => { - if (!query.workspaceId) { - set.status = 400; - return { error: "workspaceId is required" }; - } + .get("/api/tasks", async ({ query }) => { return await service.listTasks(query.workspaceId); }, { query: t.Object({ workspaceId: t.String() }), @@ -316,10 +292,6 @@ const app = new Elysia() }) .get("/api/tasks/:taskId", async ({ params, query, set }) => { - if (!query.workspaceId) { - set.status = 400; - return { error: "workspaceId is required" }; - } const task = await service.getTask(params.taskId, query.workspaceId); if (!task) { set.status = 404; @@ -332,10 +304,6 @@ const app = new Elysia() }) .get("/api/tasks/:taskId/events", async ({ params, query, set }) => { - if (!query.workspaceId) { - set.status = 400; - return { error: "workspaceId is required" }; - } const task = await service.getTask(params.taskId, query.workspaceId); if (!task) { set.status = 404; @@ -349,10 +317,6 @@ const app = new Elysia() // ── Approvals ── .get("/api/approvals", async ({ query, set }) => { - if (!query.workspaceId) { - set.status = 400; - return { error: "workspaceId is required" }; - } const status = query.status as ApprovalStatus | undefined; if (status === "pending") { @@ -373,11 +337,6 @@ const app = new Elysia() }) .post("/api/approvals/:approvalId", async ({ params, body, set }) => { - if (!body.workspaceId || (body.decision !== "approved" && body.decision !== "denied")) { - set.status = 400; - return { error: "workspaceId and decision are required" }; - } - const resolved = await service.resolveApproval( body.workspaceId, params.approvalId, @@ -402,21 +361,13 @@ const app = new Elysia() }) // ── Policies ── - .get("/api/policies", async ({ query, set }) => { - if (!query.workspaceId) { - set.status = 400; - return { error: "workspaceId is required" }; - } + .get("/api/policies", async ({ query }) => { return await service.listAccessPolicies(query.workspaceId); }, { query: t.Object({ workspaceId: t.String() }), }) - .post("/api/policies", async ({ body, set }) => { - if (!body.workspaceId || !body.toolPathPattern || !body.decision) { - set.status = 400; - return { error: "workspaceId, toolPathPattern, and decision are required" }; - } + .post("/api/policies", async ({ body }) => { return await service.upsertAccessPolicy({ id: body.id, workspaceId: body.workspaceId, @@ -439,21 +390,13 @@ const app = new Elysia() }) // ── Credentials ── - .get("/api/credentials", async ({ query, set }) => { - if (!query.workspaceId) { - set.status = 400; - return { error: "workspaceId is required" }; - } + .get("/api/credentials", async ({ query }) => { return await service.listCredentials(query.workspaceId); }, { query: t.Object({ workspaceId: t.String() }), }) .post("/api/credentials", async ({ body, set }) => { - if (!body.workspaceId || !body.sourceKey || !body.scope || !body.secretJson) { - set.status = 400; - return { error: "workspaceId, sourceKey, scope, and secretJson are required" }; - } if (body.scope === "actor" && (!body.actorId || body.actorId.trim().length === 0)) { set.status = 400; return { error: "actorId is required for actor-scoped credential" }; @@ -493,10 +436,6 @@ const app = new Elysia() set.status = 401; return { error: "Unauthorized internal call" }; } - if (!body.callId || !body.toolPath) { - set.status = 400; - return { error: "callId and toolPath are required" }; - } const call: ToolCallRequest = { runId: params.runId, From e0f5d9560166d5af34eff1880cb5dbf3a02c6cee Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sun, 8 Feb 2026 01:03:08 -0800 Subject: [PATCH 24/34] Fix Monaco editor layout, IntelliSense, and tool inventory sync - Increase editor height from 240px to 400px, remove overflow-hidden that clipped autocomplete/hover widgets, add fixedOverflowWidgets - Use versioned extra lib filenames and setEagerModelSync to fix IntelliSense completions not updating when tools load asynchronously - Trigger server-side tool refresh (GET /api/tools) after adding a tool source so workspace inventory syncs immediately - Add turbopack: {} to next.config.ts to silence Next.js 16 warning --- executor/apps/web/next.config.ts | 3 + .../apps/web/src/components/code-editor.tsx | 89 ++++++++++++------- .../apps/web/src/components/tools-view.tsx | 13 +-- 3 files changed, 65 insertions(+), 40 deletions(-) diff --git a/executor/apps/web/next.config.ts b/executor/apps/web/next.config.ts index da068aeee..8c4b26ba9 100644 --- a/executor/apps/web/next.config.ts +++ b/executor/apps/web/next.config.ts @@ -1,6 +1,9 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { + // Required for Turbopack (Next.js 16 default) – even if empty, + // it silences the "no turbopack config" warning. + turbopack: {}, async rewrites() { return [ { diff --git a/executor/apps/web/src/components/code-editor.tsx b/executor/apps/web/src/components/code-editor.tsx index 693e22b02..cab3f7404 100644 --- a/executor/apps/web/src/components/code-editor.tsx +++ b/executor/apps/web/src/components/code-editor.tsx @@ -1,8 +1,9 @@ "use client"; -import { useRef, useEffect, useCallback } from "react"; +import { useRef, useEffect } from "react"; import Editor, { type OnMount, type BeforeMount, type Monaco } from "@monaco-editor/react"; import type { ToolDescriptor } from "@/lib/types"; +import { cn } from "@/lib/utils"; // ── Generate TypeScript declarations for the tools proxy ── @@ -159,41 +160,41 @@ export function CodeEditor({ onChange, tools, className, - height = "240px", + height = "400px", }: CodeEditorProps) { const editorRef = useRef[0] | null>(null); const monacoRef = useRef(null); - const extraLibDisposables = useRef<{ dispose: () => void }[]>([]); - - const updateToolTypes = useCallback( - (monaco: Monaco) => { - // Dispose old extra libs - for (const d of extraLibDisposables.current) d.dispose(); - extraLibDisposables.current = []; - - const dts = generateToolsDts(tools); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const ts = (monaco.languages as any).typescript; - extraLibDisposables.current.push( - ts.javascriptDefaults.addExtraLib(BASE_ENVIRONMENT_DTS, "ts:env.d.ts"), - ts.javascriptDefaults.addExtraLib(dts, "ts:tools.d.ts"), - ); - }, - [tools], - ); + const envLibDisposable = useRef<{ dispose: () => void } | null>(null); + const toolsLibDisposable = useRef<{ dispose: () => void } | null>(null); + const toolsLibVersion = useRef(0); - // Update types when tools change + // Update types when tools change (or on first mount) useEffect(() => { - if (monacoRef.current) { - updateToolTypes(monacoRef.current); - } - }, [tools, updateToolTypes]); + const m = monacoRef.current; + if (!m) return; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const jsDefaults = (m.languages as any).typescript.javascriptDefaults; + + // Dispose previous tool type declarations + toolsLibDisposable.current?.dispose(); + + const dts = generateToolsDts(tools); + + // Use a versioned filename — disposing + re-adding the same filename + // can cause the TS worker to serve stale completions from its cache. + const version = ++toolsLibVersion.current; + toolsLibDisposable.current = jsDefaults.addExtraLib( + dts, + `file:///node_modules/@types/executor-tools/v${version}.d.ts`, + ); + }, [tools]); // Clean up on unmount useEffect(() => { return () => { - for (const d of extraLibDisposables.current) d.dispose(); + envLibDisposable.current?.dispose(); + toolsLibDisposable.current?.dispose(); }; }, []); @@ -229,8 +230,26 @@ export function CodeEditor({ lib: ["esnext"], }); - // Add our tools type declarations - updateToolTypes(monaco); + // Ensure the worker eagerly syncs models so that completions from + // addExtraLib declarations are available immediately. + ts.javascriptDefaults.setEagerModelSync(true); + + // Add stable environment declarations (once) + envLibDisposable.current?.dispose(); + envLibDisposable.current = ts.javascriptDefaults.addExtraLib( + BASE_ENVIRONMENT_DTS, + "file:///node_modules/@types/executor-env/index.d.ts", + ); + + // Add initial tool type declarations + // (will be replaced by useEffect when tools load from the API) + toolsLibDisposable.current?.dispose(); + const dts = generateToolsDts(tools); + const version = ++toolsLibVersion.current; + toolsLibDisposable.current = ts.javascriptDefaults.addExtraLib( + dts, + `file:///node_modules/@types/executor-tools/v${version}.d.ts`, + ); // Define the dark theme matching our UI monaco.editor.defineTheme("executor-dark", { @@ -292,10 +311,11 @@ export function CodeEditor({ }; return ( -
+
onChange(v ?? "")} @@ -303,6 +323,7 @@ export function CodeEditor({ onMount={handleMount} options={{ minimap: { enabled: false }, + fixedOverflowWidgets: true, fontSize: 13, lineHeight: 22, fontFamily: "var(--font-geist-mono), 'JetBrains Mono', monospace", @@ -324,15 +345,23 @@ export function CodeEditor({ showFields: true, showVariables: true, showModules: true, + showProperties: true, + showKeywords: true, preview: true, + shareSuggestSelections: true, }, quickSuggestions: { other: true, comments: false, strings: true, }, + acceptSuggestionOnCommitCharacter: true, parameterHints: { enabled: true, + cycle: true, + }, + inlineSuggest: { + enabled: true, }, wordWrap: "on", automaticLayout: true, diff --git a/executor/apps/web/src/components/tools-view.tsx b/executor/apps/web/src/components/tools-view.tsx index a944d9149..c7a14c0bf 100644 --- a/executor/apps/web/src/components/tools-view.tsx +++ b/executor/apps/web/src/components/tools-view.tsx @@ -315,17 +315,10 @@ function AddSourceDialog({ // Trigger a server-side tool refresh so the workspace tools inventory // is updated immediately (instead of waiting for the worker poll). + // Fetching the tools list forces the server to load external sources + // and sync the workspaceTools table. try { - await fetch(`/api/tool-sources`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - workspaceId: context.workspaceId, - name: sourceName, - type: sourceType, - config, - }), - }); + await fetch(`/api/tools?workspaceId=${encodeURIComponent(context.workspaceId)}`); } catch { // Server-side refresh is best-effort; the worker will eventually sync. } From d1a060dae95257dd7c8016a2c0d20716e06124c4 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sun, 8 Feb 2026 01:03:24 -0800 Subject: [PATCH 25/34] Extract Vercel sandbox string fragments, fix hardcoded constants - Create sandbox-fragments.ts with named string-builder functions for shared logic - Vercel sandbox runtime now composes from fragments instead of inline ~200 lines - APPROVAL_DENIED_PREFIX and TASK_TIMEOUT_MARKER now flow from execution-constants.ts - Add cross-reference comments between runtime-core.ts and sandbox-fragments.ts --- .../apps/server/src/runtimes/runtime-core.ts | 4 + .../server/src/runtimes/sandbox-fragments.ts | 224 ++++++++++++++++++ .../src/runtimes/vercel-sandbox-runtime.ts | 187 ++------------- 3 files changed, 242 insertions(+), 173 deletions(-) create mode 100644 executor/apps/server/src/runtimes/sandbox-fragments.ts diff --git a/executor/apps/server/src/runtimes/runtime-core.ts b/executor/apps/server/src/runtimes/runtime-core.ts index 0dca4c229..f92c7cdd1 100644 --- a/executor/apps/server/src/runtimes/runtime-core.ts +++ b/executor/apps/server/src/runtimes/runtime-core.ts @@ -1,3 +1,7 @@ +// NOTE: The Vercel sandbox runtime (vercel-sandbox-runtime.ts) contains a JS +// string version of similar logic, built from sandbox-fragments.ts. Changes to +// the core helpers here (formatArgs, createToolsProxy, console proxy, execution +// loop, result mapping) should be mirrored there. import { APPROVAL_DENIED_PREFIX, TASK_TIMEOUT_MARKER } from "../execution-constants"; import { Script, createContext } from "node:vm"; import type { diff --git a/executor/apps/server/src/runtimes/sandbox-fragments.ts b/executor/apps/server/src/runtimes/sandbox-fragments.ts new file mode 100644 index 000000000..b8a214d0c --- /dev/null +++ b/executor/apps/server/src/runtimes/sandbox-fragments.ts @@ -0,0 +1,224 @@ +/** + * String fragments for the Vercel sandbox runner script. + * + * The Vercel sandbox runs code in a remote microVM that cannot import local + * modules, so its entire runtime must be shipped as a self-contained JS string. + * These fragments mirror the TypeScript logic in runtime-core.ts — keep them + * in sync when making behavioural changes. + * + * Constants from execution-constants.ts are interpolated at build time so the + * sandbox never hardcodes magic strings. + */ + +import { + APPROVAL_DENIED_PREFIX, + TASK_TIMEOUT_MARKER, +} from "../execution-constants"; + +// --------------------------------------------------------------------------- +// Shared helper: formatArgs +// Mirror of: runtime-core.ts formatArgs() +// --------------------------------------------------------------------------- +export function buildFormatArgs(): string { + return ` +function formatArgs(args) { + return args.map((value) => { + if (typeof value === "string") return value; + try { + return JSON.stringify(value); + } catch { + return String(value); + } + }).join(" "); +}`; +} + +// --------------------------------------------------------------------------- +// HTTP helper (sandbox-only — runtime-core uses the adapter directly) +// --------------------------------------------------------------------------- +export function buildCallInternal(): string { + return ` +async function callInternal(path, payload) { + const response = await fetch(baseUrl + path, { + method: "POST", + headers: { + "content-type": "application/json", + ...(token ? { authorization: "Bearer " + token } : {}), + }, + body: JSON.stringify(payload), + }); + + const text = await response.text(); + let data = {}; + if (text.length > 0) { + try { + data = JSON.parse(text); + } catch { + data = { error: text }; + } + } + + if (!response.ok) { + const message = typeof data.error === "string" + ? data.error + : "Internal request failed (" + response.status + ")"; + throw new Error(message); + } + + return data; +}`; +} + +// --------------------------------------------------------------------------- +// Output helpers +// Mirror of: runtime-core.ts appendStdout / appendStderr +// --------------------------------------------------------------------------- +export function buildOutputHelpers(): string { + return ` +function emitOutput(stream, line) { + return callInternal("/internal/runs/" + encodeURIComponent(runId) + "/output", { + stream, + line, + timestamp: Date.now(), + }); +} + +function appendStdout(line) { + stdoutLines.push(line); + void emitOutput("stdout", line); +} + +function appendStderr(line) { + stderrLines.push(line); + void emitOutput("stderr", line); +}`; +} + +// --------------------------------------------------------------------------- +// Shared helper: createToolsProxy +// Mirror of: runtime-core.ts createToolsProxy() +// The sandbox version calls HTTP endpoints instead of adapter.invokeTool(). +// --------------------------------------------------------------------------- +export function buildCreateToolsProxy(): string { + return ` +function createToolsProxy(path = []) { + const callable = () => {}; + return new Proxy(callable, { + get(_target, prop) { + if (prop === "then") return undefined; + if (typeof prop !== "string") return undefined; + return createToolsProxy([...path, prop]); + }, + async apply(_target, _thisArg, args) { + const toolPath = path.join("."); + if (!toolPath) { + throw new Error("Tool path missing in invocation"); + } + + const data = await callInternal( + "/internal/runs/" + encodeURIComponent(runId) + "/tool-call", + { + callId: "call_" + randomUUID(), + toolPath, + input: args.length > 0 ? args[0] : {}, + }, + ); + + if (data.ok) { + return data.value; + } + + if (data.denied) { + throw new Error(${JSON.stringify(APPROVAL_DENIED_PREFIX)} + String(data.error || "Tool call denied")); + } + + throw new Error(String(data.error || "Tool call failed")); + }, + }); +}`; +} + +// --------------------------------------------------------------------------- +// Shared: sandbox + context setup, execution, and result mapping +// Mirror of: runtime-core.ts runCodeWithAdapter() execution & catch blocks +// --------------------------------------------------------------------------- +export function buildSandboxExecution(): string { + return ` +const tools = createToolsProxy(); +const consoleProxy = { + log: (...args) => appendStdout(formatArgs(args)), + info: (...args) => appendStdout(formatArgs(args)), + warn: (...args) => appendStderr(formatArgs(args)), + error: (...args) => appendStderr(formatArgs(args)), +}; + +const sandbox = Object.assign(Object.create(null), { + tools, + console: consoleProxy, + setTimeout, + clearTimeout, +}); +const context = vm.createContext(sandbox, { + codeGeneration: { + strings: false, + wasm: false, + }, +}); +const runnerScript = new vm.Script("(async () => {\\n\\"use strict\\";\\n" + userCode + "\\n})()"); + +const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error(${JSON.stringify(TASK_TIMEOUT_MARKER)})), requestTimeoutMs); +}); + +let result; +try { + const value = await Promise.race([ + Promise.resolve(runnerScript.runInContext(context, { timeout: Math.max(1, requestTimeoutMs) })), + timeoutPromise, + ]); + if (value !== undefined) { + appendStdout("result: " + formatArgs([value])); + } + + result = { + status: "completed", + stdout: stdoutLines.join("\\n"), + stderr: stderrLines.join("\\n"), + exitCode: 0, + durationMs: Date.now() - startedAt, + }; +} catch (error) { + const message = error instanceof Error ? error.message : String(error); + + if (message === ${JSON.stringify(TASK_TIMEOUT_MARKER)} || message.includes("Script execution timed out")) { + const timeoutMessage = "Execution timed out after " + requestTimeoutMs + "ms"; + appendStderr(timeoutMessage); + result = { + status: "timed_out", + stdout: stdoutLines.join("\\n"), + stderr: stderrLines.join("\\n"), + error: timeoutMessage, + durationMs: Date.now() - startedAt, + }; + } else if (message.startsWith(${JSON.stringify(APPROVAL_DENIED_PREFIX)})) { + const deniedMessage = message.slice(${JSON.stringify(APPROVAL_DENIED_PREFIX)}.length).trim(); + appendStderr(deniedMessage); + result = { + status: "denied", + stdout: stdoutLines.join("\\n"), + stderr: stderrLines.join("\\n"), + error: deniedMessage, + durationMs: Date.now() - startedAt, + }; + } else { + appendStderr(message); + result = { + status: "failed", + stdout: stdoutLines.join("\\n"), + stderr: stderrLines.join("\\n"), + error: message, + durationMs: Date.now() - startedAt, + }; + } +}`; +} diff --git a/executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts b/executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts index e6a17077d..f9adbbfa8 100644 --- a/executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts +++ b/executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts @@ -5,6 +5,13 @@ import type { SandboxExecutionResult, SandboxRuntime, } from "../types"; +import { + buildFormatArgs, + buildCallInternal, + buildOutputHelpers, + buildCreateToolsProxy, + buildSandboxExecution, +} from "./sandbox-fragments"; const RESULT_MARKER = "__EXECUTOR_RESULT__"; @@ -19,6 +26,8 @@ function stripTrailingSlash(value: string): string { } function buildRunnerScript(codeFilePath: string): string { + // Composed from sandbox-fragments.ts — see that file for the mirror + // relationship with runtime-core.ts. return ` import { readFile } from "node:fs/promises"; import { randomUUID } from "node:crypto"; @@ -42,179 +51,11 @@ const userCode = await readFile(${JSON.stringify(codeFilePath)}, "utf8"); const startedAt = Date.now(); const stdoutLines = []; const stderrLines = []; - -function formatArgs(args) { - return args.map((value) => { - if (typeof value === "string") return value; - try { - return JSON.stringify(value); - } catch { - return String(value); - } - }).join(" "); -} - -async function callInternal(path, payload) { - const response = await fetch(baseUrl + path, { - method: "POST", - headers: { - "content-type": "application/json", - ...(token ? { authorization: "Bearer " + token } : {}), - }, - body: JSON.stringify(payload), - }); - - const text = await response.text(); - let data = {}; - if (text.length > 0) { - try { - data = JSON.parse(text); - } catch { - data = { error: text }; - } - } - - if (!response.ok) { - const message = typeof data.error === "string" - ? data.error - : "Internal request failed (" + response.status + ")"; - throw new Error(message); - } - - return data; -} - -function emitOutput(stream, line) { - return callInternal("/internal/runs/" + encodeURIComponent(runId) + "/output", { - stream, - line, - timestamp: Date.now(), - }); -} - -function appendStdout(line) { - stdoutLines.push(line); - void emitOutput("stdout", line); -} - -function appendStderr(line) { - stderrLines.push(line); - void emitOutput("stderr", line); -} - -function createToolsProxy(path = []) { - const callable = () => {}; - return new Proxy(callable, { - get(_target, prop) { - if (prop === "then") return undefined; - if (typeof prop !== "string") return undefined; - return createToolsProxy([...path, prop]); - }, - async apply(_target, _thisArg, args) { - const toolPath = path.join("."); - if (!toolPath) { - throw new Error("Tool path missing in invocation"); - } - - const data = await callInternal( - "/internal/runs/" + encodeURIComponent(runId) + "/tool-call", - { - callId: "call_" + randomUUID(), - toolPath, - input: args.length > 0 ? args[0] : {}, - }, - ); - - if (data.ok) { - return data.value; - } - - if (data.denied) { - throw new Error("APPROVAL_DENIED:" + String(data.error || "Tool call denied")); - } - - throw new Error(String(data.error || "Tool call failed")); - }, - }); -} - -const tools = createToolsProxy(); -const consoleProxy = { - log: (...args) => appendStdout(formatArgs(args)), - info: (...args) => appendStdout(formatArgs(args)), - warn: (...args) => appendStderr(formatArgs(args)), - error: (...args) => appendStderr(formatArgs(args)), -}; - -const sandbox = Object.assign(Object.create(null), { - tools, - console: consoleProxy, - setTimeout, - clearTimeout, -}); -const context = vm.createContext(sandbox, { - codeGeneration: { - strings: false, - wasm: false, - }, -}); -const runnerScript = new vm.Script("(async () => {\\n\"use strict\";\\n" + userCode + "\\n})()"); - -const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => reject(new Error("TASK_TIMEOUT")), requestTimeoutMs); -}); - -let result; -try { - const value = await Promise.race([ - Promise.resolve(runnerScript.runInContext(context, { timeout: Math.max(1, requestTimeoutMs) })), - timeoutPromise, - ]); - if (value !== undefined) { - appendStdout("result: " + formatArgs([value])); - } - - result = { - status: "completed", - stdout: stdoutLines.join("\\n"), - stderr: stderrLines.join("\\n"), - exitCode: 0, - durationMs: Date.now() - startedAt, - }; -} catch (error) { - const message = error instanceof Error ? error.message : String(error); - - if (message === "TASK_TIMEOUT" || message.includes("Script execution timed out")) { - const timeoutMessage = "Execution timed out after " + requestTimeoutMs + "ms"; - appendStderr(timeoutMessage); - result = { - status: "timed_out", - stdout: stdoutLines.join("\\n"), - stderr: stderrLines.join("\\n"), - error: timeoutMessage, - durationMs: Date.now() - startedAt, - }; - } else if (message.startsWith("APPROVAL_DENIED:")) { - const deniedMessage = message.slice("APPROVAL_DENIED:".length).trim(); - appendStderr(deniedMessage); - result = { - status: "denied", - stdout: stdoutLines.join("\\n"), - stderr: stderrLines.join("\\n"), - error: deniedMessage, - durationMs: Date.now() - startedAt, - }; - } else { - appendStderr(message); - result = { - status: "failed", - stdout: stdoutLines.join("\\n"), - stderr: stderrLines.join("\\n"), - error: message, - durationMs: Date.now() - startedAt, - }; - } -} +${buildFormatArgs()} +${buildCallInternal()} +${buildOutputHelpers()} +${buildCreateToolsProxy()} +${buildSandboxExecution()} process.stdout.write(RESULT_MARKER + JSON.stringify(result) + "\\n"); `; From d86dc25744f0bb6dd5a7228aa19d92012478c25c Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sun, 8 Feb 2026 01:03:30 -0800 Subject: [PATCH 26/34] Route web UI task creation through executor HTTP API instead of direct Convex mutation Tasks created from the web UI now go through POST /api/tasks on the executor server, which runs typechecking, publishes events, and handles auto-execution instead of bypassing the service layer via direct Convex mutation. --- .../apps/web/src/components/tasks-view.tsx | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/executor/apps/web/src/components/tasks-view.tsx b/executor/apps/web/src/components/tasks-view.tsx index 54f8d7d7b..0e830c6fa 100644 --- a/executor/apps/web/src/components/tasks-view.tsx +++ b/executor/apps/web/src/components/tasks-view.tsx @@ -26,7 +26,7 @@ import { PageHeader } from "@/components/page-header"; import { TaskStatusBadge } from "@/components/status-badge"; import { useSession } from "@/lib/session-context"; import { useWorkspaceTools } from "@/hooks/use-workspace-tools"; -import { useMutation, useQuery } from "convex/react"; +import { useQuery } from "convex/react"; import { convexApi } from "@/lib/convex-api"; import type { TaskRecord, @@ -65,7 +65,6 @@ function TaskComposer() { const [runtimeId, setRuntimeId] = useState("local-bun"); const [timeoutMs, setTimeoutMs] = useState("15000"); const [submitting, setSubmitting] = useState(false); - const createTask = useMutation(convexApi.database.createTask); const runtimes = useQuery(convexApi.database.listRuntimeTargets, {}); const { tools } = useWorkspaceTools(context ?? null); @@ -74,16 +73,23 @@ function TaskComposer() { if (!context || !code.trim()) return; setSubmitting(true); try { - const task = await createTask({ - id: `task_${crypto.randomUUID()}`, - code, - runtimeId, - timeoutMs: parseInt(timeoutMs) || 15000, - workspaceId: context.workspaceId, - actorId: context.actorId, - clientId: context.clientId, + const response = await fetch("/api/tasks", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + code, + runtimeId, + timeoutMs: parseInt(timeoutMs) || 15000, + workspaceId: context.workspaceId, + actorId: context.actorId, + clientId: context.clientId, + }), }); - toast.success(`Task created: ${task.id}`); + const data = await response.json(); + if (!response.ok) { + throw new Error(data.error ?? "Failed to create task"); + } + toast.success(`Task created: ${data.taskId}`); } catch (err) { toast.error( err instanceof Error ? err.message : "Failed to create task", From c550527edc9a841d07723d3c7853e1c804f35fd0 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sun, 8 Feb 2026 01:13:02 -0800 Subject: [PATCH 27/34] Replace raw fetch() with typed Eden Treaty client in cli.test.ts and web UI - cli.test.ts: bootstrap, health, approvals, tasks all use Eden now - tasks-view.tsx: task creation uses typed executor.api.tasks.post() - Add executor-client.ts to web app with Eden Treaty client - Add @elysiajs/eden to web deps and @executor/server/* tsconfig path --- bun.lock | 7 ++ executor/apps/server/package.json | 1 + executor/apps/server/src/cli.test.ts | 73 ++++++++----------- executor/apps/web/package.json | 2 + .../apps/web/src/components/tasks-view.tsx | 26 +++---- executor/apps/web/src/lib/executor-client.ts | 9 +++ executor/apps/web/tsconfig.json | 3 +- 7 files changed, 64 insertions(+), 57 deletions(-) create mode 100644 executor/apps/web/src/lib/executor-client.ts diff --git a/bun.lock b/bun.lock index 774aa6dc1..2e7927c02 100644 --- a/bun.lock +++ b/bun.lock @@ -90,6 +90,7 @@ "zod": "^4.3.6", }, "devDependencies": { + "@elysiajs/eden": "^1.4.6", "@types/bun": "latest", "typescript": "^5.9.3", }, @@ -98,8 +99,10 @@ "name": "@executor/web", "version": "0.1.0", "dependencies": { + "@elysiajs/eden": "^1.4.6", "@executor/contracts": "workspace:*", "@monaco-editor/react": "^4.7.0", + "@tanstack/react-query": "^5.80.7", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "convex": "^1.31.7", @@ -845,6 +848,10 @@ "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "postcss": "^8.4.41", "tailwindcss": "4.1.18" } }, "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g=="], + "@tanstack/query-core": ["@tanstack/query-core@5.90.20", "", {}, "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.90.20", "", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw=="], + "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], diff --git a/executor/apps/server/package.json b/executor/apps/server/package.json index d47e2299f..910203241 100644 --- a/executor/apps/server/package.json +++ b/executor/apps/server/package.json @@ -21,6 +21,7 @@ "typecheck": "bunx tsc --noEmit" }, "devDependencies": { + "@elysiajs/eden": "^1.4.6", "@types/bun": "latest", "typescript": "^5.9.3" } diff --git a/executor/apps/server/src/cli.test.ts b/executor/apps/server/src/cli.test.ts index e25888928..9e54f1ba1 100644 --- a/executor/apps/server/src/cli.test.ts +++ b/executor/apps/server/src/cli.test.ts @@ -11,6 +11,8 @@ import { test, expect, afterAll } from "bun:test"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { ConvexClient } from "convex/browser"; +import { treaty } from "@elysiajs/eden"; +import type { App } from "./index"; import { api } from "../../../convex/_generated/api"; import { resolve } from "node:path"; @@ -21,6 +23,7 @@ const BASE = `http://127.0.0.1:${PORT}`; let proc: Bun.Subprocess | null = null; let convex: ConvexClient | null = null; +let executor: ReturnType>; afterAll(async () => { proc?.kill(); @@ -52,11 +55,9 @@ function connectMcp(workspaceId: string, actorId: string) { } async function bootstrap(): Promise<{ workspaceId: string; actorId: string; sessionId: string }> { - return fetch(`${BASE}/api/auth/anonymous/bootstrap`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({}), - }).then((r) => r.json()) as any; + const { data, error } = await executor.api.auth.anonymous.bootstrap.post({}); + if (error) throw error; + return data!; } /** Subscribe to Convex and resolve the instant a pending approval for `toolPath` appears. */ @@ -136,12 +137,11 @@ test("server starts", async () => { await waitForHealth(); - const health = (await fetch(`${BASE}/api/health`).then((r) => r.json())) as { - ok: boolean; - tools: number; - }; - expect(health.ok).toBe(true); - expect(health.tools).toBeGreaterThan(0); + executor = treaty(BASE); + + const { data: health } = await executor.api.health.get(); + expect(health!.ok).toBe(true); + expect(health!.tools).toBeGreaterThan(0); }, 45_000); // ── MCP ── @@ -220,16 +220,12 @@ test("approval-required tool blocks until approved", async () => { const approvalId = await waitForApproval(session.workspaceId, "admin.send_announcement"); // Approve it - const approveResp = await fetch(`${BASE}/api/approvals/${approvalId}`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - workspaceId: session.workspaceId, - decision: "approved", - reviewerId: "e2e-test", - }), + const { data: approveResult } = await executor.api.approvals({ approvalId }).post({ + workspaceId: session.workspaceId, + decision: "approved", + reviewerId: "e2e-test", }); - expect(approveResp.status).toBe(200); + expect(approveResult).toBeTruthy(); // Now the tool call completes const result = (await resultP) as { @@ -261,14 +257,10 @@ test("denied approval propagates failure", async () => { const approvalId = await waitForApproval(session.workspaceId, "admin.delete_data"); - await fetch(`${BASE}/api/approvals/${approvalId}`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - workspaceId: session.workspaceId, - decision: "denied", - reason: "too dangerous", - }), + await executor.api.approvals({ approvalId }).post({ + workspaceId: session.workspaceId, + decision: "denied", + reason: "too dangerous", }); const result = (await resultP) as { @@ -288,15 +280,13 @@ test("tasks are persisted and queryable via Convex subscription", async () => { const session = await bootstrap(); // Create via REST - const { taskId } = (await fetch(`${BASE}/api/tasks`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - code: "return 'persisted'", - workspaceId: session.workspaceId, - actorId: session.actorId, - }), - }).then((r) => r.json())) as { taskId: string }; + const { data: created, error: createError } = await executor.api.tasks.post({ + code: "return 'persisted'", + workspaceId: session.workspaceId, + actorId: session.actorId, + }); + if (createError) throw createError; + const taskId = created!.taskId as string; // Wait for completion via Convex subscription — no polling const task = await waitForTask(taskId); @@ -304,10 +294,11 @@ test("tasks are persisted and queryable via Convex subscription", async () => { expect(task.stdout).toContain("persisted"); // Verify it shows up in the list via REST - const tasks = (await fetch( - `${BASE}/api/tasks?workspaceId=${session.workspaceId}`, - ).then((r) => r.json())) as Array<{ id: string }>; - expect(tasks.some((t) => t.id === taskId)).toBe(true); + const { data: tasks, error: listError } = await executor.api.tasks.get({ + query: { workspaceId: session.workspaceId }, + }); + if (listError) throw listError; + expect(tasks!.some((t) => t.id === taskId)).toBe(true); }, 30_000); // ── Shutdown ── diff --git a/executor/apps/web/package.json b/executor/apps/web/package.json index f82c13598..da0db438d 100644 --- a/executor/apps/web/package.json +++ b/executor/apps/web/package.json @@ -9,6 +9,8 @@ "lint": "eslint" }, "dependencies": { + "@elysiajs/eden": "^1.4.6", + "@tanstack/react-query": "^5.80.7", "@executor/contracts": "workspace:*", "@monaco-editor/react": "^4.7.0", "class-variance-authority": "^0.7.1", diff --git a/executor/apps/web/src/components/tasks-view.tsx b/executor/apps/web/src/components/tasks-view.tsx index 0e830c6fa..e2f979938 100644 --- a/executor/apps/web/src/components/tasks-view.tsx +++ b/executor/apps/web/src/components/tasks-view.tsx @@ -25,6 +25,7 @@ import { Separator } from "@/components/ui/separator"; import { PageHeader } from "@/components/page-header"; import { TaskStatusBadge } from "@/components/status-badge"; import { useSession } from "@/lib/session-context"; +import { executor } from "@/lib/executor-client"; import { useWorkspaceTools } from "@/hooks/use-workspace-tools"; import { useQuery } from "convex/react"; import { convexApi } from "@/lib/convex-api"; @@ -73,21 +74,16 @@ function TaskComposer() { if (!context || !code.trim()) return; setSubmitting(true); try { - const response = await fetch("/api/tasks", { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - code, - runtimeId, - timeoutMs: parseInt(timeoutMs) || 15000, - workspaceId: context.workspaceId, - actorId: context.actorId, - clientId: context.clientId, - }), + const { data, error } = await executor.api.tasks.post({ + code, + runtimeId, + timeoutMs: parseInt(timeoutMs) || 15000, + workspaceId: context.workspaceId, + actorId: context.actorId, + clientId: context.clientId, }); - const data = await response.json(); - if (!response.ok) { - throw new Error(data.error ?? "Failed to create task"); + if (error) { + throw error; } toast.success(`Task created: ${data.taskId}`); } catch (err) { @@ -142,7 +138,7 @@ function TaskComposer() {
diff --git a/executor/apps/web/src/lib/executor-client.ts b/executor/apps/web/src/lib/executor-client.ts new file mode 100644 index 000000000..20f7bb748 --- /dev/null +++ b/executor/apps/web/src/lib/executor-client.ts @@ -0,0 +1,9 @@ +import { treaty } from "@elysiajs/eden"; +import type { App } from "@executor/server/src/index"; + +// Eden constructs URLs from base + route path. +// In the browser, use current origin so requests go through Next.js rewrites (/api/* → executor). +// On the server (SSR), hit the executor directly. +export const executor = treaty( + typeof window !== "undefined" ? window.location.origin : "http://localhost:4001" +); diff --git a/executor/apps/web/tsconfig.json b/executor/apps/web/tsconfig.json index cf9c65d3e..14585f2ad 100644 --- a/executor/apps/web/tsconfig.json +++ b/executor/apps/web/tsconfig.json @@ -19,7 +19,8 @@ } ], "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "@executor/server/*": ["../server/*"] } }, "include": [ From 1bf1a35f06b4b7b7e07af89a910c7318e0ad5abb Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sun, 8 Feb 2026 01:13:14 -0800 Subject: [PATCH 28/34] Replace useWorkspaceTools with TanStack Query + Eden, remove workspace tool sync - Rewrite useWorkspaceTools hook to use @tanstack/react-query + Eden - Add QueryClientProvider to app layout - Remove syncWorkspaceTools/listWorkspaceToolsForContext from database layer - Remove workspace tool inventory sync from service and worker - Tools are now fetched directly from the API, not materialized to Convex --- executor/apps/server/src/database.ts | 17 ------ executor/apps/server/src/service.ts | 27 ---------- executor/apps/server/src/worker.ts | 41 -------------- executor/apps/web/src/app/layout.tsx | 9 ++-- .../web/src/components/dashboard-view.tsx | 2 +- .../apps/web/src/components/tools-view.tsx | 31 +++-------- .../apps/web/src/hooks/use-workspace-tools.ts | 54 ++++++++++++++----- executor/apps/web/src/lib/query-client.ts | 9 ++++ executor/apps/web/src/lib/query-provider.tsx | 13 +++++ 9 files changed, 78 insertions(+), 125 deletions(-) create mode 100644 executor/apps/web/src/lib/query-client.ts create mode 100644 executor/apps/web/src/lib/query-provider.tsx diff --git a/executor/apps/server/src/database.ts b/executor/apps/server/src/database.ts index e0bc4636d..2a232b226 100644 --- a/executor/apps/server/src/database.ts +++ b/executor/apps/server/src/database.ts @@ -27,7 +27,6 @@ type MutationName = | "database:upsertAccessPolicy" | "database:upsertCredential" | "database:upsertToolSource" - | "database:syncWorkspaceTools" | "database:deleteToolSource" | "database:createTaskEvent"; @@ -46,7 +45,6 @@ type QueryName = | "database:resolveCredential" | "database:listToolSources" | "database:listToolSourceWorkspaceUpdates" - | "database:listWorkspaceToolsForContext" | "database:listTaskEvents"; export class ExecutorDatabase { @@ -225,21 +223,6 @@ export class ExecutorDatabase { return await this.query("database:listToolSourceWorkspaceUpdates", {}); } - async syncWorkspaceTools(params: { - workspaceId: string; - tools: ToolDescriptor[]; - }): Promise { - return await this.mutation("database:syncWorkspaceTools", params); - } - - async listWorkspaceToolsForContext(params: { - workspaceId: string; - actorId?: string; - clientId?: string; - }): Promise { - return await this.query("database:listWorkspaceToolsForContext", params); - } - async deleteToolSource(workspaceId: string, sourceId: string): Promise { return await this.mutation("database:deleteToolSource", { workspaceId, sourceId }); } diff --git a/executor/apps/server/src/service.ts b/executor/apps/server/src/service.ts index e4966d61a..c85b2a58e 100644 --- a/executor/apps/server/src/service.ts +++ b/executor/apps/server/src/service.ts @@ -131,7 +131,6 @@ export class ExecutorService { string, { signature: string; loadedAt: number; tools: Map } >(); - private readonly workspaceToolInventorySignatures = new Map(); private readonly workspaceToolLoadWarnings = new Map(); private readonly inFlightTaskIds = new Set(); private readonly autoExecuteTasks: boolean; @@ -491,9 +490,6 @@ export class ExecutorService { const signature = sourceSignature(workspaceId, sources); const cached = this.workspaceToolCache.get(workspaceId); if (cached && cached.signature === signature) { - if (this.workspaceToolInventorySignatures.get(workspaceId) !== signature) { - await this.syncWorkspaceToolInventory(workspaceId, cached.tools, signature); - } return cached.tools; } @@ -531,32 +527,9 @@ export class ExecutorService { tools: merged, }); - await this.syncWorkspaceToolInventory(workspaceId, merged, signature); - return merged; } - private async syncWorkspaceToolInventory( - workspaceId: string, - tools: Map, - signature: string, - ): Promise { - const inventory = [...tools.values()].map((tool) => ({ - path: tool.path, - description: tool.description, - approval: tool.approval, - source: tool.source, - argsType: tool.metadata?.argsType, - returnsType: tool.metadata?.returnsType, - })); - - await this.db.syncWorkspaceTools({ - workspaceId, - tools: inventory, - }); - this.workspaceToolInventorySignatures.set(workspaceId, signature); - } - private getToolDecision( task: TaskRecord, tool: ToolDefinition, diff --git a/executor/apps/server/src/worker.ts b/executor/apps/server/src/worker.ts index 671f70b5f..01926b4ae 100644 --- a/executor/apps/server/src/worker.ts +++ b/executor/apps/server/src/worker.ts @@ -51,7 +51,6 @@ const convexClient = new ConvexClient(convexUrl, { }); let draining = false; -let syncingToolInventories = false; async function drainQueue(trigger: string): Promise { if (draining) { @@ -79,30 +78,6 @@ async function drainQueue(trigger: string): Promise { } } -async function syncWorkspaceToolInventories(trigger: string): Promise { - if (syncingToolInventories) { - return; - } - - syncingToolInventories = true; - try { - const updates = await service.listToolSourceWorkspaceUpdates(); - if (updates.length === 0) { - return; - } - - console.log(`[worker] ${trigger}: syncing tool inventories for ${updates.length} workspace(s)`); - for (const update of updates) { - await service.refreshWorkspaceTools(update.workspaceId); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.error(`[worker] tool inventory sync failed: ${message}`); - } finally { - syncingToolInventories = false; - } -} - const queueSubscription = convexClient.onUpdate( api.database.listQueuedTaskIds, { limit: 1 }, @@ -116,31 +91,15 @@ const queueSubscription = convexClient.onUpdate( }, ); -const toolSourceSubscription = convexClient.onUpdate( - api.database.listToolSourceWorkspaceUpdates, - {}, - (updates) => { - if (updates.length > 0) { - void syncWorkspaceToolInventories("onUpdate"); - } - }, - (error) => { - console.warn(`[worker] tool source watcher error: ${error.message}`); - }, -); - const interval = setInterval(() => { void drainQueue("interval"); - void syncWorkspaceToolInventories("interval"); }, pollMs); await drainQueue("startup"); -await syncWorkspaceToolInventories("startup"); function shutdown(signal: string): void { console.log(`[worker] received ${signal}, shutting down...`); queueSubscription.unsubscribe(); - toolSourceSubscription.unsubscribe(); clearInterval(interval); void convexClient.close(); process.exit(0); diff --git a/executor/apps/web/src/app/layout.tsx b/executor/apps/web/src/app/layout.tsx index 62c4c4d01..8c444ed36 100644 --- a/executor/apps/web/src/app/layout.tsx +++ b/executor/apps/web/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { JetBrains_Mono, Inter } from "next/font/google"; import { Toaster } from "sonner"; import { AppConvexProvider } from "@/lib/convex-provider"; +import { QueryProvider } from "@/lib/query-provider"; import { SessionProvider } from "@/lib/session-context"; import "./globals.css"; @@ -31,9 +32,11 @@ export default function RootLayout({ className={`${inter.variable} ${jetbrainsMono.variable} antialiased`} > - - {children} - + + + {children} + + {/* Tools summary */} - {tools && tools.length > 0 && ( + {tools.length > 0 && ( diff --git a/executor/apps/web/src/components/tools-view.tsx b/executor/apps/web/src/components/tools-view.tsx index c7a14c0bf..1108961dd 100644 --- a/executor/apps/web/src/components/tools-view.tsx +++ b/executor/apps/web/src/components/tools-view.tsx @@ -304,24 +304,13 @@ function AddSourceDialog({ config: Record, ) => { if (!context) return; - // Write the tool source to the database await upsertToolSource({ workspaceId: context.workspaceId, name: sourceName, type: sourceType, config, }); - toast.success(`Source "${sourceName}" added`); - - // Trigger a server-side tool refresh so the workspace tools inventory - // is updated immediately (instead of waiting for the worker poll). - // Fetching the tools list forces the server to load external sources - // and sync the workspaceTools table. - try { - await fetch(`/api/tools?workspaceId=${encodeURIComponent(context.workspaceId)}`); - } catch { - // Server-side refresh is best-effort; the worker will eventually sync. - } + toast.success(`Source "${sourceName}" added — loading tools…`); }; const handlePresetAdd = async (preset: ApiPreset) => { @@ -763,11 +752,9 @@ export function ToolsView() { Inventory - {tools && ( - - {tools.length} - - )} + + {toolsLoading ? "…" : tools.length} + @@ -818,11 +805,9 @@ export function ToolsView() { Available Tools - {tools && ( - - {tools.length} - - )} + + {toolsLoading ? "…" : tools.length} + @@ -833,7 +818,7 @@ export function ToolsView() { ))}
) : ( - + )} diff --git a/executor/apps/web/src/hooks/use-workspace-tools.ts b/executor/apps/web/src/hooks/use-workspace-tools.ts index 912dd9c5e..45a3a6dae 100644 --- a/executor/apps/web/src/hooks/use-workspace-tools.ts +++ b/executor/apps/web/src/hooks/use-workspace-tools.ts @@ -1,7 +1,10 @@ "use client"; -import { useQuery } from "convex/react"; -import { convexApi } from "../lib/convex-api"; +import { useQuery as useTanstackQuery } from "@tanstack/react-query"; +import { useQuery as useConvexQuery } from "convex/react"; +import { convexApi } from "@/lib/convex-api"; +import { executor } from "@/lib/executor-client"; +import type { ToolDescriptor } from "@/lib/types"; interface WorkspaceContext { workspaceId: string; @@ -9,20 +12,45 @@ interface WorkspaceContext { clientId?: string; } +/** + * Fetches tool metadata from the server API (`GET /api/tools`) via Eden Treaty, + * cached and deduplicated by TanStack Query. + * + * Automatically re-fetches when the Convex `toolSources` subscription changes + * (the reactive value is included in the query key). + */ export function useWorkspaceTools(context: WorkspaceContext | null) { - const tools = useQuery( - convexApi.database.listWorkspaceToolsForContext, - context - ? { - workspaceId: context.workspaceId, - actorId: context.actorId, - clientId: context.clientId, - } - : "skip", + // Watch tool sources reactively so we invalidate when sources change + const toolSources = useConvexQuery( + convexApi.database.listToolSources, + context ? { workspaceId: context.workspaceId } : "skip", ); + const { data, isLoading } = useTanstackQuery({ + queryKey: [ + "workspace-tools", + context?.workspaceId, + context?.actorId, + context?.clientId, + toolSources, + ], + queryFn: async () => { + if (!context) return []; + const { data, error } = await executor.api.tools.get({ + query: { + workspaceId: context.workspaceId, + ...(context.actorId && { actorId: context.actorId }), + ...(context.clientId && { clientId: context.clientId }), + }, + }); + if (error) throw error; + return data as ToolDescriptor[]; + }, + enabled: !!context, + }); + return { - tools, - loading: !!context && tools === undefined, + tools: data ?? [], + loading: !!context && isLoading, }; } diff --git a/executor/apps/web/src/lib/query-client.ts b/executor/apps/web/src/lib/query-client.ts new file mode 100644 index 000000000..62c99fcab --- /dev/null +++ b/executor/apps/web/src/lib/query-client.ts @@ -0,0 +1,9 @@ +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30_000, // tools don't change that often + }, + }, +}); diff --git a/executor/apps/web/src/lib/query-provider.tsx b/executor/apps/web/src/lib/query-provider.tsx new file mode 100644 index 000000000..bc84263e9 --- /dev/null +++ b/executor/apps/web/src/lib/query-provider.tsx @@ -0,0 +1,13 @@ +"use client"; + +import { QueryClientProvider } from "@tanstack/react-query"; +import { queryClient } from "./query-client"; +import type { ReactNode } from "react"; + +export function QueryProvider({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} From 29e17c9083215ad2f72eb87956b4d5a7da27dfbf Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Sun, 8 Feb 2026 09:40:57 -0800 Subject: [PATCH 29/34] Add optional WorkOS auth flow with workspace switcher and icon upload --- bun.lock | 289 +++++++- dev.ts | 151 ++-- executor/apps/web/package.json | 2 + executor/apps/web/src/app/callback/route.ts | 19 + executor/apps/web/src/app/sign-in/route.ts | 23 + executor/apps/web/src/app/sign-out/route.ts | 20 + executor/apps/web/src/app/sign-up/route.ts | 23 + .../apps/web/src/components/app-shell.tsx | 299 ++++++-- .../apps/web/src/lib/auth-capabilities.ts | 2 + executor/apps/web/src/lib/convex-provider.tsx | 27 + executor/apps/web/src/lib/session-context.tsx | 328 ++++++++- executor/apps/web/src/proxy.ts | 20 + executor/convex/_generated/api.d.ts | 44 +- executor/convex/auth.config.ts | 23 + executor/convex/auth.ts | 687 ++++++++++++++++++ executor/convex/convex.config.ts | 8 + executor/convex/database.test.ts | 63 +- executor/convex/database.ts | 318 ++++---- executor/convex/http.ts | 8 + executor/convex/schema.ts | 84 ++- executor/package.json | 3 +- executor/packages/contracts/src/index.ts | 3 + 22 files changed, 2027 insertions(+), 417 deletions(-) create mode 100644 executor/apps/web/src/app/callback/route.ts create mode 100644 executor/apps/web/src/app/sign-in/route.ts create mode 100644 executor/apps/web/src/app/sign-out/route.ts create mode 100644 executor/apps/web/src/app/sign-up/route.ts create mode 100644 executor/apps/web/src/lib/auth-capabilities.ts create mode 100644 executor/apps/web/src/proxy.ts create mode 100644 executor/convex/auth.config.ts create mode 100644 executor/convex/auth.ts create mode 100644 executor/convex/convex.config.ts create mode 100644 executor/convex/http.ts diff --git a/bun.lock b/bun.lock index 2e7927c02..a097e27d6 100644 --- a/bun.lock +++ b/bun.lock @@ -69,6 +69,7 @@ "executor": { "name": "executor-monorepo", "dependencies": { + "@convex-dev/workos-authkit": "^0.1.6", "convex": "^1.31.7", }, "devDependencies": { @@ -99,10 +100,12 @@ "name": "@executor/web", "version": "0.1.0", "dependencies": { + "@convex-dev/workos": "^0.0.1", "@elysiajs/eden": "^1.4.6", "@executor/contracts": "workspace:*", "@monaco-editor/react": "^4.7.0", "@tanstack/react-query": "^5.80.7", + "@workos-inc/authkit-nextjs": "^2.13.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "convex": "^1.31.7", @@ -288,6 +291,12 @@ "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], + "@convex-dev/workos": ["@convex-dev/workos@0.0.1", "", { "dependencies": { "@workos-inc/authkit-react": "^0.11.0", "tsdown": "^0.12.7", "vitest": "^3.1.4" }, "peerDependencies": { "convex": "^1.25.4", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" } }, "sha512-8gZOgmcTitcKXwagdU69XC4Va6wMPFIhSqSOEaXmFXMEPtkMgxPW1dhJzrmm9UQ4iRgZsckjd2O5aQjUH7kHGQ=="], + + "@convex-dev/workos-authkit": ["@convex-dev/workos-authkit@0.1.6", "", { "dependencies": { "@convex-dev/workos": "^0.0.1", "@convex-dev/workpool": "^0.2.19", "@workos-inc/authkit-react": "^0.13.0", "@workos-inc/node": "^7.75.1", "convex-helpers": "^0.1.104", "type-fest": "^5.1.0" }, "peerDependencies": { "convex": "^1.29.3", "react": "^18.3.1 || ^19.0.0" } }, "sha512-o7FVJlMQcWTmYTflAnB5D0N+uWK9pEqQZ456xrP2kZN2KUpN236kf+oqFk0kIWzRvFMh/78V57N2LQjEH784eA=="], + + "@convex-dev/workpool": ["@convex-dev/workpool@0.2.19", "", { "peerDependencies": { "convex": ">=1.25.0 <1.35.0", "convex-helpers": "^0.1.94" } }, "sha512-U2KwYnsKILyxW1baWEhDv+ZtnL5FZbYFxBT5owQ0Lw/kseiudMZraA4clH+/6gowHSahWpkq4wndhcOfpfhuOA=="], + "@discordjs/builders": ["@discordjs/builders@1.13.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.33", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w=="], "@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="], @@ -562,6 +571,14 @@ "@openassistant/reacord": ["@openassistant/reacord@workspace:assistant/packages/reacord"], + "@oxc-project/types": ["@oxc-project/types@0.112.0", "", {}, "sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ=="], + + "@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.6.0", "", { "dependencies": { "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg=="], + + "@peculiar/json-schema": ["@peculiar/json-schema@1.1.12", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w=="], + + "@peculiar/webcrypto": ["@peculiar/webcrypto@1.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.3.8", "@peculiar/json-schema": "^1.1.12", "pvtsutils": "^1.3.5", "tslib": "^2.6.2", "webcrypto-core": "^1.8.0" } }, "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], @@ -584,6 +601,8 @@ "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="], + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], @@ -710,6 +729,84 @@ "@redocly/openapi-core": ["@redocly/openapi-core@1.34.6", "", { "dependencies": { "@redocly/ajv": "^8.11.2", "@redocly/config": "^0.22.0", "colorette": "^1.2.0", "https-proxy-agent": "^7.0.5", "js-levenshtein": "^1.1.6", "js-yaml": "^4.1.0", "minimatch": "^5.0.1", "pluralize": "^8.0.0", "yaml-ast-parser": "0.0.43" } }, "sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.3", "", { "os": "android", "cpu": "arm64" }, "sha512-0T1k9FinuBZ/t7rZ8jN6OpUKPnUjNdYHoj/cESWrQ3ZraAJ4OMm6z7QjSfCxqj8mOp9kTKc1zHK3kGz5vMu+nQ=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JWWLzvcmc/3pe7qdJqPpuPk91SoE/N+f3PcWx/6ZwuyDVyungAEJPvKm/eEldiDdwTmaEzWfIR+HORxYWrCi1A=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-MTakBxfx3tde5WSmbHxuqlDsIW0EzQym+PJYGF4P6lG2NmKzi128OGynoFUqoD5ryCySEY85dug4v+LWGBElIw=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jje3oopyOLs7IwfvXoS6Lxnmie5JJO7vW29fdGFu5YGY1EDbVDhD+P9vDihqS5X6fFiqL3ZQZCMBg6jyHkSVww=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm" }, "sha512-A0n8P3hdLAaqzSFrQoA42p23ZKBYQOw+8EH5r15Sa9X1kD9/JXe0YT2gph2QTWvdr0CVK2BOXiK6ENfy6DXOag=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-kWXkoxxarYISBJ4bLNf5vFkEbb4JvccOwxWDxuK9yee8lg5XA7OpvlTptfRuwEvYcOZf+7VS69Uenpmpyo5Bjw=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z03/wrqau9Bicfgb3Dbs6SYTHliELk2PM2LpG2nFd+cGupTMF5kanLEcj2vuuJLLhptNyS61rtk7SOZ+lPsTUA=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.3", "", { "os": "linux", "cpu": "x64" }, "sha512-iSXXZsQp08CSilff/DCTFZHSVEpEwdicV3W8idHyrByrcsRDVh9sGC3sev6d8BygSGj3vt8GvUKBPCoyMA4tgQ=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.3", "", { "os": "linux", "cpu": "x64" }, "sha512-qaj+MFudtdCv9xZo9znFvkgoajLdc+vwf0Kz5N44g+LU5XMe+IsACgn3UG7uTRlCCvhMAGXm1XlpEA5bZBrOcw=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.3", "", { "os": "none", "cpu": "arm64" }, "sha512-U662UnMETyjT65gFmG9ma+XziENrs7BBnENi/27swZPYagubfHRirXHG2oMl+pEax2WvO7Kb9gHZmMakpYqBHQ=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.3", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-gekrQ3Q2HiC1T5njGyuUJoGpK/l6B/TNXKed3fZXNf9YRTJn3L5MOZsFBn4bN2+UX+8+7hgdlTcEsexX988G4g=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-85y5JifyMgs8m5K2XzR/VDsapKbiFiohl7s5lEj7nmNGO0pkTXE7q6TQScei96BNAsoK7JC3pA7ukA8WRHVJpg=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.3", "", { "os": "win32", "cpu": "x64" }, "sha512-a4VUQZH7LxGbUJ3qJ/TzQG8HxdHvf+jOnqf7B7oFx1TEBm+j2KNL2zr5SQ7wHkNAcaPevF6gf9tQnVBnC4mD+A=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], + "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], "@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="], @@ -862,22 +959,62 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@types/accepts": ["@types/accepts@1.3.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ=="], + + "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], + "@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="], + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + + "@types/content-disposition": ["@types/content-disposition@0.5.9", "", {}, "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ=="], + + "@types/cookie": ["@types/cookie@0.5.4", "", {}, "sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA=="], + + "@types/cookies": ["@types/cookies@0.9.2", "", { "dependencies": { "@types/connect": "*", "@types/express": "*", "@types/keygrip": "*", "@types/node": "*" } }, "sha512-1AvkDdZM2dbyFybL4fxpuNCaWyv//0AwsuUk2DWeXyM1/5ZKm6W3z6mQi24RZ4l2ucY+bkSHzbDVpySqPGuV8A=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@types/express": ["@types/express@4.17.25", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "^1" } }, "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw=="], + + "@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.8", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA=="], + + "@types/http-assert": ["@types/http-assert@1.5.6", "", {}, "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw=="], + + "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], + "@types/keygrip": ["@types/keygrip@1.0.6", "", {}, "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ=="], + + "@types/koa": ["@types/koa@2.15.0", "", { "dependencies": { "@types/accepts": "*", "@types/content-disposition": "*", "@types/cookies": "*", "@types/http-assert": "*", "@types/http-errors": "*", "@types/keygrip": "*", "@types/koa-compose": "*", "@types/node": "*" } }, "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g=="], + + "@types/koa-compose": ["@types/koa-compose@3.2.9", "", { "dependencies": { "@types/koa": "*" } }, "sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA=="], + + "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="], + "@types/node": ["@types/node@20.19.33", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw=="], + "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], + + "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], + "@types/react": ["@types/react@19.2.13", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], "@types/react-reconciler": ["@types/react-reconciler@0.31.0", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-wa1KtV3SHKnWeLHfZFg/7nrPSe/Ejdn+qbanNX4V6i9I7C3ECKPfBnC1KM3ef58KFWEohLmTvoTH5XuEIKGX2g=="], + "@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="], + + "@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="], + "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], @@ -948,8 +1085,30 @@ "@vercel/sandbox": ["@vercel/sandbox@1.4.1", "", { "dependencies": { "@vercel/oidc": "^3.1.0", "async-retry": "1.3.3", "jsonlines": "0.1.1", "ms": "2.1.3", "picocolors": "^1.1.1", "tar-stream": "3.1.7", "undici": "^7.16.0", "xdg-app-paths": "5.1.0", "zod": "3.24.4" } }, "sha512-5jaNLv6QJ0112ZFhCv9hQDCV3IYogpbEn5LzMcY5E8TZsf5tF0avat2tBe7McOJvgVs0SDkuzjvGjUMKtTkjrA=="], + "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "@vitest/runner": ["@vitest/runner@3.2.4", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="], + + "@vitest/snapshot": ["@vitest/snapshot@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="], + + "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + + "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + "@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.7", "", {}, "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="], + "@workos-inc/authkit-js": ["@workos-inc/authkit-js@0.13.0", "", {}, "sha512-iA0Dt7D1BmY2/1s4oeA36W/aRt8/b5iyH6rP4AlgnjrcH2lUGkBgDXL76NXc0M7repkDQTMcJJ2NhCSo2rcWmg=="], + + "@workos-inc/authkit-nextjs": ["@workos-inc/authkit-nextjs@2.13.0", "", { "dependencies": { "@workos-inc/node": "^7.72.0", "iron-session": "^8.0.1", "jose": "^5.2.3", "path-to-regexp": "^6.2.2" }, "peerDependencies": { "next": "^13.5.9 || ^14.2.26 || ^15.2.3 || ^16", "react": "^18.0 || ^19.0.0", "react-dom": "^18.0 || ^19.0.0" } }, "sha512-ppxzhfakPumHPPggYSROaAlgxfS7viFMPmWPG76Tp6Rh9G7YqkBSp7xtvMtM6gXOFFMvvEJRcKEta6YHeercTQ=="], + + "@workos-inc/authkit-react": ["@workos-inc/authkit-react@0.11.0", "", { "dependencies": { "@workos-inc/authkit-js": "0.13.0" }, "peerDependencies": { "react": ">=17" } }, "sha512-67HFSxP4wXC8ECGyvc1yGMwuD5NGkwT2OPt8DavHoKAlO+hRaAlu9wwzqUx1EJrHht0Dcx+l20Byq8Ab0bEhlg=="], + + "@workos-inc/node": ["@workos-inc/node@7.82.0", "", { "dependencies": { "iron-session": "~6.3.1", "jose": "~5.6.3", "leb": "^1.0.0", "qs": "6.14.1" } }, "sha512-8h6XjIJf8nqNGYQMkWsjZ72WXMtzqrb4Azz39schXWoSRmwoK6tK+GpeAviJ9slddiJdOcp0Ht9/r+L6pGQMCg=="], + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], @@ -994,8 +1153,14 @@ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + "asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "assistant-monorepo": ["assistant-monorepo@workspace:assistant"], + "ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "^7.28.5", "pathe": "^2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="], + "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="], @@ -1024,6 +1189,8 @@ "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], + "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], "bowser": ["bowser@2.13.1", "", {}, "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw=="], @@ -1034,6 +1201,8 @@ "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], @@ -1042,6 +1211,8 @@ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -1054,10 +1225,16 @@ "caniuse-lite": ["caniuse-lite@1.0.30001769", "", {}, "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg=="], + "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], + "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], @@ -1092,6 +1269,8 @@ "convex": ["convex@1.31.7", "", { "dependencies": { "esbuild": "0.27.0", "prettier": "^3.0.0" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react", "react"], "bin": { "convex": "bin/main.js" } }, "sha512-PtNMe1mAIOvA8Yz100QTOaIdgt2rIuWqencVXrb4McdhxBHZ8IJ1eXTnrgCC9HydyilGT1pOn+KNqT14mqn9fQ=="], + "convex-helpers": ["convex-helpers@0.1.111", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "convex": "^1.25.4", "hono": "^4.0.5", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "typescript": "^5.5", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@standard-schema/spec", "hono", "react", "typescript", "zod"], "bin": { "convex-helpers": "bin.cjs" } }, "sha512-0O59Ohi8HVc3+KULxSC6JHsw8cQJyc8gZ7OAfNRVX7T5Wy6LhPx3l8veYN9avKg7UiPlO7m1eBiQMHKclIyXyQ=="], + "convex-test": ["convex-test@0.0.41", "", { "peerDependencies": { "convex": "^1.16.4" } }, "sha512-GPHeYFOi70n7UtW0eCEQFVhzl/+m8PvbWkDCbKpHLybI1MrScf4sVpGeM0cC2qmtxiduxa2nLPbehPalhh9oyQ=="], "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], @@ -1122,6 +1301,8 @@ "dedent": ["dedent@1.7.1", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg=="], + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], @@ -1136,6 +1317,8 @@ "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], @@ -1156,6 +1339,8 @@ "dotenv": ["dotenv@17.2.4", "", {}, "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw=="], + "dts-resolver": ["dts-resolver@2.1.3", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], @@ -1174,6 +1359,8 @@ "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], "enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="], @@ -1190,6 +1377,8 @@ "es-iterator-helpers": ["es-iterator-helpers@1.2.2", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "safe-array-concat": "^1.1.3" } }, "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w=="], + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], @@ -1240,6 +1429,8 @@ "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], @@ -1256,6 +1447,8 @@ "executor-monorepo": ["executor-monorepo@workspace:executor"], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], "express-rate-limit": ["express-rate-limit@8.2.1", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g=="], @@ -1316,6 +1509,8 @@ "fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="], @@ -1396,6 +1591,8 @@ "hono": ["hono@4.11.8", "", {}, "sha512-eVkB/CYCCei7K2WElZW9yYQFWssG0DhaDhVvr7wy5jJ22K+ck8fWW0EsLpB0sITUTvPnc97+rrbQqIr5iqiy9Q=="], + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], @@ -1424,6 +1621,10 @@ "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + "iron-session": ["iron-session@8.0.4", "", { "dependencies": { "cookie": "^0.7.2", "iron-webcrypto": "^1.2.1", "uncrypto": "^0.1.3" } }, "sha512-9ivNnaKOd08osD0lJ3i6If23GFS2LsxyMU8Gf/uBUEgm8/8CC1hrrCHFDpMo3IFbpBgwoo/eairRsaD3c5itxA=="], + + "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], + "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], @@ -1558,6 +1759,8 @@ "language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="], + "leb": ["leb@1.0.0", "", {}, "sha512-Y3c3QZfvKWHX60BVOQPhLCvVGmDYWyJEiINE3drOog6KCyN2AOwvuQQzlS3uJg1J85kzpILXIUwRXULWavir+w=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], @@ -1600,6 +1803,8 @@ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], + "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], "lucide-react": ["lucide-react@0.563.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA=="], @@ -1752,6 +1957,10 @@ "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], @@ -1790,8 +1999,14 @@ "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], + + "pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], + "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="], + "quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], "radix-ui": ["radix-ui@1.4.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-accessible-icon": "1.1.7", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-aspect-ratio": "1.1.7", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-context-menu": "2.2.16", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-form": "0.1.8", "@radix-ui/react-hover-card": "1.1.15", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-menubar": "1.1.16", "@radix-ui/react-navigation-menu": "1.2.14", "@radix-ui/react-one-time-password-field": "0.1.8", "@radix-ui/react-password-toggle-field": "0.1.3", "@radix-ui/react-popover": "1.1.15", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-progress": "1.1.7", "@radix-ui/react-radio-group": "1.3.8", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-scroll-area": "1.2.10", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-slider": "1.3.6", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-switch": "1.2.6", "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-toggle-group": "1.1.11", "@radix-ui/react-toolbar": "1.1.11", "@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-escape-keydown": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA=="], @@ -1814,6 +2029,8 @@ "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], @@ -1840,6 +2057,12 @@ "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + "rolldown": ["rolldown@1.0.0-rc.3", "", { "dependencies": { "@oxc-project/types": "=0.112.0", "@rolldown/pluginutils": "1.0.0-rc.3" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.3", "@rolldown/binding-darwin-arm64": "1.0.0-rc.3", "@rolldown/binding-darwin-x64": "1.0.0-rc.3", "@rolldown/binding-freebsd-x64": "1.0.0-rc.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.3", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.3", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.3", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.3", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.3", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.3", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.3", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.3", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.3" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Po/YZECDOqVXjIXrtC5h++a5NLvKAQNrd9ggrIG3sbDfGO5BqTUsrI6l8zdniKRp3r5Tp/2JTrXqx4GIguFCMw=="], + + "rolldown-plugin-dts": ["rolldown-plugin-dts@0.13.14", "", { "dependencies": { "@babel/generator": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/types": "^7.28.1", "ast-kit": "^2.1.1", "birpc": "^2.5.0", "debug": "^4.4.1", "dts-resolver": "^2.1.1", "get-tsconfig": "^4.10.1" }, "peerDependencies": { "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-beta.9", "typescript": "^5.0.0", "vue-tsc": "^2.2.0 || ^3.0.0" }, "optionalPeers": ["@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-wjNhHZz9dlN6PTIXyizB6u/mAg1wEFMW9yw7imEVe3CxHSRnNHVyycIX0yDEOVJfDNISLPbkCIPEpFpizy5+PQ=="], + + "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], @@ -1888,6 +2111,8 @@ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], @@ -1906,10 +2131,14 @@ "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + "state-local": ["state-local@1.0.7", "", {}, "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="], "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], @@ -1946,6 +2175,8 @@ "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], + "strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], @@ -1970,10 +2201,18 @@ "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], + + "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], + "tldts": ["tldts@7.0.22", "", { "dependencies": { "tldts-core": "^7.0.22" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-nqpKFC53CgopKPjT6Wfb6tpIcZXHcI6G37hesvikhx0EmUGPkZrujRyAjgnmp1SHNgpQfKVanZ+KfpANFt2Hxw=="], "tldts-core": ["tldts-core@7.0.22", "", {}, "sha512-KgbTDC5wzlL6j/x6np6wCnDSMUq4kucHNm00KXPbfNzmllCmtmvtykJHfmgdHntwIeupW04y8s1N/43S1PkQDw=="], @@ -1996,13 +2235,15 @@ "tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="], + "tsdown": ["tsdown@0.12.9", "", { "dependencies": { "ansis": "^4.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "debug": "^4.4.1", "diff": "^8.0.2", "empathic": "^2.0.0", "hookable": "^5.5.3", "rolldown": "^1.0.0-beta.19", "rolldown-plugin-dts": "^0.13.12", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.14", "unconfig": "^7.3.2" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-MfrXm9PIlT3saovtWKf/gCJJ/NQCdE0SiREkdNC+9Qy6UHhdeDPxnkFaBD7xttVUmgp0yUHtGirpoLB+OVLuLA=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "type-fest": ["type-fest@5.4.3", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA=="], "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], @@ -2022,6 +2263,12 @@ "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], + "unconfig": ["unconfig@7.4.2", "", { "dependencies": { "@quansync/fs": "^1.0.0", "defu": "^6.1.4", "jiti": "^2.6.1", "quansync": "^1.0.0", "unconfig-core": "7.4.2" } }, "sha512-nrMlWRQ1xdTjSnSUqvYqJzbTBFugoqHobQj58B2bc8qxHKBBHMNNsWQFP3Cd3/JZK907voM2geYPWqD4VK3MPQ=="], + + "unconfig-core": ["unconfig-core@7.4.2", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg=="], + + "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], + "undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], @@ -2054,8 +2301,16 @@ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + + "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], + + "vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="], + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + "webcrypto-core": ["webcrypto-core@1.8.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.3.13", "@peculiar/json-schema": "^1.1.12", "asn1js": "^3.0.5", "pvtsutils": "^1.3.5", "tslib": "^2.7.0" } }, "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], @@ -2066,6 +2321,8 @@ "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], @@ -2110,6 +2367,8 @@ "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "@convex-dev/workos-authkit/@workos-inc/authkit-react": ["@workos-inc/authkit-react@0.13.0", "", { "dependencies": { "@workos-inc/authkit-js": "0.14.0" }, "peerDependencies": { "react": ">=17" } }, "sha512-JhcPI9+I0MGOu7B33Gych76Xi7jKfKKfoZ4Mb6JPY9raeEqJQcnN4jMite2ynmMdmiZljIq7wkyf2WAsEU3SzQ=="], + "@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], @@ -2140,6 +2399,8 @@ "@redocly/openapi-core/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], @@ -2164,6 +2425,12 @@ "@vercel/sandbox/zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="], + "@workos-inc/authkit-nextjs/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], + + "@workos-inc/node/iron-session": ["iron-session@6.3.1", "", { "dependencies": { "@peculiar/webcrypto": "^1.4.0", "@types/cookie": "^0.5.1", "@types/express": "^4.17.13", "@types/koa": "^2.13.5", "@types/node": "^17.0.41", "cookie": "^0.5.0", "iron-webcrypto": "^0.2.5" }, "peerDependencies": { "express": ">=4", "koa": ">=2", "next": ">=10" }, "optionalPeers": ["express", "koa", "next"] }, "sha512-3UJ7y2vk/WomAtEySmPgM6qtYF1cZ3tXuWX5GsVX4PJXAcs5y/sV9HuSfpjKS6HkTL/OhZcTDWJNLZ7w+Erx3A=="], + + "@workos-inc/node/jose": ["jose@5.6.3", "", {}, "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g=="], + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -2196,18 +2463,20 @@ "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "iron-session/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "is-bun-module/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "msw/type-fest": ["type-fest@5.4.3", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA=="], - "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + "parse-json/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], @@ -2230,6 +2499,12 @@ "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], + + "tsdown/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "vitest/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -2244,6 +2519,8 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@convex-dev/workos-authkit/@workos-inc/authkit-react/@workos-inc/authkit-js": ["@workos-inc/authkit-js@0.14.0", "", {}, "sha512-Wt6rKoZdZnuAgiEBlvMDKhleU9DAwpyyy1SibacrnOWQgF4rcNlC+NTnHiiXu6OiOjQo6pPxhW42kCL+Gi+fjw=="], + "@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], "@dotenvx/dotenvx/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], @@ -2268,6 +2545,12 @@ "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "@workos-inc/node/iron-session/@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="], + + "@workos-inc/node/iron-session/cookie": ["cookie@0.5.0", "", {}, "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="], + + "@workos-inc/node/iron-session/iron-webcrypto": ["iron-webcrypto@0.2.8", "", { "dependencies": { "buffer": "^6" } }, "sha512-YPdCvjFMOBjXaYuDj5tiHst5CEk6Xw84Jo8Y2+jzhMceclAnb3+vNPP/CTtb5fO2ZEuXEaO4N+w62Vfko757KA=="], + "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], diff --git a/dev.ts b/dev.ts index 0d57f8eea..c8bf62fb7 100644 --- a/dev.ts +++ b/dev.ts @@ -4,22 +4,16 @@ * Usage: bun dev * * Starts: - * 1. Convex local backend (port 3210) - * 2. Convex function push (once) + * 1. Convex cloud dev function push (once) + * 2. Convex cloud dev function watcher * 3. Executor server (port 4001) * 4. Executor web UI (port 3002) * 5. Assistant server (port 3000) * 6. Discord bot - * 7. Convex function watcher (watches for schema/function changes) * * All processes are killed when this script exits (Ctrl+C). */ -import { existsSync } from "node:fs"; -import { readdir } from "node:fs/promises"; -import { join } from "node:path"; -import { homedir } from "node:os"; - const colors = { convex: "\x1b[36m", // cyan executor: "\x1b[33m", // yellow @@ -37,6 +31,33 @@ function prefix(name: ServiceName, line: string): string { const procs: Bun.Subprocess[] = []; +async function resolveExecutorConvexUrl(): Promise { + if (Bun.env.CONVEX_URL) { + return Bun.env.CONVEX_URL; + } + + const envFile = Bun.file("./executor/.env.local"); + if (!(await envFile.exists())) { + throw new Error("Missing executor/.env.local with CONVEX_URL"); + } + + const content = await envFile.text(); + const line = content + .split("\n") + .map((entry) => entry.trim()) + .find((entry) => entry.startsWith("CONVEX_URL=")); + + if (!line) { + throw new Error("CONVEX_URL not found in executor/.env.local"); + } + + const url = line.slice("CONVEX_URL=".length).trim(); + if (!url) { + throw new Error("CONVEX_URL is empty in executor/.env.local"); + } + return url; +} + function spawnService(name: ServiceName, cmd: string[], opts: { cwd?: string; env?: Record; @@ -77,101 +98,19 @@ function spawnService(name: ServiceName, cmd: string[], opts: { return proc; } -// ── Convex local backend ── - -async function findBackendBinary(): Promise { - const binDir = join(homedir(), ".cache", "convex", "binaries"); - if (!existsSync(binDir)) { - throw new Error(`No convex-local-backend found. Run: bun executor/apps/server/src/cli.ts start`); - } - const entries = await readdir(binDir); - for (const entry of entries) { - const path = join(binDir, entry, "convex-local-backend"); - if (existsSync(path)) return path; - } - throw new Error(`No convex-local-backend binary found in ${binDir}`); -} - -async function waitForBackend(url: string, timeoutMs = 15_000): Promise { - const start = Date.now(); - while (Date.now() - start < timeoutMs) { - try { - const resp = await fetch(`${url}/instance_name`); - if (resp.ok) return await resp.text(); - } catch { /* not ready */ } - await Bun.sleep(200); - } - throw new Error(`Convex backend did not start within ${timeoutMs}ms`); -} - -interface BackendConfig { - adminKey: string; - instanceSecret: string; - ports: { cloud: number; site: number }; -} - -async function readBackendConfig(instanceName: string): Promise { - for (const base of [ - join(homedir(), ".convex", "convex-backend-state"), - join(homedir(), ".convex", "anonymous-convex-backend-state"), - ]) { - const configPath = join(base, instanceName, "config.json"); - if (existsSync(configPath)) { - return await Bun.file(configPath).json(); - } - } - throw new Error(`No config found for instance: ${instanceName}`); -} +// ── Convex cloud deployment ── -const CONVEX_INSTANCE = "local-rhys_sullivan-executor"; - -async function startConvexBackend(): Promise<{ url: string; adminKey: string }> { - const CONVEX_PORT = 3210; - const url = `http://127.0.0.1:${CONVEX_PORT}`; - - // Check if already running - try { - const instanceName = await waitForBackend(url, 1000); - console.log(prefix("convex", `Backend already running: ${instanceName}`)); - const config = await readBackendConfig(instanceName); - return { url, adminKey: config.adminKey }; - } catch { /* not running, start it */ } - - const binary = await findBackendBinary(); - const config = await readBackendConfig(CONVEX_INSTANCE); - spawnService("convex", [ - binary, - "--port", String(CONVEX_PORT), - "--instance-name", CONVEX_INSTANCE, - "--instance-secret", config.instanceSecret, - ]); - - const instanceName = await waitForBackend(url); - console.log(prefix("convex", `Backend ready: ${instanceName}`)); - return { url, adminKey: config.adminKey }; -} - -async function pushConvexFunctions(backendUrl: string, adminKey: string): Promise { +async function pushConvexFunctions(): Promise { console.log(prefix("convex", "Pushing functions...")); - const envFile = join(import.meta.dir, "executor", ".env.executor-push"); - await Bun.write(envFile, [ - `CONVEX_SELF_HOSTED_URL=${backendUrl}`, - `CONVEX_SELF_HOSTED_ADMIN_KEY=${adminKey}`, - ].join("\n")); - - const cleanEnv = { ...Bun.env }; - delete cleanEnv.CONVEX_DEPLOYMENT; - const proc = Bun.spawn([ "bunx", "convex", "dev", "--once", "--typecheck", "disable", - "--env-file", envFile, ], { cwd: "./executor", stdout: "pipe", stderr: "pipe", - env: { ...cleanEnv, FORCE_COLOR: "1" }, + env: { ...Bun.env, FORCE_COLOR: "1" }, }); const stdout = await Bun.readableStreamToText(proc.stdout); @@ -205,26 +144,28 @@ if (!Bun.env.DISCORD_BOT_TOKEN) { console.log(`${colors.bot}[bot]${colors.reset} Skipped — no DISCORD_BOT_TOKEN set\n`); } -// 1. Convex backend (must be ready before anything else) -const convex = await startConvexBackend(); +// 1. Push functions (must complete before executor starts) +await pushConvexFunctions(); -// 2. Push functions (must complete before executor starts) -await pushConvexFunctions(convex.url, convex.adminKey); +const executorConvexUrl = await resolveExecutorConvexUrl(); +console.log(prefix("convex", `Using deployment URL: ${executorConvexUrl}`)); -// 3. Start Convex file watcher (repushes on changes, no backend management) +// 2. Start Convex file watcher (repushes on changes) spawnService("convex", [ "bunx", "convex", "dev", "--typecheck", "disable", - "--env-file", join(import.meta.dir, "executor", ".env.executor-push"), ], { cwd: "./executor", - env: (() => { const e = { ...Bun.env }; delete e.CONVEX_DEPLOYMENT; return e; })(), }); -// 4. Everything else in parallel +// 3. Everything else in parallel spawnService("executor", ["bun", "--hot", "apps/server/src/index.ts"], { cwd: "./executor", - env: { EXECUTOR_SERVER_AUTO_EXECUTE: "1" }, + env: { + EXECUTOR_SERVER_AUTO_EXECUTE: "1", + CONVEX_URL: executorConvexUrl, + EXECUTOR_CONVEX_URL: executorConvexUrl, + }, }); spawnService("web", ["bun", "run", "dev", "--", "-p", "3002"], { @@ -236,12 +177,18 @@ await Bun.sleep(2000); spawnService("assistant", ["bun", "run", "--cwd", "packages/server", "dev"], { cwd: "./assistant", + env: { + CONVEX_URL: executorConvexUrl, + }, }); if (Bun.env.DISCORD_BOT_TOKEN) { await Bun.sleep(1000); spawnService("bot", ["bun", "run", "--cwd", "packages/bot", "dev"], { cwd: "./assistant", + env: { + CONVEX_URL: executorConvexUrl, + }, }); } diff --git a/executor/apps/web/package.json b/executor/apps/web/package.json index da0db438d..361beeffc 100644 --- a/executor/apps/web/package.json +++ b/executor/apps/web/package.json @@ -9,8 +9,10 @@ "lint": "eslint" }, "dependencies": { + "@convex-dev/workos": "^0.0.1", "@elysiajs/eden": "^1.4.6", "@tanstack/react-query": "^5.80.7", + "@workos-inc/authkit-nextjs": "^2.13.0", "@executor/contracts": "workspace:*", "@monaco-editor/react": "^4.7.0", "class-variance-authority": "^0.7.1", diff --git a/executor/apps/web/src/app/callback/route.ts b/executor/apps/web/src/app/callback/route.ts new file mode 100644 index 000000000..245546de0 --- /dev/null +++ b/executor/apps/web/src/app/callback/route.ts @@ -0,0 +1,19 @@ +import type { NextRequest } from "next/server"; +import { redirect } from "next/navigation"; + +function getExternalOrigin(request: NextRequest) { + const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host"); + const proto = request.headers.get("x-forwarded-proto") ?? request.nextUrl.protocol.replace(":", ""); + if (host && proto) { + return `${proto}://${host}`; + } + return request.nextUrl.origin; +} + +export async function GET(request: NextRequest) { + if (!process.env.WORKOS_CLIENT_ID) { + return redirect("/"); + } + const { handleAuth } = await import("@workos-inc/authkit-nextjs"); + return handleAuth({ baseURL: getExternalOrigin(request) })(request); +} diff --git a/executor/apps/web/src/app/sign-in/route.ts b/executor/apps/web/src/app/sign-in/route.ts new file mode 100644 index 000000000..f9f0228c6 --- /dev/null +++ b/executor/apps/web/src/app/sign-in/route.ts @@ -0,0 +1,23 @@ +import type { NextRequest } from "next/server"; +import { redirect } from "next/navigation"; + +function getExternalOrigin(request: NextRequest) { + const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host"); + const proto = request.headers.get("x-forwarded-proto") ?? request.nextUrl.protocol.replace(":", ""); + if (host && proto) { + return `${proto}://${host}`; + } + return request.nextUrl.origin; +} + +export async function GET(request: NextRequest) { + if (!process.env.WORKOS_CLIENT_ID) { + return redirect("/"); + } + + const redirectUri = `${getExternalOrigin(request)}/callback`; + + const { getSignInUrl } = await import("@workos-inc/authkit-nextjs"); + const authorizationUrl = await getSignInUrl({ redirectUri }); + return redirect(authorizationUrl); +} diff --git a/executor/apps/web/src/app/sign-out/route.ts b/executor/apps/web/src/app/sign-out/route.ts new file mode 100644 index 000000000..a2c7ff53c --- /dev/null +++ b/executor/apps/web/src/app/sign-out/route.ts @@ -0,0 +1,20 @@ +import type { NextRequest } from "next/server"; +import { redirect } from "next/navigation"; + +function getExternalOrigin(request: NextRequest) { + const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host"); + const proto = request.headers.get("x-forwarded-proto") ?? request.nextUrl.protocol.replace(":", ""); + if (host && proto) { + return `${proto}://${host}`; + } + return request.nextUrl.origin; +} + +export async function GET(request: NextRequest) { + if (!process.env.WORKOS_CLIENT_ID) { + return redirect("/"); + } + + const { signOut } = await import("@workos-inc/authkit-nextjs"); + return signOut({ returnTo: `${getExternalOrigin(request)}/` }); +} diff --git a/executor/apps/web/src/app/sign-up/route.ts b/executor/apps/web/src/app/sign-up/route.ts new file mode 100644 index 000000000..7b23fbd16 --- /dev/null +++ b/executor/apps/web/src/app/sign-up/route.ts @@ -0,0 +1,23 @@ +import type { NextRequest } from "next/server"; +import { redirect } from "next/navigation"; + +function getExternalOrigin(request: NextRequest) { + const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host"); + const proto = request.headers.get("x-forwarded-proto") ?? request.nextUrl.protocol.replace(":", ""); + if (host && proto) { + return `${proto}://${host}`; + } + return request.nextUrl.origin; +} + +export async function GET(request: NextRequest) { + if (!process.env.WORKOS_CLIENT_ID) { + return redirect("/"); + } + + const redirectUri = `${getExternalOrigin(request)}/callback`; + + const { getSignUpUrl } = await import("@workos-inc/authkit-nextjs"); + const authorizationUrl = await getSignUpUrl({ redirectUri }); + return redirect(authorizationUrl); +} diff --git a/executor/apps/web/src/components/app-shell.tsx b/executor/apps/web/src/components/app-shell.tsx index 58f03426c..209353f25 100644 --- a/executor/apps/web/src/components/app-shell.tsx +++ b/executor/apps/web/src/components/app-shell.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, type FormEvent } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { @@ -10,13 +10,31 @@ import { Wrench, Menu, X, - Terminal, - RotateCcw, + ChevronsUpDown, + Plus, + Check, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Sheet, SheetContent, SheetTrigger, SheetTitle } from "@/components/ui/sheet"; import { Separator } from "@/components/ui/separator"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; import { useSession } from "@/lib/session-context"; +import { workosEnabled } from "@/lib/auth-capabilities"; import { cn } from "@/lib/utils"; const NAV_ITEMS = [ @@ -57,40 +75,236 @@ function NavLinks({ onClick }: { onClick?: () => void }) { ); } -function SessionInfo() { - const { context, loading, resetWorkspace } = useSession(); +function WorkspaceSelector({ inHeader = false }: { inHeader?: boolean }) { + const { context, mode, workspaces, switchWorkspace, creatingWorkspace, createWorkspace } = useSession(); + const [createOpen, setCreateOpen] = useState(false); + const [newWorkspaceName, setNewWorkspaceName] = useState(""); + const [newWorkspaceIcon, setNewWorkspaceIcon] = useState(null); + const [createError, setCreateError] = useState(null); + + const activeWorkspace = context + ? workspaces.find((workspace) => workspace.id === context.workspaceId) + : null; + const activeWorkspaceLabel = activeWorkspace?.name ?? (mode === "guest" ? "Guest Workspace" : "Select workspace"); + const activeWorkspaceInitial = (activeWorkspaceLabel[0] ?? "W").toUpperCase(); + + const openCreateWorkspace = () => { + setCreateError(null); + setNewWorkspaceName(""); + setNewWorkspaceIcon(null); + setCreateOpen(true); + }; - if (loading || !context) return null; + const handleCreateWorkspace = async (event?: FormEvent) => { + event?.preventDefault(); + + const trimmed = newWorkspaceName.trim(); + if (trimmed.length < 2) { + setCreateError("Workspace name must be at least 2 characters."); + return; + } + + try { + await createWorkspace(trimmed, newWorkspaceIcon); + setCreateError(null); + setNewWorkspaceName(""); + setNewWorkspaceIcon(null); + setCreateOpen(false); + } catch (cause) { + setCreateError(cause instanceof Error ? cause.message : "Failed to create workspace"); + } + }; + + const triggerClassName = inHeader + ? "h-full w-full justify-between rounded-none border-0 bg-transparent px-3 text-[12px] font-medium shadow-none hover:bg-accent/40" + : "h-8 w-full justify-between text-[11px]"; return ( -
- -
-
- - Session - + <> + + -
-
-
-
- - {context.workspaceId} + + {activeWorkspace?.iconUrl ? ( + {activeWorkspaceLabel} + ) : ( + + {activeWorkspaceInitial} + + )} + {activeWorkspaceLabel} + + + + + {mode === "workos" + ? workspaces.map((workspace) => { + const isActive = workspace.id === context?.workspaceId; + return ( + switchWorkspace(workspace.id)} + className="text-xs" + > + + {workspace.iconUrl ? ( + {workspace.name} + ) : ( + + {(workspace.name[0] ?? "W").toUpperCase()} + + )} + {workspace.name} + + {workspace.kind === "organization" ? "Org" : "Hobby"} + + + ); + }) + : ( + + Guest workspace + + )} + {mode === "workos" ? ( + <> + + + + New workspace + + + ) : null} + + + + + +
+ + Create workspace + + Create a new hobby workspace for your account. + + +
+ { + setCreateError(null); + setNewWorkspaceName(event.target.value); + }} + placeholder="Acme Labs" + maxLength={64} + /> + { + setCreateError(null); + setNewWorkspaceIcon(event.target.files?.[0] ?? null); + }} + /> + {newWorkspaceIcon ? ( +

+ Icon: {newWorkspaceIcon.name} +

+ ) : null} + {createError ? ( +

{createError}

+ ) : null} +
+ + + + +
+
+
+ + ); +} + +function SessionInfo() { + const { loading, isSignedInToWorkos, workosProfile } = useSession(); + const avatarUrl = workosProfile?.avatarUrl ?? null; + const avatarLabel = workosProfile?.name || workosProfile?.email || "User"; + const avatarInitial = (avatarLabel[0] ?? "U").toUpperCase(); + + if (loading) { + return ( +
+ + Loading session... +
+ ); + } + + return ( +
+ +
+ {isSignedInToWorkos ? ( +
+ {avatarUrl ? ( + {avatarLabel} + ) : ( +
+ {avatarInitial} +
+ )} +
+

{avatarLabel}

+ {workosProfile?.email ? ( +

{workosProfile.email}

+ ) : null} +
- - {context.actorId} - -
+ ) : ( +

Guest mode

+ )} + + {!isSignedInToWorkos && workosEnabled ? ( + + + + ) : null} + + {isSignedInToWorkos ? ( + + + + ) : null}
); @@ -99,11 +313,8 @@ function SessionInfo() { function Sidebar() { return (