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

@xtrape/capsule-agent-node

v0.4.0

Published

Node.js Agent SDK for embedding Capsule Services into the Opstage governance loop.

Readme

@xtrape/capsule-agent-node

Node.js embedded Agent SDK for connecting Capsule Services to Opstage.

License: Apache-2.0 Status: Public Review Docs

@xtrape/capsule-agent-node supports the stable Node.js Embedded Agent SDK path for single Capsule Services. For multi-service OpHub functionality, use OpHub (Go runtime).

Package status: Xtrape Capsule is currently in Public Review before the v0.1.0 Public Preview release. This package is published under the public-review dist-tag. APIs, contracts, deployment instructions, and SDK interfaces may still change.

Install

During Public Review, install the prerelease package with:

pnpm add @xtrape/capsule-agent-node@public-review

The current Public Review version may change before v0.1.0.

During v0.3 development, this repository may use a local workspace, GitHub branch dependency, or 0.3.0-rc.x package for @xtrape/capsule-contracts-node until the final 0.3.0 package is published.

For this repository itself:

pnpm install
pnpm build

Minimal Example

This example matches the current SDK API: create a CapsuleAgent, configure providers with .health() / .configs(), register Actions with .action(), then call start().

import { CapsuleAgent } from "@xtrape/capsule-agent-node";

const agent = new CapsuleAgent({
  backendUrl: process.env.OPSTAGE_BACKEND_URL ?? "http://localhost:8080",
  registrationToken: process.env.OPSTAGE_REGISTRATION_TOKEN,
  tokenStore: { file: "./data/agent-token.txt" },
  // Optional. If omitted, the SDK derives agent identity from `service`.
  // Provide it explicitly when one Agent owns multiple Capsule Services on
  // the same host, or when you want a stable agent code that survives
  // service renames.
  agent: {
    code: "hello-capsule-agent",
    name: "Hello Capsule Agent",
    runtime: "nodejs",
  },
  service: {
    code: "hello-capsule",
    name: "Hello Capsule",
    version: "0.1.0",
    runtime: "nodejs",
    description: "Minimal Capsule Service example",
  },
});

agent.health(() => ({
  status: "UP",
  message: "ok",
  details: {
    uptimeSeconds: Math.floor(process.uptime()),
  },
}));

agent.configs(() => [
  {
    key: "HELLO_MODE",
    label: "Hello mode",
    type: "string",
    editable: false,
    sensitive: false,
    valuePreview: process.env.HELLO_MODE ?? "default",
  },
]);

agent.action({
  name: "echo",
  label: "Echo",
  description: "Return the submitted message.",
  dangerLevel: "LOW",
  requiresConfirmation: false,
  inputSchema: {
    type: "object",
    required: ["message"],
    properties: {
      message: { type: "string", default: "hello" },
    },
  },
  prepare: () => ({
    initialPayload: { message: "hello" },
    currentState: { service: "ready" },
  }),
  handler: async (payload) => ({
    success: true,
    data: { echo: payload.message },
  }),
});

await agent.start();

How Registration Works

  1. An operator creates a single-use Registration Token in Opstage CE.
  2. The service starts with registrationToken and calls the Agent registration API.
  3. Opstage returns an Agent ID and Agent token.
  4. The SDK stores the issued credentials in tokenStore.file as <agentId>:<agentToken>.
  5. Future restarts reuse the stored Agent token; the Registration Token is not needed again.

If the token file is lost or the Agent is revoked, create a new Registration Token and restart the service with it.

Service Manifest

The service option describes the Capsule Service reported to Opstage:

service: {
  code: "hello-capsule",          // stable unique service code
  name: "Hello Capsule",         // operator-facing display name
  description: "...",            // optional
  version: "0.1.0",              // service version
  runtime: "nodejs",             // nodejs | java | python | go | other
  manifest: { labels: { team: "ai" } }, // optional passthrough metadata
}

The SDK wraps this as a CapsuleService manifest with kind: "CapsuleService", schemaVersion: "1.0", and agentMode: "embedded".

Health Reporting

Use .health(provider) to report protocol-level health:

agent.health(async () => ({
  status: "UP", // UP | DEGRADED | DOWN | UNKNOWN
  message: "queue healthy",
  details: { queueDepth: 0 },
}));

The provider runs for heartbeats and service reports. Do not include secrets in details.

Agent health providers return protocol-level HealthStatus values: UP, DEGRADED, DOWN, UNKNOWN.

Opstage may derive an operator-facing effectiveStatus: HEALTHY, UNHEALTHY, STALE, OFFLINE.

Config Reporting

Use .configs(provider) to report observed config metadata:

agent.configs(() => [
  {
    key: "UPSTREAM_BASE_URL",
    type: "string",
    sensitive: false,
    editable: false,
    valuePreview: process.env.UPSTREAM_BASE_URL,
  },
  {
    key: "UPSTREAM_API_KEY",
    type: "secret",
    sensitive: true,
    editable: false,
    valuePreview: "[REDACTED]",
    secretRef: "env://UPSTREAM_API_KEY",
  },
]);

Configs are reported to Opstage for visibility; the current CE flow does not push config values from Opstage into the service.

Actions

Use .action() to expose an operator-triggerable operation:

agent.action({
  name: "reload-cache",
  label: "Reload Cache",
  dangerLevel: "MEDIUM",
  requiresConfirmation: true,
  timeoutSeconds: 30,
  handler: async () => {
    await reloadCache();
    return { success: true, message: "Cache reloaded." };
  },
});

Action metadata is published in the service report. The SDK intentionally strips runtime-only handler functions before reporting the Action Catalog.

Action Prepare / Execute

Opstage uses two Command types for actions:

  1. ACTION_PREPARE — created when the UI opens an Action panel. The SDK calls prepare() if present and returns dynamic form metadata such as inputSchema, initialPayload, and currentState.
  2. ACTION_EXECUTE — created when the operator confirms the Action. The SDK calls handler(payload) and reports the result.

If an Action has no prepare handler, the SDK returns a default prepare payload based on the action metadata and inputSchema defaults.

Command Polling

The embedded Agent starts three loops by default:

| Loop | Default | Purpose | | -------------- | ---------: | ---------------------------------- | | Heartbeat | 30 seconds | Agent liveness and latest health | | Service report | 60 seconds | Manifest, configs, actions, health | | Command poll | 5 seconds | Fetch and execute pending Commands |

You can override intervals:

new CapsuleAgent({
  // ...
  intervals: {
    heartbeatMs: 30_000,
    serviceReportMs: 60_000,
    commandPollMs: 5_000,
  },
});

For tests, set autoStartLoops: false and call start() to perform one registration/report/heartbeat/poll cycle.

Token Storage

The default token store is file-based via FileTokenStore. Store the token file in a private data directory:

new CapsuleAgent({
  // ...
  tokenStore: { file: "./data/agent-token.txt" },
});

Security recommendations:

  • chmod the containing directory so only the service user can read it;
  • never commit token files;
  • rotate by revoking the Agent in Opstage and registering a new one;
  • prefer secret managers for production wrappers when available.

Security Notes

  • Registration Tokens are single-use bootstrap credentials.
  • Agent Tokens are long-lived bearer tokens; treat them as secrets.
  • Actions are remote operational capabilities. Use dangerLevel, requiresConfirmation, and server-side validation in handlers.
  • Do not report raw passwords, API keys, cookies, OTPs, browser storage, or session files through health/config/action results.
  • Logs are redacted by the SDK where possible, but service handlers remain responsible for avoiding secret leakage.

API Reference

Main exports:

  • CapsuleAgent
  • FileTokenStore
  • AgentApiClient
  • AgentApiError, RegistrationError, AgentAuthError, NetworkError
  • AgentLogLevel, AgentLogRecord, StructuredLogger, ConsoleLogger (types)
  • SDK option and provider types from types.ts

Core methods:

| Method | Description | | --------------------------- | ------------------------------------------------------------- | | new CapsuleAgent(options) | Creates the embedded Agent. | | .health(provider) | Registers a health provider. | | .configs(provider) | Registers a config provider. | | .action(action) | Registers an operator Action. | | .start() | Registers if needed, reports service state, and starts loops. | | .stop() | Stops background loops. | | .runHealth() | Runs the configured health provider. |

Typed errors

Added in 0.2.0. Pre-0.2 code that catches AgentApiError still works because the new classes extend it.

The SDK surfaces four error classes so callers can branch on the failure mode without parsing messages:

| Class | When it's thrown | Should the caller retry? | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | | RegistrationError | /api/agents/register rejects the one-time token (expired, revoked, already used, malformed). Needs operator action — mint a fresh registration token. | No. | | AgentAuthError | Any non-register call returns 401/403. The agent token has been revoked or the agent disabled. Re-register before resuming. | No. | | NetworkError | fetch() threw before getting an HTTP response (DNS failure, ECONNREFUSED, abort, timeout). status === 0; carries an optional cause. | Yes — SDK already does. | | AgentApiError | Catch-all parent. Direct instances carry HTTP 4xx/5xx that don't fit the more specific cases (e.g. 409 conflict, 422 validation, 5xx server error). | Only on 5xx. |

The SDK's internal retry() loop already implements this policy: only NetworkError and 5xx AgentApiError are retried with exponential backoff; 4xx — including the two typed subclasses — surfaces immediately so the caller isn't waiting on a backoff that can never succeed.

import {
  CapsuleAgent,
  RegistrationError,
  AgentAuthError,
  NetworkError,
} from "@xtrape/capsule-agent-node";

try {
  await agent.start();
} catch (err) {
  if (err instanceof RegistrationError) {
    console.error("registration token is no longer valid:", err.code);
    process.exit(64);
  }
  if (err instanceof AgentAuthError) {
    console.error("agent token rejected — was the agent revoked?");
    process.exit(65);
  }
  if (err instanceof NetworkError) {
    console.error("could not reach Opstage:", err.message);
    process.exit(75);
  }
  throw err;
}

Structured logger

Added in 0.2.0. Existing zero-config callers (no logger / no structuredLogger) keep using the global console exactly as before.

By default the SDK calls console.debug / .info / .warn / .error. For JSON-line logging, pino, OpenTelemetry, or any in-house collector, pass a structuredLogger:

import { CapsuleAgent, type StructuredLogger } from "@xtrape/capsule-agent-node";

const collector: StructuredLogger = {
  log({ level, message, fields }) {
    process.stdout.write(JSON.stringify({ t: Date.now(), level, message, ...fields }) + "\n");
  },
};

const agent = new CapsuleAgent({
  // ...
  structuredLogger: collector,
});

Each record arrives as { level: "debug" | "info" | "warn" | "error", message: string, fields?: Record<string, unknown> }. The SDK normalizes the optional context before emitting:

  • Error instances become { error: { name, message, stack } } so they survive JSON.stringify.
  • Primitives become { value: <primitive> }.
  • Plain objects pass through unchanged.

In all cases the redactor runs over fields first, so tokens and secret-bearing keys never reach the collector. If both structuredLogger and logger are configured, structuredLogger wins.

Version Compatibility

| Package | Compatible with | | ---------------------------------- | ------------------------------------------------------------- | | @xtrape/[email protected] | Opstage CE 0.1.x and @xtrape/[email protected] |

Use matching minor versions across CE, Agent SDK, and Contracts during Public Review and Public Preview. The wire protocol may still change before v1.0.

Troubleshooting

Registration fails with 401 / REGISTRATION_TOKEN_INVALID

The token is single-use and short-lived.

  • Check the token has not already been consumed by an earlier successful start (registration tokens flip to USED on first use).
  • Check the token has not expired. Operators set expiresInSeconds when creating the token; the default in CE is short.
  • Check the token has not been revoked from the Opstage console.
  • Re-create a fresh token in the console and start the agent with it.

Agent reports ECONNREFUSED / cannot reach backend

  • Confirm OPSTAGE_BACKEND_URL resolves from inside the container or host where the agent runs. The default of http://localhost:8080 only works if the agent runs on the same host as Opstage.
  • If you sit behind a reverse proxy, point backendUrl at the proxy URL — not the internal Opstage container address.
  • Outbound HTTPS through corporate proxies: respect HTTPS_PROXY / NO_PROXY env vars (undici honors them via setGlobalDispatcher).

Agent token file is rejected on second start (401 / UNAUTHORIZED)

  • File permissions: the agent process must be able to read tokenStore.file. Run chmod 600 (owner read/write) and ensure the process owner matches.
  • File contents: the SDK writes <agentId>:<agentToken> plaintext. If the file exists but contains anything else, delete it and re-register with a fresh registration token.
  • Token revocation: an operator may have revoked the agent or the agent token from the console. Inspect the agent's status in Opstage; if it shows REVOKED or DISABLED, that's the cause.

Heartbeats succeed but action results never arrive

  • Confirm the action name registered with agent.action({ name: "..." }) matches the actionName the backend dispatches in the command. The SDK reports ACTION_HANDLER_NOT_FOUND if they differ.
  • Confirm start() is awaited and not called repeatedly — multiple CapsuleAgent instances on the same host with the same agent code will fight over heartbeats and command polls.
  • Check commandPollMs / commandPollIntervalSeconds is not absurdly large; default is 5s.

Documentation

  • Site: https://xtrape-com.github.io/xtrape-capsule-site/
  • Node embedded Agent guide: https://xtrape-com.github.io/xtrape-capsule-site/agents/node-embedded-agent
  • Action model: https://xtrape-com.github.io/xtrape-capsule-site/agents/action-model
  • Opstage CE: https://xtrape-com.github.io/xtrape-capsule-site/opstage-ce/overview

Contributing

See CONTRIBUTING.md for development workflow and PR checks. See SECURITY.md for vulnerability reporting and token/action safety guidance.

License

Apache-2.0. "Xtrape", "Xtrape Capsule", and "Opstage" are trademarks of their respective owners; the open-source license does not grant trademark rights.

v0.4 Experimental Capsule Bus Hook

The SDK exposes a minimal experimental publish hook into CE's built-in SQLite-backed in-process event-to-command router:

await agent.publishBusEvent({
  eventType: "demo.item.created",
  payload: { itemId: "item-1" },
});

The hook posts to Opstage CE as the registered embedded agent and defaults sourceServiceCode to the configured service code.

Typed errors for Bus failure modes:

  • BusDisabledErrorCAPSULE_BUS_DISABLED (404)
  • BusRateLimitedErrorBUS_RATE_LIMITED (429)
  • BusDepthExceededErrorBUS_DEPTH_EXCEEDED (422)

Capsule Bus is experimental in v0.4: not a standalone Bus Server, external broker, workflow engine, or service mesh API.