Skip to content

Releases: jmmana/Agent-Viewer

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 13 Oct 15:37
b9513a2

[0.8.0] - 2026-10-13

"Auditoria formal" (formal audit): a SHA-256 hash chain over every stored event (#105) with periodic Ed25519-signed
checkpoints (#387, #106), named ingestion keys with submitter identity (#107) and scoped read-only auditor keys
(#108), chain-compatible retention and redaction that tombstones a row without breaking the chain (#109), and an
offline-verifiable audit bundle with a standalone zero-dependency verifier script (#110). This release also ships
the audit guide, key management guide, threat model, migration notes from the single shared token, and a docs
hygiene check wired into CI (#111, split into sub-issues #408 to #411 per .claude/agent-policy.md's size rule).
The guarantee, in one sentence, from docs/threat-model.md: "a hash chain proves that stored history was not
altered after the fact; it does not prove that what an agent reported is true."

Upgrading

Purely additive: no breaking change to the event contract, the HTTP API or the embeddable library's props. The
hash chain, checkpoints, named/scoped keys and the audit bundle are all SQLite-only and opt-in by use; a
deployment running memory storage or the single shared AGENT_VIEWER_API_TOKEN keeps working exactly as before,
with every audit route reporting enabled: false or 409 audit_requires_chain in memory mode. See
docs/migrating-to-0.8.md for the practical runbook from the single shared token to
named keys, and docs/threat-model.md for what the chain proves and what it does not.

Known gaps

  • GET /api/v1/audit/verify/agent-viewer verify were not tombstone-aware until the fix below landed in this
    same release; any 0.8.0 deployment running an earlier pre-release build of this milestone should re-verify.
  • Closing an open SSE connection when its key is revoked or expires, submittedBy on SSE reconnect-replay
    frames, and submittedBy as a rollup/export dimension remain deferred (documented on issue #108).
  • Pricing-version digests in the audit bundle are not yet chain-anchored (documented in server/audit/bundle.ts
    and docs/audit-bundle.md, deferred to a follow-up of #83).
  • A full Spanish translation of docs/audit.md, docs/key-management.md/docs/api-keys.md and
    docs/threat-model.md is deferred; the Spanish README mirrors only the changed Security table rows.

Fixed

  • GET /api/v1/audit/verify/agent-viewer verify are now tombstone-aware (issue #408, v0.8.0 milestone): a row legitimately tombstoned by issue #109's retention/redaction routes rewrites its own event_json by design, so it never recomputes to its own stored hash again; before this fix server/audit/verify.ts's walk had no tombstone handling at all and reported hash_mismatch at the first such row, indistinguishable from real tampering, contradicting #109's and the 0.8.0 exit criterion's stated goal ("after a retention purge the chain still verifies through tombstones"). The walk now skips the content-hash recompute for a row with tombstoned_at IS NOT NULL (its link to the previous row is still checked normally, since links are never touched by a tombstone) and instead cross-checks it against the chained audit.retention.purged/audit.redaction.applied event its tombstone_audit_seq names: that event must exist, be of the right type, and have committed exactly this row's original hash/content_hash, or the new tombstone_invalid BreakKind is reported (a forged or missing tombstone is not a silent pass). Mirrors the logic scripts/verify-audit-bundle.mjs's REDACTION check (#401) already implemented and tests/audit-retention.test.mjs's tombstoneAwareWalk helper already pinned in tests. docs/audit-chain.md gains a "Tombstones do not break verification" section; docs/retention.md's "Known limits" entry for this gap is removed (closed, not reworded); docs/threat-model.md's "what the chain does not prove" bullet about this gap moves to "what the chain proves", corrected. New tests in tests/audit-verify.test.mjs: a clean tombstoned chain, a tombstone pointing nowhere, one pointing at a real but unrelated row, and one whose committing audit event records the wrong original hash.

Added

  • Migration guide from the single shared token (issue #409, sub-issue of #111, docs only): new
    docs/migrating-to-0.8.md covers the practical runbook for an operator running today's single shared
    AGENT_VIEWER_API_TOKEN: what changes (nothing functional) for the bootstrap token, the CLI's generated
    session token, the Docker api-token file (AGENT_VIEWER_TOKEN_FILE) and AGENT_VIEWER_WEBHOOK_SECRET;
    step by step from backing up the SQLite file through creating one named ingest key per client, an auditor
    key, switching clients over and retiring routine use of the bootstrap token; and the exact, code-verified
    shape in which rows stored before the hash chain existed are reported by GET /api/v1/audit/verify/
    agent-viewer verify (chain.unchained count plus a legacy_rows warning, never a separate "pre-chain"
    status, never silently counted as individually verified). Linked from README.md's and
    docs/README.es.md's Security tables and from docs/api-keys.md's bootstrap-token section. New
    tests/audit-migration.test.mjs: builds a SQLite database staged at schema version 16 (the "0.7.0 shape",
    the same staging pattern tests/audit-verify.test.mjs's legacy-rows case and
    tests/usage-rollup-migration.test.mjs already use), appends pre-chain events, starts the real 0.8.0 server
    on that file (running migrations 17-22) and asserts both documented claims over real HTTP. No server, CLI,
    SDK or library runtime code changed.

  • Docs hygiene check in CI: relative links, README linkage, em dash scan (issue #410, sub-issue of #111): new scripts/check-docs.mjs, npm run check:docs, wired into .github/workflows/ci.yml right after the existing token/api_key doc-content check. Three checks: (1) every relative markdown link and image in README.md, the top-level docs/*.md files (including docs/README.es.md) and .github/SECURITY.md resolves to a real file, and a #anchor resolves to a real heading in the target, using a from-scratch reimplementation of GitHub's own heading-slug algorithm (code spans stripped, non letter/digit/space/hyphen/underscore characters dropped, then each remaining whitespace character replaced with a hyphen individually, deliberately without trimming or collapsing runs, which is what makes emoji-prefixed headings slug the way GitHub actually renders them); (2) every 0.8.0 doc this issue names (docs/audit.md, docs/audit-chain.md, docs/audit-bundle.md, docs/api-keys.md, docs/retention.md, docs/threat-model.md) is linked from README.md; (3) no U+2014 in any file changed since the v0.7.0 tag (git diff v0.7.0..HEAD, added/copied/modified/renamed only), chosen over a repo-wide retroactive scan because the latter already failed on untouched pre-0.7.0 prose outside this issue's scope. Running this script against the current repo surfaced and fixed real pre-existing bugs it was built to catch: two em dashes in docs/retention.md and one each in docs/crew/evidence/{ambience-148,lamps-149}/README.md (all introduced since v0.7.0), four links to a stale docs/otlp.md#otlp-metrics-post-v1metrics anchor missing the heading's own -issue-73 suffix (README.md, docs/README.es.md, docs/integration.md, docs/otlp.md's own table of contents), two links to a stale docs/integration.md#reconnect-replay-and-resync anchor missing -issue-54 (docs/library.md, docs/library.es.md), and docs/audit.md itself, shipped with issue #388, never having been linked from README.md at all (added to the existing GET /api/v1/audit/verify row). New tests/check-docs.test.mjs: pure-function unit tests for the slug algorithm (including the two real, previously-fragile emoji/ampersand anchors above as fixtures) and link/em-dash checks against synthetic fixture repos, plus CLI invocations against the real repo. The CI workflow needs no extra checkout change: the script resolves v0.7.0 itself, fetching just that one tag shallowly from origin if the default shallow checkout does not already have it.

  • Threat model for the 0.8.0 formal-audit work (issue #111, first sub-issue, docs only): new docs/threat-model.md states, in one place, what the hash chain (#105), signed checkpoints (#387), chain verification (#106), named and scoped keys (#107/#108), chain-compatible retention/redaction (#109) and the offline-verifiable audit bundle (#110) actually guarantee: the assets at risk, every actor from a compromised agent to an operator holding both the database and the signing key, explicit "what the chain proves" and "what it does not" sections, memory-mode behavior (every audit route refuses rather than returning a degraded result), bundle confidentiality (an exported bundle is not redacted, unlike GET /api/v1/usage/export), the invoice-cross-check rules (unknown is never zero, estimated is never mixed with reported), residual risks and recommended mitigations, and explicit non-goals (not tamper-proof against a fully privileged operator, not a compliance certification, not provider billing). Cross-linked from README.md/docs/README.es.md's Security tables, .github/SECURITY.md (which also gets a corrected "Data exfiltration surface" paragraph reflecting shipped scoped keys instead of describing them as a future 0.8.0 item), docs/integration.md's audit-trail section, docs/audit-chain.md and docs/api-keys.md. No server, CLI, SDK or library runtime code changed. Also documents, rather than silently assumes, that GET /api/v1/audit/verify/agent-viewer verify are not yet tombstone-aware and report hash_mismatch on a legitimately redacted/retained row even though the standalone bundle verifier (#401) already handles this correctly; filed as [issue #408](https://github.com/jmmana/A...

Read more

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 12 Oct 15:52
79cb2f1

[0.7.0] - 2026-10-12

"Captura automática e interoperabilidad" (automatic capture and interoperability): an OTLP/HTTP GenAI traces
receiver and file import, packaged LangGraph and OpenAI Agents SDK adapters tested against the real
frameworks in CI, a durable local queue in both SDKs, and a Prometheus /metrics export (core endpoint,
ledger-derived families, in-process ingestion counters, optional OTLP metrics push).

Upgrading

Purely additive: no breaking change to the event contract, the HTTP API, or the embeddable library's props.
Every new setting defaults to off or to its existing behavior, so an existing deployment needs no
configuration change to keep working.

  • The SDK durable queue (Python spool_dir=..., TypeScript spoolDir/AGENT_VIEWER_SPOOL_DIR) is opt-in:
    without it, both SDKs behave exactly as before (an unrecovered transient failure still raises/throws).
  • GET /metrics is opt-in (AGENT_VIEWER_METRICS=1, CLI --metrics); unset, the route answers 404
    exactly like before this release. See docs/metrics.md for every new environment
    variable it reads.
  • The OTLP traces receiver (POST /v1/traces) and file import share the existing OTLP auth, rate limit and
    size-limit settings already in place for /v1/logs//v1/metrics; no new required variable.
  • examples/openai-agents-adapter/ is removed (dead code since issue #361); import
    @warlockcode/agent-viewer-openai-agents instead if anything in your own tree still referenced that path
    (see the Removed section below).

Added

  • Durable local queue in the Python SDK (#101, v0.7.0 milestone): optional, bounded, on-disk spool (spool_dir=..., stdlib sqlite3 only, no third-party dependency) so a transient failure (connection down, reset, timeout, 408, 429, any 5xx) after the normal retries never raises into the host agent. Events are written to disk with their original id and timestamp and replayed in enqueue order once the server returns, through POST /api/v1/events/batch, so the server's own idempotency by id keeps totals honest. New AgentViewer arguments (spool_dir, spool_max_events, spool_max_bytes, spool_overflow, spool_batch_size, spool_background, spool_probe_interval, spool_max_probe_interval) and methods (flush(), spool_stats(), spool_rejected(), close(), usable as a context manager, plus an atexit final flush). A size cap evicts the oldest non-usage events before usage events (drop_oldest, the default) or refuses the new event (drop_newest); every drop and every server-side rejection is counted in SpoolStats, never silently lost, and the spool never sums, prices or defaults usage (a missing cost stays absent, never 0). Also fixes transient-failure classification with the spool disabled: a raw OSError/http.client.HTTPException and HTTP 408 are now retried instead of escaping or failing immediately. New docs/sdk-queue.md (shared contract with the TypeScript SDK, #102) and tests/fixtures/sdk-queue-scenarios.json (shared test vectors). New tests/test_python_sdk_spool.py (34 tests: outages, restart persistence, overflow matrix, poison events, 413/429/401 handling, corrupt/newer-schema spool files, permissions, fork safety, a seeded randomized concurrency test). CI now runs both Python SDK test files and adds a Python 3.9 job to exercise the SDK's declared minimum version.
  • SDK: per-event sessionId on emit()/emitBatch() and durationMs on toolCompleted()/toolFailed() (first sub-issue of #99, v0.7.0 milestone): prerequisite for the packaged LangGraph adapter (#99), which serves many thread_ids through one process and builds events through the low-level emit/emitBatch path, not through a per-session AgentViewer instance. TypeScript's EmitEventInput.sessionId? (sdk/typescript/index.ts) now overrides the client's own sessionId for that one event in both emit() and emitBatch() (previously hardcoded to this.sessionId, ignoring any such field on the input); Python's AgentViewer.emit() gains the same session_id/timestamp_ms keyword arguments emit_batch() already had per item, so a callback's own fire time can be recorded instead of the time it reaches a background queue. ToolCallFinishOptions (toolCallId plus durationMs) is the new option type for toolCompleted()/toolFailed() (TypeScript) and duration_ms the new keyword for tool_completed()/tool_failed() (Python), matching the contract: durationMs is valid on ToolCompletedPayloadSchema/ToolFailedPayloadSchema but not ToolStartedPayloadSchema, so toolStarted()/tool_started() do not accept it. New tests in tests/sdk-typescript.test.mjs and tests/test_python_sdk.py cover the per-event session override and the duration field landing only on completed/failed. Deferred to later sub-issues of #99: the packages/sdk npm workspace extraction (#343), the TypeScript (#344) and Python (#345) LangGraph adapters and their recorded-run fixtures, and the release/documentation plumbing (#346).
  • packages/sdk: publishable npm workspace extraction of the TypeScript SDK (second sub-issue of #99, v0.7.0 milestone, issue #343): the root package.json now declares "workspaces": ["packages/*"], and the logic that used to live only at sdk/typescript/index.ts moved to the new workspace package @warlockcode/agent-viewer-sdk (packages/sdk/src/index.ts), with no runtime or type import of src/integrations/* or zod: every structural type it needs (CanonicalEvent, CanonicalEventType, EventSeverity, LlmErrorKind, the llm.usage/llm.failed/tool.* payload shapes, ViewerSnapshot, UsageSummary, CallRecord, the usage rollup types, the budget types, UsageFigures) is copied, not imported, from the root contract and server modules, so the published .d.ts references no file outside the package. sdk/typescript/index.ts is now a thin re-export of the package, so tests/sdk-typescript.test.mjs and every example adapter keep working unchanged. A new CI type test (tests/types/sdk-package-canonical-event.ts, checked by npm run lint) asserts the package's CanonicalEvent/CanonicalEventType/EventSeverity/LlmErrorKind stay assignable to the root contract's types in both directions. packages/sdk/package.json has exports, files, publishConfig.access: public, is ESM only, has its own tsconfig.json emitting declarations, and a check:package script running publint --strict and attw --pack . (also runnable at the root as npm run check:package:sdk). The root npm run build:packages (npm run build --workspaces --if-present) builds every workspace package and now runs automatically before npm run lint and npm test (prelint/pretest), since the sdk/typescript/index.ts re-export needs packages/sdk/dist to exist to resolve the package by name. docker/Dockerfile's deps stage now also copies packages/sdk/package.json before npm ci, so the workspace lockfile resolves inside the image. CI gained three new steps: building the workspace packages, check:package:sdk, and a packed-tarball smoke test (npm pack the package, install the .tgz into a throwaway ESM project, import it). packages/sdk/README.md documents the package and the workspace layout convention (issue #100's OpenAI Agents adapter reuses this same layout). Deferred to later sub-issues of #99: the TypeScript (#344) and Python (#345) LangGraph adapters and their recorded-run fixtures, and the release/documentation plumbing (#346) that will actually publish this package to npm.
  • packages/openai-agents: publishable npm workspace extraction of the OpenAI Agents adapter (second sub-issue of #100, v0.7.0 milestone, issue #361): examples/openai-agents-adapter/processor.ts/index.ts move, unchanged in behavior, to the new sibling workspace package @warlockcode/agent-viewer-openai-agents (packages/openai-agents/src/), reusing packages/sdk's exact layout (issue #343) rather than inventing a new convention: same tsconfig.json shape, same exports/files/publishConfig fields, same check:package (publint --strict && attw --pack . --profile esm-only), no second "workspaces" field at the root. The processor now imports AgentViewer/EmitEventInput from the published @warlockcode/agent-viewer-sdk package instead of the repo-relative sdk/typescript/index.ts path; @openai/agents is a peerDependencies entry (the host brings its own version). Deviates from issue #361's own text in one place, documented in packages/openai-agents/README.md: the issue lists @warlockcode/agent-viewer (the main library package) as a peer dependency, but the processor never imports anything from it, only from @warlockcode/agent-viewer-sdk; that is declared as a regular dependencies entry instead (an internal implementation detail, not a version the host needs to pin like @openai/agents). tests/openai-agents-adapter.test.mjs now imports the processor from its new path; its test suite, assertions and the tests/examples-adapters.test.mjs doc comment are otherwise unchanged. Root build:packages now builds packages/sdk before packages/openai-agents explicitly (npm run build --workspaces has no dependency-graph ordering, and this package's .ts sources import the sdk package by name, needing its dist/ first); a new npm run check:package:openai-agents script, a CI step for it, and a packed-tarball smoke test (installing both this package's and packages/sdk's local tarballs together, since neither is on the public npm registry yet, plus the real @openai/agents) are added alongside the existing packages/sdk ones. .github/workflows/release.yml gains a release-openai-agents job (needs release) publishing @warlockcode/agent-viewer-openai-agents with provenance after the main package, same skip-without-NPM_TOKEN/skip-on-prerelease pattern, plus a version-lockstep check against the ...
Read more

v0.6.0

Choose a tag to compare

@jmmana jmmana released this 12 Oct 03:39
e531019

[0.6.0] - 2026-10-11

"Precios y control" (pricing and control): a versioned, auditable server pricing table (#83) with a per-row,
list-price cost estimate computed at ingestion and recomputable later (#84); budgets per agent, model, session,
user or tag with server-computed ok/warning/exceeded/unknown state (#85); signed outbound alert delivery
when a budget crosses a threshold (#86); a library budget marker that is purely host-decided and hidden by
default (#87); a Model Ops Pricing editor and Budgets panel, and the office canvas wired to real budget state
(#88); and measured latency percentiles and failure rates per model (#89). This release is release-engineering and
documentation only (#90): three new guides (docs/pricing.md, docs/budgets.md, docs/webhooks.md), one
canonical webhook-signing reference replacing three partial copies, and the version bump.

Added

  • Crew CEO phone animation: 32 original RGBA drawings in four directions for pickup and conversation, reverse-frame hangup and return to idle, selected only from PHONE_CALL snapshots. Per-actor playback prevents another CEO from inheriting the shared phone pose; preserves call notices, reduced motion and fallback. Originals, metadata, tests and MP4 evidence retained (#316, Refs #118/#145).

  • Crew CEO think, talk and review: 48 original RGBA frames across four orientations, driven only by reported states with transition/cancellation, reduced-motion and localized fallback support. Preserved originals, measured metadata and runtime video/camera evidence (#280, Refs #118).

  • Budget alerts: the budget.alert canonical event, crossing detection, deduplication and storage (#319, sub-issue of #86, v0.6.0 milestone): src/integrations/canonicalTypes.ts gains 'budget.alert' in CANONICAL_EVENT_TYPES (additive, schema stays 1.0) and canonicalContract.ts a BudgetAlertPayloadSchema, server-emitted only. New server/budgetAlerts.ts adapts #86's original design (written before #85 shipped, assuming an onBudgetsEvaluated(BudgetEvaluation[]) hook with an arbitrary thresholds array) to the real, merged onBudgetTransition(BudgetTransition) hook (#85, sub-issue #305): buildBudgetAlert turns a transition landing on warning/exceeded into a deterministic evt_budget_ id (sha256 of budgetId/to/window.start, same construction as webhookEventId) and a payload named after the real BudgetStatus/BudgetTransition fields (knownSpent, the full BudgetCallCounts, a pricingVersions array), never the guessed names of #86's draft; a transition into ok or unknown is never an alert. New EventStore.recordBudgetAlert() (memory: a Set reserved synchronously before the method's only await; SQLite: one transaction, a new budget_alerts table with UNIQUE (budget_id, period_start, state), migration 15) is the entire deduplication mechanism: a real crossing offered any number of times (two concurrent live checks racing on the same transition, a re-imported history, a restart) is stored and broadcast exactly once. POST /api/v1/events and /events/batch now reject a client-submitted type: "budget.alert" or an id starting evt_budget_ (including via Idempotency-Key) with 400 reserved_event_type (batch: existing all-or-nothing validation-error shape). New tests/budget-alerts.test.mjs (pure buildBudgetAlert cases, a 50-concurrent-transition dedup race, a new-period re-arm case, two end-to-end crossings against the real app with SSE/reconnect replay, and the reserved-type/id guard on both ingest endpoints, run against both memory and SQLite storage); extended tests/canonical-contract.test.mjs, tests/event-fingerprint.test.mjs and tests/budgets-sse.test.mjs (the live event: budget transition frame stays unstored, but the new budget.alert event is a real canonical event and now legitimately appears in a reconnect replay). Deferred to later sub-issues of #86: the signed outbound webhook and its budget_alert_deliveries table (#321), GET /api/v1/budget-alerts (#322), the SDK verifyWebhookSignature helpers (#323), and the library trackUsage privacy guard plus the feature's README/docs narrative (#320).

  • Budget alerts: library trackUsage privacy guard and documentation (#320, sub-issue of #86, v0.6.0 milestone): applyExternalEvent now ignores every budget.alert event completely (not applied to any agent, task or meeting, never added to events) unless the host opts in with trackUsage: true, the exact same guard llm.usage already has; the embedded <AgentOffice> (always trackUsage: false) never sees a spend figure from an alert this way. A host that does pass trackUsage: true keeps the raw event in its own history but the library still renders none of its summary or payload. Documented in docs/library.md/docs/library.es.md and in the event table of docs/integration.md (server-emitted only, reserved evt_budget_ id prefix, rejected from client submission).

  • Budget alerts: signed outbound webhook delivery with retries (#321, sub-issue of #86, v0.6.0 milestone): new server/budgetAlertDelivery.ts. Every detected budget.alert (except a backfill: true one, from an already-closed period) is queued for delivery to AGENT_VIEWER_ALERT_WEBHOOK_URL when configured; with no URL, the feature is off and every delivery stays skipped. Each attempt POSTs the alert's exact payload, byte-identical on every retry, with X-Agent-Viewer-Event: budget.alert, X-Agent-Viewer-Delivery (the alert id, stable across retries), X-Agent-Viewer-Attempt, a fresh X-Agent-Viewer-Timestamp per attempt and, when AGENT_VIEWER_ALERT_WEBHOOK_SECRET is set, X-Agent-Viewer-Signature (the same ${timestamp}.${rawBody} hex HMAC-SHA256 scheme server/index.ts verifies inbound webhooks with, computed with this separate secret; omitted entirely, not faked, when no secret is set). Any 2xx succeeds; a network error, a timeout, 408, 429 or any 5xx retries with backoff min(5000ms * 2^(attempt-1), 1h) (uniform jitter in [base/2, base], a Retry-After header raising the floor); any other 4xx or a redirect fails immediately with no retry; after AGENT_VIEWER_ALERT_WEBHOOK_MAX_ATTEMPTS (default 6) the delivery is failed for good, a dead letter, logged with only the alert id and the last HTTP status. A plain http:// target is refused at startup unless loopback or AGENT_VIEWER_ALERT_WEBHOOK_ALLOW_HTTP=1; there is no SSRF protection on https:// targets today, a documented, current limitation, not yet a check. A background worker (startAlertDeliveryWorker) polls due deliveries; in SQLite mode a still-pending delivery resumes after a restart, in memory mode it is lost. New budget_alert_deliveries table (migration 16). New tests/budget-alert-delivery.test.mjs.

  • Budget alerts: SDK verifyWebhookSignature helpers (#323, sub-issue of #86, v0.6.0 milestone): a pure, constant-time, receiver-side verification helper for the outbound budget.alert webhook (#321), in both SDKs, mirroring the exact HMAC-SHA256 scheme server/index.ts already uses to verify an inbound webhook's signature: ${timestamp}.${rawBody}, hex digest. Python's verify_webhook_signature(secret, timestamp, raw_body, signature, tolerance_ms=300000, now_ms=None) (sdk/python/agent_viewer.py) compares with hmac.compare_digest; TypeScript's verifyWebhookSignature({ secret, timestamp, rawBody, signature, toleranceMs, nowMs }) (sdk/typescript/index.ts) hashes both sides with SHA-256 first (mirroring the server's own safeEqual) before crypto.timingSafeEqual, so a signature of any length never throws and never leaks its length through timing. Both reject a timestamp more than toleranceMs/tolerance_ms (default 300000, 5 minutes) away from nowMs/now_ms in either direction, return a plain boolean and never raise or throw. Neither SDK gains any way to build or emit a budget.alert event: that type stays server-reserved (#319); these functions only verify one a receiver already has. New tests in tests/test_python_sdk.py and tests/sdk-typescript.test.mjs, each building the expected signature independently with the same scheme, covering a good signature, a tampered body, a stale timestamp, a future timestamp beyond tolerance, the exact tolerance boundary, a wrong secret and (TypeScript) a signature of the wrong length. sdk/python/README.md documents the helper with a minimal receiver example.

  • Budget alerts: GET /api/v1/budget-alerts read API (#322, sub-issue of #86, v0.6.0 milestone): GET /api/v1/budget-alerts[?budgetId=&since=&limit=] lists recorded budget_alerts rows newest first by detectedAt (since is an epoch-ms floor; limit defaults to 100 and is capped at 500; a non-numeric since/limit is 400 validation_failed), each with its real delivery: { status, attempts, lastStatus, deliveredAt } embedded (read from #321's budget_alert_deliveries, migration 16), plus a webhook: { configured, signed } summary derived only from whether AGENT_VIEWER_ALERT_WEBHOOK_URL/_SECRET are set, never the URL or the secret itself. GET /api/v1/budget-alerts/:alertId reads one alert, or 404 { "error": "not_found" }. Same /api/v1 auth and rate limiting as every other route; no create/edit/redeliver endpoint, alerts are a pure consequence of spend. Written before it was certain whether #321 (the webhook and its budget_alert_deliveries migration) would merge first: EventStore.getBudgetAlertDeliveries still checks for the table via sqlite_master (never a hardcoded flag) and falls back to the honest PENDING_BUDGET_ALERT_DELIVERY placeholder (status: "pending") for any id it cannot resolve, so the endpoint stays correct regardless of merge order; #321 landed first in practice, so every alert now carries its real pending/delivered/failed/skipped status end to end. New tests/budget-alerts-api.test.mjs (auth, query validation, the newest-first/`si...

Read more

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 10 Oct 19:00
622929e

[0.5.0] - 2026-10-10

"Spend in the office": per-agent usage badges and a per-call detail panel on the canvas, fed only by host props (#76, #77); the portal reading those same figures from the real usage ledger for canvas badges, the agent inspector and the call-detail modal (#78); cost per meeting and per tool computed on the server and shown in the Meetings panel and tool tooltips (#80, #81); Model Ops's Matrix, Agents and Feed tabs reconciling call by call against the ledger instead of local counters (#79); and the library guide's four privacy rules written out in one place for integrators: the host computes, the component only displays; usage UI stays hidden until the host opts in; a missing figure is always "unknown", never 0; and the call detail panel is metadata only, never prompt, completion or tool-argument text. Full reference: the usage chapter of the library guide (español).

Added

  • Crew: its own audio engine, with music in the Lounge room (#150, epic #114): Crew gets a real audio engine (src/crew/crewAudioEngine.ts), independent of the Cartoon mode sound effects (src/engine/soundEffects.ts); separate code, separate state, separate preference. Every sound is synthesized at runtime with Web Audio API oscillators, so there is no recorded audio file to license: origin and license (CC0, original code, no third-party IP) for each sound are documented in src/crew/crewAudioAssets.ts (CREW_AUDIO_MANIFEST, the canonical source) and in docs/crew/AUDIO_LICENSES.md (human-readable table and rationale). Music is off by default and never starts on its own: CrewStage's accessibility-and-audio panel gets a Play/Pause button that only a click or keyboard activation can trigger; selecting the Lounge room and pressing Play starts its ambient loop, moving to any other room (Development, QA, the CEO office, etc.) fades it out and requires a fresh Play press to resume, so Lounge music can never play by accident elsewhere. CREW_ROOM_MUSIC maps every room explicitly (null = silence; only lounge has a track today), so adding a sound to another room is a one-line, reviewable change. The engine fades in/out on room changes and on manual pause, pauses on window/tab blur and visibilitychange and resumes only if it was already authorized to play (never a new autoplay), and dispose()s fully on unmount (every oscillator stopped, every node disconnected, the AudioContext closed, no listener left behind): switching to Cartoon mode unmounts CrewStage and so cleans up Crew's audio without touching Cartoon's own sound preference. useCrewAudio (src/crew/useCrewAudio.ts) wires the engine to React's lifecycle and already-existing mute/volume preferences (crewPreferences.ts, wired but inert since #157); a browser blocking autoplay (AudioContext.resume() rejecting) is caught and surfaced as its own blocked state with a caption, never thrown. New subtitles for every audio state change (playing/paused/blocked) reuse the existing room/camera caption mechanism (subtitles preference, #157), satisfying the same textual-equivalent requirement for this sound. New tests/lib/crewAudioEngine.test.ts, tests/lib/useCrewAudio.test.tsx (mocked Web Audio API, autoplay-blocked handling, focus/visibility pause-resume, StrictMode-safe cleanup) and tests/lib/crewAudioAssets.test.ts (license manifest completeness, room isolation). All new English/Spanish UI text follows CrewStage's existing inline-ternary pattern. Deliberately out of this change's scope, per the issue's own split into sub-topics: the phone ring (#145) and the coffee sound (#146) are separate issues and are not wired to any room; the engine already exposes the primitive (playCue) and the per-room mapping those issues need, with no further change expected here. A per-room volume slider was not added: CrewPreferences.volume/muted (#157) stay global, and room isolation is achieved architecturally (each room's track is independent and fades out when left), matching the issue's "no sala de Lounge por accidente" acceptance criterion without inventing a second, undocumented preference axis.
  • Example: library-host, a worked host app reading the rollup/calls API (#260, v0.5.0 milestone): new examples/library-host/, the runnable example #82's guide points to for "getting figures from the server". usageFromRollup.ts declares the server's rollup/calls response shapes and two pure mappers, rollupToUsage and callsToDetail, that only copy or null a figure, never compute one (locked by a token-scanner AST-style check in tests/lib/libraryHost.test.tsx, since the whole point of this example is showing the one right way to feed the library real spend). HostOffice.tsx fetches both endpoints on mount and every 15 seconds (abortable, never overlapping) through a small node:http proxy (proxy.ts, about 70 lines) that injects Authorization: Bearer $AGENT_VIEWER_API_TOKEN server side; the token variable is referenced only there and in the example's own README.md (tests/library-host-proxy.test.mjs checks this statically, plus that the proxy forwards only allowlisted GET paths, strips a client-sent Authorization, never echoes the token, and refuses to start empty). main.tsx/index.html/vite.config.ts give it its own dev server, unrelated to the root build. seed.jsonl/seed.sh seed five agents covering a known cost, mixed currencies, an explicitly unknown cost and a failed call (agent-viewer send cannot send llm.usage, so seed.sh posts the batch endpoint directly, taking the server token as an argument rather than by the same environment variable name, so it stays outside that "only two files" rule too).
    Reconciled against the real shipped #66, not the issue's illustrative sketch: the shipped rollup response has no single totalTokens field, only a per-kind breakdown already summed server-side (tokens.input.sum, ...); computing one from input.sum + output.sum here would be exactly the client-side arithmetic this mapper must not do, so totalTokens reads "unknown" on the compact badge in this example until the rollup API grows a combined field, while the per-kind breakdown, cost and the call detail panel all show real figures. Likewise, an agent absent from the rollup gets no byAgent entry, which the already-shipped #76 badge and accessible list render as no usage text at all (not a literal "unknown" badge); this example's tests check that real, shipped behavior rather than the issue's pre-#76 wording. See examples/library-host/README.md and the module doc comment in usageFromRollup.ts for the full reasoning on both.
  • Docs: reproducible, script-verified role palette measurement for the Crew visual style contract (#116 partial, epic #114): re-audited #116 and its own blocking foundations (CREW-001..008: #172/#173/#166/#168/#171 closed, #167/#170 still open) and confirmed the issue's core remains genuinely blocked, as it requires final art and real walk animation for all six roles that only exists, as a prototype, for the CEO; that is art direction, not something this change can decide. Found one real, bounded gap inside the already-merged contract itself: section 6 of docs/crew/visual-style-contract.md/.es.md described the five non-CEO roles' outerwear swatches as "measured automatically... 16-level color quantization, alpha >= 200..." but no script in the repository ever implemented that measurement; the documented values (~#005060 and similar) were hand-eyeballed approximations, not reproducible ones. New scripts/crew-palette-measure.mjs (a from-scratch PNG decoder built only on node:zlib, no new dependency, since the project has no PNG-decoding library) measures each role's idle-front prototype PNG with that exact method, explicitly excluding hair/outline and light-skin pixels by hue and lightness so they cannot be mistaken for the outerwear color. The five swatches are now exact and script-verified (#085868, #1848C8, #283838, #482888, #C85838), replacing the old ~-prefixed guesses in both documents, and locked by new tests/crew-palette-measure.test.mjs (npm run crew:palette:measure reproduces them against the real bank PNGs) so the table cannot silently drift from the art it describes. This does not ratify any palette: every swatch still reads unratified, and section 13's open follow-up ("art-director ratification... ") is explicitly left standing, since that is a human art-direction call this change does not make. No PNG/SVG/manifest asset was touched. See the #116 row in docs/crew/IMPLEMENTATION-STATUS.md for the full accounting.
  • Crew: real desk and plant artwork from the asset bank in the CEO office (#115 partial, epic #114): closes the gap the issue itself flagged as pending after #172/#194 ("this rescue does not yet integrate art into the rooms") for a small, honest slice. The executive desk and the floor plant (furniture.desk-executive, furniture.plant-floor, both still status: prototype, never approved) now render as the real original SVG in the independent Crew renderer, not the usual flat-shaded 2.5D box, only for the ceo-desk/ceo-plant furniture of the CEO office; the other 51 bank assets and the other ten rooms are untouched. New src/crew/crewPropImages.ts (load/dispose, mirroring the existing CEO sprite loader) and src/crew/useCrewPropImages.ts (per-room load/cleanup, same lifecycle as useCrewSprite), with crewPropsLayer.ts/renderCrewRoom.ts drawing the resolved image as a flat billboard instead of the polygon box when one is available, falling back to the box (with its own English/Spanish status text, data-testid="crew-prop-art-status") if the image fails to load. Two known, documented limits: the bank has no per-angle verified views for any furniture piece yet, so the same single image is reused across all four cameras rather ...
Read more

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 10 Oct 16:26
80716ae

[0.4.0] - 2026-10-09

"Usage ledger": the server keeps one durable, append-only row per model call with its own receive time, answers grouped audit questions and paginated call listings over it, exports it as CSV or JSONL with per-currency reconciliation totals, redacts secrets at export, bounds how long it (and raw events) are kept, and no longer accepts a token in a URL or starts open by accident. Also ships the portal loading full history on open and OTLP metrics ingestion. The one breaking change is listed first; see docs/migrating-to-0.4.md for the full upgrade notes, docs/usage-ledger.md, docs/usage-export.md, docs/retention.md and docs/redaction.md for the references themselves.

Breaking changes

  • Removed: a token or api_key query parameter never authenticates /api/v1 or /v1/logs, in any mode, even when the value is correct (#71), including POST /api/v1/webhooks/generic when no webhook secret is configured. Such a request now gets 401 query_token_not_supported. EventSource clients (which cannot send an Authorization header) now call the new POST /api/v1/stream-tickets first and open the stream with the single-use ticket it returns (?ticket=); connectEventStream in @warlockcode/agent-viewer does this automatically wherever fetch cannot stream a response body. Migration notes, before/after for every client: docs/migrating-to-0.4.md.
  • The server refuses to bind a non-loopback interface with no token (#71). npm run server and a direct startServer() call now default to 127.0.0.1 (AGENT_VIEWER_HOST), not every interface. Setting AGENT_VIEWER_HOST to anything else (for example 0.0.0.0) with no AGENT_VIEWER_API_TOKEN configured makes the process print one line to stderr and exit with code 1 (OpenApiRefusedError, code: 'open_api_refused'). Set a token, bind loopback, or set AGENT_VIEWER_ALLOW_OPEN=1 to accept an open API anyway. The agent-viewer CLI and the Docker images are unaffected: they always run with a token.
  • With no token configured and no AGENT_VIEWER_ALLOW_OPEN=1, /api/v1 now rejects requests that do not look local (#71). A request from a non-loopback remote address gets 403 open_api_loopback_only; a non-loopback Host header gets 403 open_api_host_not_allowed; an Origin header whose host is neither loopback nor listed in an explicit AGENT_VIEWER_CORS_ORIGIN gets 403 open_api_origin_not_allowed (a bare * does not count as a list). A request with no Origin header (curl, the SDKs, the CLI) is unaffected, and an HMAC-signed webhook still works regardless of where it comes from. A reverse proxy in front of the server makes every request look local, so a proxied deployment still needs a token.

Added

  • Crew walk frame pipeline (#117): four original CEO prototype atlases with eight illustrated frames per direction, a versioned manifest and safe prototype-only importer, lazy scene/facing loading, camera transition preload, measured foot anchors and cancellation/reduced-motion/fallback handling in the independent renderer. Playback follows reported isWalking without creating events, usage or navigation. Includes integrity/runtime tests and four-view/mobile/zoom PNG and MP4 evidence; final art and physical paths remain outside this pipeline pilot.

  • Crew visual style contract (#116): a documentation-only deliverable formalizing the "Office Beans" chibi style that the approved CEO reference already draws, so a future illustrator or engineer has one contract to target instead of reverse-engineering the CEO prototype. No art was added or changed. See docs/crew/visual-style-contract.md (English) and its Spanish twin docs/crew/visual-style-contract.es.md.

    • Names the asset lifecycle the issue asked for (reference | vector-study | prototype | approved), ties each state to what asset-manifest.json and scripts/crew-assets.mjs already enforce today, and records that approved and a formal vector-study manifest entry are not reachable yet (follow-up work, out of scope here).
    • Documents the measured character canvas contract (256x352 px RGBA, anchor 0.5/0.9375) that the 11 existing character prototypes already share, the six roles' silhouette/palette cards (CEO's palette authored on the reference sheet, the other five roles' measured automatically from their prototype art and flagged as unratified), the clip/animation contract (ceo.blink.front.v1 as the reference implementation), the ADR-001 layer order and zoom range, and the explicit prohibition on faking a missing facing by stretching or mirroring existing art.
    • scripts/crew-assets.mjs: validateCrewAssets() now also enforces the documented character canvas contract (CREW_CHARACTER_CANVAS 256x352, CREW_CHARACTER_ANCHOR 0.5/0.9375) for every kind: 'character' entry, not just the CEO's four facings. Covered by two new cases in tests/crew-assets.test.mjs plus a dedicated assertion across all 11 character prototypes.
  • Crew renderer split into independent, tested layers (#167): src/crew/renderCrewRoom.ts used to mix room structure, furniture, actor sprites and the presence badge in one function. It is now the orchestrator only; the drawing itself lives in five new modules, each with its own Vitest suite and no import of the other: crewLightingLayer.ts (the shared shading palette: wall/floor tones and per-prop-type face colors), crewSceneLayer.ts (background, walls, floor grid and door thresholds), crewPropsLayer.ts (furniture boxes), crewSpriteLayer.ts (the actor's pose or blink frame) and crewHudLayer.ts (the presence number badge, the only canvas-drawn HUD element). crewRenderPrimitives.ts holds the one shared drawing primitive (a filled, stroked polygon). renderCrewRoom.test.ts and crewViewport.test.ts pass unmodified, confirming identical pixel output and draw order after the split.

    • New crewSceneLifecycle.ts: a framework-independent mountCrewScene() that schedules a render frame with requestAnimationFrame, observes canvas size with ResizeObserver, and exposes update()/unmount(), so a Crew scene's full lifecycle (mount, update, unmount) is unit-testable without React or a real browser (tests/lib/crewSceneLifecycle.test.ts, including an end-to-end test that mounts, switches room and unmounts a real renderCrewRoom scene). It is not yet wired into CrewStage.tsx; that integration is left for a follow-up change, out of this issue's scope, after an attempt to wire it surfaced a regression in crew-sprites.spec.ts that this PR reverts before merging.
  • Two-mode architecture contract: Cartoon (legacy) and Crew (#166): a new ADR-002 formalizes the contract that visualMode: 'cartoon' | 'crew' already implemented across several merged PRs: cartoon stays the default everywhere, switching the mode never recreates the domain OfficeStore, never drops an event and never resets the host's usage/task state, and each renderer's own Crew presentation state (room, per-room camera, reducedMotion) survives the switch whether or not the host controls it. Two new automated checks enforce the contract instead of only documenting it: tests/crew-mode-boundary-166.test.mjs scans the real source tree so Cartoon can never import from src/crew/ and Crew can never import Cartoon's renderer, legacy grid or motion modules (and confirms renderFurnitureItem stays private); tests/lib/crewModeContract166.test.tsx switches visualMode twenty times (the issue's own acceptance threshold) against a growing event stream and asserts the office timer is never duplicated, the latest event is never lost, and Crew's room/camera/reducedMotion state (controlled or not, across two independent AgentOffice instances) never leaks or resets. See ISSUE-166-ACCEPTANCE.md for the full acceptance matrix.

  • Crew spatial model: collisions, functional doors and walkable zones (#170, CREW-005, building on the eleven-room catalog from #168): pure logic over the existing CrewRoomDefinition data, no new art, no new props.

    • src/crew/crewCollision.ts: axis-aligned bounding box (AABB) collision over room data that already existed. propAABB/roomBoundsAABB/agentAABB turn a furniture anchor, a room's floor and an agent's presence radius into boxes; aabbsOverlap, aabbContains and pointInAABB are the primitive tests; crewAgentCollides rejects a spot that leaves the room or overlaps any furniture footprint (with an excludeFurnitureId escape hatch so an agent can re-check its own current anchor); overlappingFurniturePairs is a data-integrity check confirmed empty for all eleven rooms; doorAABB gives the threshold box of a door on the south wall.
    • src/crew/crewModel.ts: each room's doors now also carries connectsTo (the neighbouring room id, or null) and edgeId, built from a new ten-edge spanning tree (CREW_ROOM_EDGES) that connects all eleven rooms into one building, rooted at reception. Every room's original door keeps its exact previous id and position; rooms with more than one neighbour (reception, development, lounge, coffee, meeting, research, qa) get additional doors at new, furniture-clear offsets. No room's furniture, dimensions or rendering changed.
    • src/crew/crewRoutes.ts: the reusable connectivity layer. buildCrewRoomGraph turns the door data into a room adjacency graph; defaultCrewDoorStates/setCrewDoorState/isCrewEdgeOpen are a pure, immutable open/locked state per edge (every door starts open); findCrewRoomRoute and reachableCrewRooms do a breadth-first search across rooms that skips any locked door, so a locked door can fully separate two rooms, exactly as...
Read more

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 09 Oct 20:37
7d11280

[0.3.0] - 2026-10-09

"Cifras ciertas" (true figures): the server, the portal, the CLI and the SDKs stop treating an unknown token count, cost or currency as zero, split cache reads from cache writes, add llm.failed, and Claude Code finally reports tokens through install claude-code --telemetry. First release published to npm, GHCR and (pending the owner's PyPI trusted-publisher setup) PyPI. The full semantics, field by field, are written down once in docs/usage-semantics.md (español); every section below links back to it instead of restating the rules. While the package is at 0.x, its API may still change between minor versions.

Breaking changes

  • Snapshot usage block. GET /api/v1/snapshot (and the new GET /api/v1/usage) no longer carries only a bare totalTokens / totalCost. It now also returns usage: UsageSummary (server/usageAggregates.ts): per (currency, costSource) cost buckets, a sum/unreportedCount pair per token kind, and the same breakdown by agent and by model. totalTokens and totalCost are kept for one minor as deprecated projections (removed in 1.0): totalCost is a number only when every successful call reported a cost in exactly one (currency, costSource) pair, and is null otherwise, instead of a sum across currencies.

    Before (0.2.x):

    { "totalTokens": { "input": 1200, "output": 340, "cached": 0, "reasoning": 0 }, "totalCost": 0.42 }

    After (0.3.0), alongside the same (now honest) deprecated fields:

    {
      "totalTokens": { "input": 1200, "output": 340, "cached": 0, "reasoning": 0 },
      "totalCost": null,
      "usage": {
        "total": {
          "calls": 2,
          "tokens": { "input": { "sum": 1200, "unreportedCount": 0 }, "output": { "sum": 340, "unreportedCount": 0 } },
          "byCurrency": [{ "currency": "USD", "costSource": "provider-reported", "amount": 0.3, "calls": 1 }],
          "costUnknownCount": 1
        }
      }
    }

    Migration: read usage.total (or GET /api/v1/usage) instead of totalTokens/totalCost; see docs/usage-semantics.md section 2 for every field.

  • PATCH /api/v1/agents/:agentId allow-list. Only descriptive profile and status fields are accepted (name, roleTitle, provider, model, status, statusText, workspace). tokensInput, tokensOutput, cachedTokens, reasoningTokens, cost and any other unlisted key are rejected with 400 validation_failed (issues[].path names the field), instead of silently changing spend. Migration: report usage with an llm.usage event through POST /api/v1/events, or the SDK usage() / agent.usage(...) helper.

  • Contract nullish fields. llm.usage (and llm.failed) no longer default cachedTokens or reasoningTokens to 0: an omitted or null value now stays null/absent instead of becoming a zero that looked like "the provider reported zero". New cacheReadTokens and cacheWriteTokens replace the deprecated cachedTokens (kept as an alias; a differing value in both is rejected). New llm.failed event type (canonical event count: 22 to 23).

    Before: { "inputTokens": 500, "outputTokens": 120 } meant "cached and reasoning tokens are zero".
    After: the same payload means "cached and reasoning tokens were not reported"; a provider that truly billed zero cached tokens must send "cacheReadTokens": 0 explicitly.

    Migration: readers of stored or exported events must handle null/absent as "unknown", not "zero".

  • SDK costSource default. Passing a cost with no costSource no longer sends "provider-reported"; it now sends "unknown" and prints one warning per client instance. currency and a task id are newly accepted. Migration: pass cost_source="provider-reported" (Python) or costSource: 'provider-reported' (TypeScript) when the cost really came from the provider, and pass currency.

  • Totals no longer include PATCH amounts or unknown-as-zero. A figure the 0.2.x snapshot showed (built from PATCH-edited numbers and zero-filled unknowns) can differ from the 0.3.0 figure for the exact same stored data; the 0.3.0 number is the correct one. See docs/usage-semantics.md for how to reconcile it against a provider invoice.

  • Webhook usage validation is now stricter. usage blocks without required fields (provider, model, inputTokens, outputTokens) are rejected with 400 validation_failed. Empty usage objects ({}) are now rejected, not accepted as zero-filled events. Usage with an unknown costSource is accepted as an unknown cost source, not silently mapped to an assumed value.

  • Ingestion behavior change: a client that reuses an event id for different content used to get 200 duplicate and now gets 409 conflicting_duplicate. The new event is still not stored, so totals do not change; the difference is that the loss is now visible. Batches keep answering 202 and report the item as status: "conflict".

  • Raw HTTP clients that retry by rebuilding the body with a fresh timestamp now get 409 on the retry, because timestamp is part of the event. A retry must resend the identical event. The SDKs are not affected: they build each event once and resend the same object.

  • Retries across the upgrade: an event stored by 0.2.x with a default the 0.2.x validator filled in (for example cachedTokens: 0) has a different fingerprint than the same event validated by 0.3.0, so a retry of it after upgrading gets 409. This fails closed: the stored row stays and nothing is counted twice.

  • The Claude Code hook gives a tool event a stable id but the timestamp of each hook run, so a hook that fires twice (installed in both user and project settings) now reports the second copy as conflict instead of duplicate. The hook still exits 0 and the office keeps the first copy; the count shows in /ready.

  • A 0.3.0 SDK needs a 0.3.0 server: a 0.2.x server rejects llm.failed and drops cacheReadTokens and cacheWriteTokens.

Added

  • SSE reconnect now replays every missed event or sends an explicit resync, never a silent partial replay (issue #54). New AGENT_VIEWER_SSE_REPLAY_MAX (default 10000, 0 means any reconnect that missed something always resyncs) bounds how many missed events a reconnect replays in full; past it, or when the cursor is unknown (never stored, evicted in memory mode, or lost after a memory-mode restart), the server sends a named resync frame (reason: cursor_unknown, gap_too_large or buffer_overflow, missed as a count or null when truly unknown, never 0) instead of event frames, and keeps the connection open and streaming live events afterward so older clients keep working. A full replay ends with a named replayed frame (replayed, lastEventId). GET /health gains sse: { replayMax, replaysSinceStart, eventsReplayedSinceStart, resyncsSinceStart }. connectEventStream gains lastEventId (resume from a cursor, typically snapshot.lastEventId), onResync and onReplayed options, a new resyncing RealtimeStatus, and resyncCount() on the returned connection. The demo app rebuilds its live office from GET /api/v1/snapshot on a resync through the new rebuildFromSnapshot helper (src/integrations/snapshotRebuild.ts), shows the server's own usage totals (never re-added from the snapshot's 100 events) and a dismissible notice in English and Spanish. See docs/integration.md and docs/library.md.
  • Golden reconciliation suite (npm run test:golden, issue #62): one hand-checked fixture (tests/fixtures/reconciliation/, mixed currencies, a missing cost, a missing currency, missing cache tokens, a duplicate id, a conflicting duplicate, a repeated requestId, a failed call) run through the memory store, the SQLite store across a restart, the portal reducer and the embeddable library's summarizeUsage, asserting all four reach the same figures. Runs as its own CI step, right after typecheck, and fails on the field and the path that diverged. Ships with an independent reference reducer that cross-checks the hand-worked expected figures. Deferred to a follow-up: the HTTP routes (including signed webhook retries and the PATCH rejection path), memory eviction, and a library-privacy Vitest suite; none are blocked on a missing dependency, see the fixture's README.md.
  • agent-viewer install claude-code --telemetry points a project's Claude Code at the office's OpenTelemetry logs, so token and cost per model call show up (opt-in, off by default; needs a new Claude Code session). Helper mode (default) writes an otelHeadersHelper command that looks the running office's token up on every export; static mode (--telemetry --token <token>) writes a fixed OTEL_EXPORTER_OTLP_LOGS_HEADERS instead, for an office on another machine or in Docker, with the token always masked as <token hidden> in the diffs the installer prints. The five OTEL_LOG_* content flags are always pinned to "0", and no metrics or traces exporter is ever configured. install refuses outright on a conflicting existing setting (naming the file and the key) and warns, without refusing, on a setting it only overrides by Claude Code's own precedence. --no-telemetry removes only the telemetry block, keeping the hooks; uninstall claude-code removes both, byte for byte, the same way the hooks round-trip today. New agent-viewer otel-headers --url <office-url> helper command (used by otelHeadersHelper, not meant to be run by people): never touches the network, prints the office's bearer token as JSON only when a running office has that exact origin, always exits 0. Guide: docs/claude-code.md. Refs #60; the server now has the POST /v1/logs receiver this telemetry needs (#59), so this ingests f...
Read more

v0.2.1

Choose a tag to compare

@github-actions github-actions released this 08 Oct 14:53
03b4b5c

See CHANGELOG.md

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 08 Oct 13:49
40e92cc

See CHANGELOG.md