tensorbuzz-api
v0.0.28
Published
JavaScript client for TensorBuzz error reporting with browser, Node, and Expo/React Native support.
Readme
tensorbuzz-api
JavaScript client for TensorBuzz error reporting with browser, Node, and Expo/React Native support.
Install
npm install tensorbuzz-apiDevelopment and local file-dependency builds
This package is written in TypeScript and ships from its compiled build/
output (main/types point at build/). Building requires the dev
dependencies (TypeScript) and is wired into the prepare lifecycle, so a normal
npm ci/npm install in this directory installs the toolchain and builds
automatically.
Sibling packages such as tensorbuzz-instance-control consume this package
through a local file:../tensorbuzz-api reference. When npm prepares that local
dependency it runs this package's prepare step without installing its
devDependencies, so a plain tsc build would fail with tsc: not found. To keep
the package installable from a clean checkout in that case while rejecting
manifest/lockfile drift in every packaging path, prepare
(scripts/prepare.mjs) runs npm ci --ignore-scripts before building. Use Node
24.x (the CI base image version).
Usage
import {BugReporting, debuggerInstance} from "tensorbuzz-api"
const bugReporting = new BugReporting({
authToken: "your-token",
projectId: "your-project-id",
runtimeEnvironment: "browser"
})
bugReporting.connect()You can add deployment/user context to every report:
bugReporting.collectEnvironment(() => ({
release: "2026.05.19",
runtime: "browser"
}))
bugReporting.collectParams(() => ({
accountId: currentAccount?.id,
userId: currentUser?.id
}))handleError() resolves with the parsed TensorBuzz response when the report is accepted. Parameters and environment values from collectParams(), collectEnvironment(), error.tensorbuzzParameters, and error.tensorbuzzEnvironment are sanitized before posting: sensitive keys are redacted, long strings and large arrays are bounded, and binary/upload values are summarized without including bytes.
Applications that collect request bodies or other raw diagnostic metadata can reuse the same boundary helper:
import {sanitizeBugReportPayload} from "tensorbuzz-api"
const parameters = sanitizeBugReportPayload({
body: requestBody,
headers: requestHeaders
})Project environment control
EnvironmentControlClient composes the public project-environment lifecycle,
durable automation-supervisor command, and resumable command-event APIs. A
service token must be scoped to the target account/project/environment and hold
the corresponding project-environment and agent_runs read/write scopes.
import {EnvironmentControlClient} from "tensorbuzz-api"
const control = new EnvironmentControlClient({
host: "https://server.tensorbuzz.com",
serviceToken: process.env.TENSORBUZZ_API_TOKEN
})
const ensured = await control.ensureEnvironmentInstance({
idempotencyKey: "deploy-task-42",
projectEnvironmentId: "environment-id",
reuse: true,
reuseKey: "preview-main"
})
const dispatched = await control.dispatchCommand({
idempotencyKey: "task-42-attempt-1",
projectEnvironmentId: "environment-id",
text: "Implement task 42"
})
const commandId = dispatched.projectEnvironmentAgentCommand.id
const stream = await control.streamCommandEvents({
afterSequence: loadCheckpoint(commandId),
commandId,
onEvent: (event, afterSequence) => {
consumeRedactedEvent(event)
saveCheckpoint(commandId, afterSequence)
}
})
const terminalStatus = await control.waitForCommand({commandId})
await stream.close()Persist the numeric afterSequence supplied to onEvent and provide it on the
next subscription. Reconnect delivery is at-least-once, and the client suppresses
events at or below its current checkpoint. Event callbacks may be asynchronous;
the client processes them in order and advances only after each callback succeeds.
If a callback fails, the subscription closes and stream.done rejects so the
caller can reconnect from its last durable checkpoint. Pending callback delivery
is capped at 1,000 events; overflow also fails closed for cursor-safe reconnect.
The CLI honors stdout backpressure before advancing each event. The service token is sent as an HTTP
header or transient WebSocket session metadata; it is never placed in command
payloads, URLs, cursor parameters, or output. Event and status content retains
the server's public redaction and size bounds. The client deliberately exposes
no provider selection, worker/terminal session, or raw shell controls.
Controlling error message and backtrace inclusion
By default every report includes the original error message and backtrace. To include those raw values only for trusted requests, pass shouldIncludeErrorDetails — either a boolean or a callback returning boolean | Promise<boolean>:
const bugReporting = new BugReporting({
authToken: "your-token",
projectId: "your-project-id",
shouldIncludeErrorDetails: (context) => context.environment?.isAdmin === true
})The callback receives the report/policy context: {error, errorClass, message, backtrace, url, httpMethod, requestBody, environment, parameters, hostname, runtimeEnvironment}. Surface trusted request/user metadata for decisions such as "current user is an admin" or "request IP is allowlisted" through the environment/parameters you already provide via collectEnvironment(), collectParams(), error.tensorbuzzEnvironment, or error.tensorbuzzParameters.
- Default is
true(include message and backtrace), preserving previous behavior. - When the policy denies (or is
false), the report omits bothmessageandbacktracewhile keeping the safe metadata needed for grouping and diagnostics (error_class,url,http_method,user_agent,parameters,environment,requestBody). - The callback may be synchronous or asynchronous; it is awaited before the report is serialized and sent.
- If the callback throws or rejects, evaluation fails closed (details are omitted) and never propagates out of the reporting path, so a broken policy cannot break the host application.
Request transport
By default, BugReporting uses XMLHttpRequest when available, otherwise it falls back to fetch. If neither is available, you must provide a request class.
import {BugReporting, NodeRequest} from "tensorbuzz-api"
const bugReporting = new BugReporting({
authToken: "your-token",
projectId: "your-project-id",
hostname: "api.example.com",
postUrl: "https://server.tensorbuzz.com/errors/reports",
runtimeEnvironment: "node",
RequestClass: NodeRequest
})Browser error listeners
import {BugReporting} from "tensorbuzz-api"
const bugReporting = new BugReporting({authToken: "your-token", projectId: "your-project-id"})
// Optional: enable source map parsing for script tags in web apps.
bugReporting.enableSourceMapsLoader()
bugReporting.connectOnError()
bugReporting.connectUnhandledRejection()Browser bug reporter (hardened)
Browser hosts that need stricter guarantees than connect() — an allowlisted wire payload, URL/text redaction, bounded symbolication, breadcrumbs, and Error-identity dedupe — use initializeBrowserBugReporting from the bug-reporting subpath:
import {initializeBrowserBugReporting} from "tensorbuzz-api/build/bug-reporting/browser-reporter.js"
import SourceMapsLoader from "tensorbuzz-api/build/bug-reporting/source-maps-loader.js"
const reporter = initializeBrowserBugReporting({
browser: window,
configuration: {
applicationEnvironment: "production",
authToken: "your-token",
projectId: "your-project-id",
postUrl: "https://server.tensorbuzz.com/errors/reports",
release: "2026.05.19"
},
sourceMapsLoader: new SourceMapsLoader({browser: window, loadScriptTagMaps: false})
})
if (reporter) {
window.addEventListener("beforeunload", () => reporter.disconnect())
}initializeBrowserBugReporting returns null when the host configuration does not opt in: applicationEnvironment must be "production" or "staging", authToken and projectId must be non-empty strings, the browser context must provide fetch and AbortController, the optional host runtime flag RAILS_ENV must not be "development" or "test" when defined, and a configured postUrl must be an https: URL without credentials, query, or fragment. Each call returns a fresh reporter that wires its own window error, unhandledrejection, popstate, hashchange, and document click listeners; the host owns one reporter per browser and calls disconnect() to remove them. Reports are sent with credentials: "omit", referrerPolicy: "no-referrer", redirect: "error", and a 5 second deadline that aborts the request and fails the report when it expires. The same Error object is reported once, cause chains are bounded, and failed reports release the dedupe so later reporting recovers.
The transport-boundary sanitizers are exported from tensorbuzz-api/build/bug-reporting/browser-sanitize.js:
import {safeText, safeUrl} from "tensorbuzz-api/build/bug-reporting/browser-sanitize.js"safeText strips URL credentials, redacts data:/blob:/javascript: URLs, and drops query/hash fragments except a stack frame's trailing :line(:col) coordinates. safeUrl returns only origin and pathname (bounded to 500 characters) for http(s) URLs, or null otherwise.
SourceMapsLoader browser context
SourceMapsLoader accepts optional constructor options for browser-safe map loading:
new SourceMapsLoader({
browser,
loadScriptTagMaps: false,
maxConcurrentLoads: 4,
maxCachedMaps: 64,
perMapTimeoutMs: 3000,
symbolicationBudgetMs: 3000
})browseris an injected context whose members (document,fetch,XMLHttpRequest,AbortController,setTimeout,clearTimeout) each fall back to the matchingglobalThismember.- When
browser.fetchis a function, maps are loaded with a privatefetch(GET,credentials: "omit",referrerPolicy: "no-referrer",redirect: "error") with per-map abort timeout and in-flight dedupe. Whenbrowseralso providesdocumentandlocation, maps are only resolved for https/packs/*.jsURLs on the page origin or the origins of the page's https/packs/script tags. - At most
maxConcurrentLoadsmaps load at once (further sources are skipped, not queued), new loads stop oncemaxCachedMapsmaps are cached, andloadSourceMaps(error)never waits longer thansymbolicationBudgetMs; pending loads keep running and their maps are reused by later errors. loadScriptTagMaps: falseloads only stack-referenced maps instead of every script tag.- Browser 1-based columns are converted to 0-based for source-map lookups and back for output, and unparseable stacks fall back to the raw stack instead of no frames.
- No-arg construction preserves the legacy XMLHttpRequest behavior exactly.
Node error listeners
import {BugReporting, NodeRequest} from "tensorbuzz-api"
const bugReporting = new BugReporting({
authToken: process.env.TENSORBUZZ_BUG_REPORT_AUTH_TOKEN,
hostname: process.env.TENSORBUZZ_BUG_REPORT_HOSTNAME,
projectId: process.env.TENSORBUZZ_BUG_REPORT_PROJECT_ID,
runtimeEnvironment: "node",
RequestClass: NodeRequest
})
bugReporting.connectNodeFatalHandlers()The Node fatal handlers are coordinated process-wide, including when multiple
reporter instances are connected. The first uncaught exception or unhandled
rejection gets one bounded reporting attempt, then the process terminates with
exit status 1 whether reporting succeeds, fails, or hangs. Nested fatal events
do not start more reports or extend the exit deadline. The existing
connectNodeUncaughtException() and connectNodeUnhandledRejection() methods
remain available for compatibility.
Instance control audits
import {InstanceControlAudits} from "tensorbuzz-api"
const instanceControlAudits = new InstanceControlAudits()
await instanceControlAudits.report({
emittedAt: new Date().toISOString(),
eventType: "action_cable.subscription.rejected",
instanceControlToken: "instance-control-token",
level: "error",
message: "ProjectEnvironmentTerminalSessionsChannel subscription rejected",
metadata: {attempt: 3},
postUrl: "https://server.tensorbuzz.com/errors/instance_control_audits",
projectEnvironmentInstanceId: "instance-id",
projectEnvironmentTerminalSessionId: "terminal-session-id",
projectId: "project-id"
})Expo / React Native error listeners
import {BugReporting} from "tensorbuzz-api"
const bugReporting = new BugReporting({authToken: "your-token", projectId: "your-project-id"})
bugReporting.connectExpoErrorHandlers()Direct reporting endpoint
Non-JavaScript runtimes can post JSON directly to /errors/reports. The error.parameters, error.environment, and top-level metadata fields can be JSON objects or JSON strings; TensorBuzz parses, sanitizes, and bounds them before storing the bug-report instance.
await fetch("https://server.tensorbuzz.com/errors/reports", {
body: JSON.stringify({
auth_token: "your-token",
projectId: "your-project-id",
hostname: "app.example.test",
runtimeEnvironment: "browser",
error: {
backtrace: ["Error: boom", "at checkout.js:10:4"],
environment: {release: "2026.05.19"},
error_class: "CheckoutError",
message: "Checkout failed",
parameters: {orderId: "ord_123"},
url: "https://app.example.test/checkout",
user_agent: navigator.userAgent
}
}),
headers: {"Content-Type": "application/json"},
method: "POST"
})CLI
npx tensorbuzz-api login
npx tensorbuzz-api build-logs --latest-build-group --failing
npx tensorbuzz-api environment-instance readiness --project-environment ENVIRONMENT_ID
npx tensorbuzz-api environment-instance ensure --project-environment ENVIRONMENT_ID --idempotency-key TASK_KEY
npx tensorbuzz-api environment-instance inspect --instance INSTANCE_ID
npx tensorbuzz-api environment-command dispatch --project-environment ENVIRONMENT_ID --idempotency-key TASK_KEY --text "Implement task 42"
npx tensorbuzz-api environment-command status --command-id COMMAND_ID
npx tensorbuzz-api environment-command wait --command-id COMMAND_ID --timeout-ms 300000
npx tensorbuzz-api environment-command events --command-id COMMAND_ID --after-sequence 41
npx tensorbuzz-api environment-command cancel --command-id COMMAND_ID
npx tensorbuzz-api webhook-watcher list
npx tensorbuzz-api webhook-watcher register-ci --thread-id THREAD_ID --chat-id CHAT_ID --project-id PROJECT_UUID --pr-number PR_NUMBER --build-group-id BUILD_GROUP_ID --prompt "Resume the task, inspect this completed build group, and continue from its verified result."For a durable CI + PR-review continuation, use the transactional continuation flow. It creates separate opaque build, review, and PR-lifecycle callback paths and preserves both persistent PR watchers when the exact head/build-group gate is replaced:
npx tensorbuzz-api webhook-watcher register-continuation --thread-id THREAD_ID --chat-id CHAT_ID --repository OWNER/NAME --project-id PROJECT_UUID --pr-number PR_NUMBER --build-group-id BUILD_GROUP_ID --head-sha HEAD_SHAThe chat ID must be an exact decimal Telegram ID (negative group IDs are
supported) and the thread/topic ID must be a positive decimal integer. Composite
values such as CHAT_ID:THREAD_ID, whitespace-corrupted values, and zero are
rejected before the API or route store is accessed. Before activation, the
command reads Project, BuildGroup, and Build association and calls the
BuildGroup.currentPullRequestGeneration read contract. That contract resolves
the live GitHub PR head through the linked GitHub App and requires exactly one
project/PR build group containing it, so a retained historical generation,
missing generation, or ambiguous mapping fails before provider or route-store
mutation. The supplied head and build-group are treated as event hints: the
returned GitHub-authoritative generation is re-read and verified before any
replacement activates. Reordered, duplicate, or skipped synchronize claims
therefore converge to the live head instead of moving monitoring backward.
The continuation's persistent review callback uses the deployed TensorBuzz
provider event set: github.pr_review.submitted,
github.pr_review_comment.created, github.pr_review_thread.changed,
github.pr_review.readiness_changed, and
github.pr_review.activity_after_merge. Exact provider readback must return that
same event set before registration becomes active.
Both new and idempotent registrations print one stable, secret-free JSON
receipt. It contains the exact binding, all three opaque route names and provider
subscription IDs, their event and lifecycle contracts, and proof claims showing
an accepted nonduplicate first delivery followed by an accepted same-ID
duplicate. Any continuation-flow failure is nonzero and includes
UNMONITORED — registration incomplete plus a stable safe reason code. Receipts
and errors never include the signing secret, token, signature, request headers,
payload body, or credential path.
The webhook-watcher command also supports list, remove, cleanup-orphans,
and test. list includes both legacy named watchers and opaque continuation
routes, including each continuation role, registration state, expected head,
owning project, provider subscription ID, and a conservative route
classification. Versioned continuation routes are schema-validated before any
mutation. Malformed legacy records remain read-only inventory: they are neither
coerced into the managed schema nor silently deleted. An exact active
pre-schema continuation pair is adopted in place only after both provider IDs,
scopes, lifecycles, and signed ingress proofs succeed; partial, conflicting, or
ambiguous legacy state fails UNMONITORED without creating replacements. An
exact legacy pair is completed with the separate lifecycle callback during the
same command. Route
readback also requires deliver_only: false; notification-only records cannot
claim an agent-capable continuation. Matching active strict routes must have
unambiguous build/review/lifecycle cardinality. Partial current-generation,
duplicate, or mixed-role state fails closed without provider mutation; exact
persistent review and lifecycle routes from an earlier generation remain
eligible for exact readback and reuse during the next build-generation
registration. Route
updates reload and compare the store immediately before atomic replacement, and
compensation removes or restores only routes still exactly owned by the failed
registration, so unrelated concurrent additions survive. Every remote create is
write-ahead recorded on its provisional route before activation; after a crash,
registration resolves a missing opaque provider ID by that route's random URL,
resumes missing roles, and compensates stale attempts without manual database
edits. Run cleanup with an explicit
cleanup-orphans --project-id PROJECT_UUID; it applies only to routes persisted
for that project. Before cleanup, legacy routes without stored project ownership
are read back from the provider: a route is backfilled only when that readback
proves exact ownership by the requested project. A backfilled completed one-shot
route can then be removed. Routes owned by other projects, plus legacy routes
with missing, ambiguous, malformed, or inaccessible provider readbacks, are
retained conservatively because a project-scoped token cannot prove they are
missing. Unrelated Hermes routes are also untouched. Set
TENSORBUZZ_API_TOKEN when using commands that access the
TensorBuzz API. The token used for continuation registration needs projects:read
and webhooks:manage; no broader scope is required. It
should be scoped to the single target project. Configure
HERMES_WEBHOOK_SIGNING_SECRET through the deployment secret store so
registration can safely bootstrap an empty HERMES_WEBHOOK_SUBS_PATH; the
secret is never recovered by restoring old route records. Existing installations
without that variable retain their route-level secret fallback for compatibility.
Continuation registration first persists all needed local routes as one
provisional attempt. After each createCallback, it records the returned opaque
ID on that route, reads the callback back through the same authenticated
frontend-model API, and requires exactly one active record matching the intended
project, PR, build group, events, callback URL, and lifecycle flags. It proves
all signed ingress routes before one atomic activation. Missing, ambiguous, or
unrelated records are rejected and compensated through the authenticated
destroy command; a retry can also recover an unrecorded ID from the attempt's
opaque callback URL.
If an otherwise reusable persistent review or lifecycle route returns 404
during public ingress proof, continuation registration first reads back the exact old provider ID,
deactivates that exact callback, marks the local route stale, and only then
creates a replacement. It never bulk-deletes ambiguous, provider-only,
local-only, no-topic, no-provider-ID, or cross-project records.
register-ci and register-review create agent-capable Hermes routes by default. Incoming TensorBuzz
events therefore start an agent continuation as well as targeting the configured
Telegram chat/thread. For meaningful autonomous workflows, pass a self-contained
--prompt that identifies the task and tells the agent what evidence to inspect
and what continuation to perform. Without --prompt, the watcher preserves its
concise payload-aware default (TensorBuzz: {buildGroup.name} —
{buildGroup.status} or the equivalent build text). Empty or whitespace-only
prompts are rejected before creating a subscription. Use --deliver-only
explicitly when the route should send only a Telegram notification and must not
start an agent continuation.
register-ci has a deliberately fixed contract: it requires the exact project,
PR, and build-group identifiers, subscribes only to build_group.completed, and
creates a one-time callback that auto-deletes after successful delivery. It does
not accept per-build events or lifecycle overrides.
register-review is separate and persistent. It requires the exact project and
PR plus an explicit comma-separated --events selection from
github.pr_review.submitted, github.pr_review_comment.created, and
github.pr_review_thread.changed. Review callbacks are never one-time or
auto-deleting. Both commands read the exact returned provider subscription ID
back and verify all non-secret scope and lifecycle fields before replacing the
local route.
webhook-watcher test --route-name NAME copies the selected route to a temporary,
non-one-shot test route, sends a correctly signed semantic delivery, replays the
same delivery ID to verify duplicate handling, and removes the test route. It
never sends synthetic traffic through the live CI route. It supports
build_group.completed, all five continuation review events, and all three PR
lifecycle events listed above, choosing the route's first event. Test traffic
includes tensorbuzzRegistrationProof: true; the receiver must authenticate it
and handle it as proof only, before notification, agent dispatch, or one-shot
consumption. Do not run this against a gateway whose proof-only handling has not
been verified in isolation.
Both test and registration proof require JSON objects with an exact
delivery_id match and status: "accepted" on each 2xx response. The first
claim must have boolean duplicate: false; its identical replay must have boolean
duplicate: true. Text containing "accepted", "ok", or "duplicate" is not proof.
The test command prints only this allowlisted receipt (never gateway bodies):
{"status":"ok","delivery_id":"<test-delivery-id>","signed":true,"first":{"accepted":true,"duplicate":false},"replay":{"accepted":true,"duplicate":true}}signed: true means the client signed the requests, not that a receiver's
signature enforcement has been independently proven. Registration uses
X-Webhook-Signature-V2 over timestamp.rawBody; test uses the actual outbound
raw-body HMAC contract with both TensorBuzz-native and GitHub-compatible headers.
A timestamped proof does not substitute for raw-body signature verification.
test does not read the provider or prove the complete monitoring binding.
The host must invoke an explicitly pinned, built package CLI through a thin
credentials/store adapter, not streamed source. Package tests with mocked fetch
responses are client-contract tests, not evidence that the real host sees dynamic
routes or suppresses proof dispatch. See the repository's
docs/webhook-callback-lifecycle.md
for receiver coverage, post-push gating, and approval-gated cutover/rollback.
login uses browser approval by default: it prints a TensorBuzz URL, you approve the request while signed in (including GitHub-authenticated users), and the CLI stores credentials automatically.
The CLI stores credentials in .tensorbuzz-api/credentials.json at the git root and uses the current branch by default.
Agent runs
agent-run uses the documented raw frontend-model API and a scoped service
token. Set the server and token in the environment so the secret does not enter
shell history:
export TENSORBUZZ_API_URL=https://server.tensorbuzz.com
export TENSORBUZZ_API_TOKEN=your-scoped-service-tokenThe token needs the scopes appropriate to each operation (commonly
projects:read, agent_runs:read, agent_runs:write, environments:write,
builds:read, and builds:write). The CLI also accepts --host and
--service-token; it never
prints the token. A serviceToken stored alongside host in
.tensorbuzz-api/credentials.json is supported for unattended installations.
Check readiness and start a planning-first run:
npx tensorbuzz-api agent-run readiness --project-environment ENVIRONMENT_ID --json
npx tensorbuzz-api agent-run start \
--project-environment ENVIRONMENT_ID \
--goal "Implement task TB-42" \
--source-system jira \
--source-project-id PROJECT_KEY \
--source-task-id TB-42 \
--source-url https://jira.example/browse/TB-42 \
--idempotency-key jira-TB-42start requires plan approval by default. Only pass
--plan-approval-policy auto when the automation is explicitly authorized to
execute without owner approval. Source metadata is accepted as a JSON object
with --source-metadata '{"priority":"high"}'.
Supervise the resulting run:
npx tensorbuzz-api agent-run status --run-id RUN_ID
npx tensorbuzz-api agent-run logs --run-id RUN_ID --limit 200 --json
npx tensorbuzz-api agent-run send-message --run-id RUN_ID --message "Approved" --decision approve_plan
npx tensorbuzz-api agent-run resume-from-source --run-id RUN_ID --source-event-id COMMENT_ID --source-task-id TB-42 --planning-revision 1 --message "Include CSV export" --decision request_plan_changes
npx tensorbuzz-api agent-run verify --run-id RUN_ID --branch-name agent/run-RUN_ID
npx tensorbuzz-api agent-run failure-context --run-id RUN_ID --json
npx tensorbuzz-api agent-run retry --run-id RUN_ID --reason "Verification failed" --failed-command "npm run typecheck" --prompt "Fix and verify again"
npx tensorbuzz-api agent-run cancel --run-id RUN_ID --reason "Superseded"Use the returned nextCursor as --cursor for the next log page. --limit is
bounded to 1–1000. --json writes only the command response as one compact JSON
line for reliable scripting; human output stays concise.
For manual token login, pass --auth-token (or TENSORBUZZ_API_AUTH_TOKEN):
npx tensorbuzz-api login --host https://tensorbuzz.example --repo owner/name --auth-token your-project-auth-tokenFor legacy password login, pass --password (and optionally --email, or TENSORBUZZ_API_EMAIL / TENSORBUZZ_API_PASSWORD):
npx tensorbuzz-api login --host https://tensorbuzz.example --repo owner/name --email [email protected] --password your-passwordPersistent review watcher
Register a persistent observer with an explicit supported review-event set:
npx tensorbuzz-api webhook-watcher register-review \
--thread-id THREAD_ID --chat-id CHAT_ID --project-id PROJECT_UUID \
--pr-number 42 \
--events github.pr_review.submitted,github.pr_review_comment.created