npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@womp/spark-agent

v11.0.0

Published

Portable skills-based AI agent with local and durable chat transports for Womp

Readme

@womp/spark-agent

Portable AI agent for Womp applications and Kakapo scenes with model-authored JavaScript.

Spark Agent uses AI SDK 7 UIMessage and UIMessageChunk as its public chat protocol. Generated JavaScript uses the host's executor and capabilities. Native browser execution and isolated QuickJS execution use the same model loop. The host owns conversations, artifacts, authorization, and connections.

Release notes and migration guides are in CHANGELOG.md.

Requirements

  • Node.js 22.13 or newer
  • AI SDK 7
  • @ai-sdk/react 4 for React clients
npm install @womp/spark-agent

Package entry points

  • @womp/spark-agent — browser-safe model loop, two tools, persistence hooks, and shared contracts
  • @womp/spark-agent/browser — native JavaScript executor and local chat transport
  • @womp/spark-agent/capabilities — capability composition and skill contracts
  • @womp/spark-agent/kakapo — direct Kakapo runtime and scene capability
  • @womp/spark-agent/quickjs — optional isolated JavaScript executor
  • @womp/spark-agent/ui — browser-safe UI message types, schemas, and model catalog
  • @womp/spark-agent/client — SparkChatTransport and HTTP client types
  • @womp/spark-agent/server — run service, store interfaces, executor, and development stores
  • @womp/spark-agent/express — mountable Express 5 router

The root, /browser, /ui, /capabilities, and /client entry points do not import QuickJS, Kakapo, Express, or React at runtime.

Running in a host application

runSparkAgent accepts a configured model, the stored conversation (history, compacted once it is long) or model messages, an executor, a capability composition, and an artifact resolver. It exposes exactly read_skill and execute_javascript to the model. Skills carry instructions, declarations, and JavaScript examples. Kakapo skill declarations are generated from its typed API at build time and packaged as JavaScript strings.

System prompt and editor selection

Every host sends sparkSystemPrompt(skills): the shared agent prompt and the skills directory, with nothing host-specific, so it stays identical across a conversation. Hosts attach the editor selection to the user message it was sent with as a data-selection part ({ nodeIds }). It is not rendered as chat text; the model reads it as a short note on that message.

Capabilities

Compose only supported capabilities with composeCapabilities({ scene, account, ... }). Each capability supplies its skills and createApi(context); the returned API is bound under womp. Every invocation receives a cancellation signal and an awaited recordReceipt hook. Capabilities own their transactions and must check cancellation at their commit boundary.

For a real user interaction, call context.waitForUser(label, async signal => ...) outside mutation locks. The run stays active with requires_action progress and resumes the same invocation after input; the wait is excluded from the execution budget. Abort settles the wait and late answers cannot revive it. Custom executors must honor the optional host-owned userWait argument to execute.

Shared SDK scene adapters apply each edit directly to the scene and save it; nothing is journaled or replayed. An edit that fails after it began throws SceneOperationError, whose operation reports whether the edit was committed, rolled back or left uncertain.

Persistence and run lifecycle

Persist the user message before starting a run. Supply persistence.beforeToolCall, persistence.recordReceipt, and persistence.afterToolCall for awaited storage barriers. Supply persistence.saveMessage to save the canonical UI message at each model-step boundary and at termination. Spark awaits each checkpoint before the next model request; a failed barrier aborts the loop. Consume the run's stream while awaiting its result; a host run owner must keep consuming when a view detaches.

The default model-step limit is 100; reaching it returns status: "incomplete", and hosts must not restart automatically to bypass it. A generation that ends on any other non-stop reason fails the run with the unified and provider finish reasons in its error and diagnostics.

When a run is given its stored history, Spark compacts it before any model step once the conversation reaches the compaction threshold: earlier turns, and earlier steps of a long turn, are replaced by a summary recorded on the run's message as a data-compaction part. Nothing else shortens a request; one that is still over the model's context window fails with the provider's error.

Artifacts

Screenshot and file APIs return serializable artifact references. emitImage and emitFile resolve them through the host's artifact resolver. Keep references resolvable when reloading history, and pass createSparkTools(...) to AI SDK's convertToModelMessages to reconstruct multimodal results. emitImage(artifact, { inspect: false }) displays an image without adding its bytes to model input.

Local frontend

LocalSparkChatTransport accepts a host callback returning a SparkAgentRun from runSparkAgent. The callback receives the standard AI SDK chat request, including its messages and abort signal. It persists the initiating message, resolves the model and capabilities, and starts the shared loop. Keep ownership of the active run outside the React renderer. Local browser runs end when the page closes; reconnectToStream returns null.

Remote frontend

import { useChat } from "@ai-sdk/react";
import { SparkChatTransport } from "@womp/spark-agent/client";
import {
  sparkDataPartSchemas,
  sparkMessageMetadataSchema,
  type SparkUIMessage,
} from "@womp/spark-agent/ui";

const transport = new SparkChatTransport({ api: "/agent" });

const chat = useChat<SparkUIMessage>({
  id: conversationId,
  messages: initialMessages,
  transport,
  resume: true,
  messageMetadataSchema: sparkMessageMetadataSchema,
  dataPartSchemas: sparkDataPartSchemas,
});

Render message.parts. Standard AI SDK parts represent text, reasoning, files, tool inputs, tool outputs, approvals, and step boundaries. Spark adds typed data-console, data-artifact, data-run-status, data-step-metrics, data-execution-receipt, and data-tool-input-label parts.

An explicit Stop action must cancel the durable run before stopping local observation:

await transport.cancelRun(conversationId);
await chat.stop();

Closing or reloading the page only detaches the SSE subscriber. It does not cancel the run.

HTTP and SSE

SparkChatTransport hides the durable run lifecycle from useChat:

POST  /agent/conversations/:conversationId/messages
PATCH /agent/conversations/:conversationId/settings
POST  /agent/conversations/:conversationId/goal
GET   /agent/runs/:runId/stream?after=0
POST  /agent/runs/:runId/cancel

Conversation settings contain modelId and reasoningEffort; save them with transport.saveConversationSettings(id, settings). The goal endpoint accepts set, pause, resume, clear, and update_budget commands.

A goal starts from an ordinary message, when the agent sets one, and changes when the user asks in chat. A service with evaluateGoal gives the agent the goal tool. With set it starts a goal before building, when the request sets constraints a result can be measured against, from a title and the conditions, each marked measurable or left to the user's review. The goal starts at once, with that run as its first try and with 5 tries and 2,000 credits unless the call gives others; the service evaluates each measurable condition after every try and records the result on goal.conditions. change gives the goal new conditions or limits, continue has a stopped goal go on, and remove ends it; after a change the goal goes on in that turn. A set over an existing goal ends that one, and the call's result keeps its summary as ended. onGoalChange tells a host that counts a goal's spending when a run starts or stops working for one. A host leaves handleGoal out of the run it executes to withhold the tool.

The SSE response contains ordered persistence envelopes around standard AI SDK chunks:

type StoredSparkChunk = {
  sequence: number;
  conversationId: string;
  runId: string;
  createdAt: string;
  chunk: SparkUIMessageChunk;
};

The transport unwraps each envelope and returns the contained UIMessageChunk to useChat. Active assistant messages are replayed from sequence zero when reconnecting.

Embedding in an Express backend

Spark does not call listen() when imported. The host owns its HTTP server and mounts the router:

import { createSparkAgentRouter } from "@womp/spark-agent/express";
import { SparkAgentService, createSparkRunExecutor } from "@womp/spark-agent/server";

const service = new SparkAgentService({
  store,
  executeRun: createSparkRunExecutor({
    resolveModel,
    createHost,
  }),
});

app.use(
  "/agent",
  userAccess,
  createSparkAgentRouter({
    service,
    artifactStore,
    resolveOwnerId: (request) => request.user.id,
  }),
);

MemorySparkAgentStore and MemorySparkArtifactStore are development implementations. Production hosts must supply durable implementations backed by their database/object storage and must preserve the ordering contract of appendRunChunk(). Custom stores must also:

  • implement saveConversationSettings as a settings-only update, and preserve stored settings during saveConversation;
  • implement saveConversationGoal to support goals;
  • optionally implement releaseActiveRun(conversationId, runId, updatedAt) to clear the active run only if its ID still matches.

The built-in memory and SQLite stores implement all of these.

SparkAgentServiceOptions.endOrphanedRuns defaults to false. Enable it only when one process owns the store: a message or Stop then ends unfinished runs not held by that process.

Mesh Wompify

Mesh Wompify reconstructs a mesh that is already in the scene as editable primitives, through the shared mesh-wompify and scene-editing skills. The host imports the mesh itself and sends /wompify-mesh <node id> <tier> as the user's message, with tier simple, high or ultra. The skill sets the goal the reconstruction is refined toward, with the tier's credit and tries limits.

Standalone development server

The package includes a local server bound to 127.0.0.1:8787:

spark-agent

Configure the process with:

  • OPENAI_API_KEY
  • ANTHROPIC_API_KEY
  • GEMINI_API_KEY
  • KAKAPO_URL (defaults to ws://127.0.0.1:5502)
  • PORT (defaults to 8787)
  • SPARK_STORAGE_PATH (defaults to .spark/spark.sqlite)
  • SPARK_SCENE_IDENTITY (optional; set only when the value names the same durable scene across renderer reconnects)

The CLI loads .env from its working directory when present; copy .env.example to .env and add the provider key you intend to use. Process environment variables take precedence.

For repository development, yarn dev starts the Express server and Vite demo together. Open http://127.0.0.1:5173. When using a sibling kakapo-sdk dependency, run yarn build in ../kakapo-sdk before installing Spark dependencies.

The standalone server persists conversations, run streams, and screenshots in SQLite; only one local process can execute against a database. createStandaloneSparkAgentApp(options) is asynchronous: await it before listening, and call await app.dispose() on shutdown.

yarn dev:demo-account (or spark-agent --profile demo-account) adds a fictional account skill and two development material resources beside the real local Kakapo scene API. Its account reads identify themselves as fixtures; other account services report unavailable.

Run a prompt from the command line

With the Spark server already running:

yarn agent-run --prompt "Make a cube" --model gpt-5.6-luna --reasoning max
yarn agent-run --prompt "Model this object using both views" --image front.png --image side.jpg --output .spark/agent-runs/reference-model
  • --conversation <id> continues a thread.
  • --image <path> attaches a local PNG, JPEG, WebP or GIF reference; repeat it for more images.
  • --output <new-directory> saves result.json, transcript.json, inputs, and run artifacts. For local runs, use a fresh subdirectory of the git-ignored .spark/ directory.

Progress goes to stderr; one JSON result goes to stdout. Use yarn --silent agent-run when parsing stdout. Exit codes: 0 completed, 1 run/transport/export failure, 2 invalid arguments, 3 requires user action, 130 interrupted.

Measure intent routing

yarn intent-bench checks which route Spark takes for the requests in src/intent-bench-cases.ts: editable scene, OpenSCAD, Geometry Nodes, image, image edit, mesh, image-to-mesh, Flow, import, or a reply without acting. It needs no server or renderer. Each run gets Swan's skills over fixtures: an empty scene, the case's attachments and earlier turns, and Flow open or closed. Photo cases download their image from Womp's media host. The first call that would change the scene, generate, write a graph or import is recorded, and the run stops before it takes effect. Material edits do not decide a route, since Geometry Nodes prepares its materials in the scene first.

yarn intent-bench                                  # every case 3 times on gpt-6-luna, 24 at once
yarn intent-bench --only photo-only,F1 --repeat 10 --model gpt-6-astra
yarn intent-bench --list

It prints each case's routes and pass rate and writes report.json, with every run's skills, calls, deciding call and reply, under .spark/intent-bench/. It exits 1 when any run took a route the case does not expect. Model calls are paid; the full set costs about $0.10 on gpt-6-luna.

Run cost estimates

The chat displays estimated API token cost in USD after every completed model step, from the provider-reported usage and the rates in src/pricing.ts. Each data-step-metrics part stores its cost snapshot, so recorded runs are not repriced when rates change. Missing usage or unknown models are shown as unavailable, never priced as zero. Estimates exclude taxes, discounts, and scene hosting; the provider bill is authoritative.

Prompt caching is enabled for every listed model. It depends on an identical request prefix, which is why per-turn state travels in messages rather than the system prompt.

Execution security

The standalone host uses a fresh QuickJS runtime for each JavaScript execution. It has no Node.js or browser globals and receives only the composed capability APIs. Imports, filesystem access, network APIs, and host environment variables are not exposed. QuickJS limits memory, stack size, execution time, source size, host calls, and data crossing the host bridge. Both executors allow 100,000 host calls per execution by default (maxHostCalls overrides this in QuickJS).

Browser hosts can instead use native page execution. It runs in the page realm: it is not a sandbox and cannot interrupt a synchronous infinite loop.

QuickJS isolates generated code from the JavaScript host API; it is not an operating-system security boundary. Multi-tenant deployments must additionally isolate workers, authorize every Kakapo session and tool call, enforce quotas, and avoid passing privileged functions through the runtime bridge.

Validation

yarn build
yarn typecheck
yarn test
yarn web:build

Release procedure

Pushes to main run .github/workflows/publish.yml. Publication is driven by package.json, not Git tags: already-published versions are skipped; an unpublished stable version newer than npm's latest runs the release checks and is published automatically.

Versions follow MAJOR.MINOR.PATCH: major for breaking changes, minor for backward-compatible features, patch for backward-compatible fixes.

  1. Configure npm's trusted publisher once for @womp/spark-agent: organization wompxyz, repository spark-agent, workflow filename publish.yml, no environment, and allow direct npm publish. GitHub-hosted runners use OIDC; no publishing token is required.
  2. Bump package.json to an unused stable version, add its entry to CHANGELOG.md, and push the release commit to main. Prereleases are not published by this workflow.
  3. CI installs the frozen lockfile, runs typechecks, tests and the UI build, verifies the packed tarball's entry points and imports, then publishes and verifies registry integrity and latest.
  4. If publication reports an uncertain result, inspect npm before rerunning the workflow. Published versions cannot be replaced; corrections need a new version.
  5. In the consuming application, run yarn add @womp/spark-agent@<version> --exact and commit its manifest and lockfile together.

Layout

  • src/agent.ts — AI SDK stream and Spark tool orchestration
  • src/prompt.ts — shared system prompt
  • src/compaction.ts — conversation compaction before each model step
  • src/context-window.ts — model context windows and request size estimates
  • src/ui.ts — browser-safe protocol types and validation schemas
  • src/client.ts — durable ChatTransport implementation
  • src/server/ — service, storage contracts, Express adapter, artifacts, and standalone composition
  • src/runtime.ts — Kakapo scene connections and edit transactions
  • src/quickjs-executor.ts — isolated JavaScript execution and host bridge
  • src/womp-scene-api.ts — Womp-compatible Kakapo scene API
  • web/src/App.tsx — reference useChat<SparkUIMessage> application