Skip to content

Latest commit

 

History

History
206 lines (140 loc) · 31.6 KB

File metadata and controls

206 lines (140 loc) · 31.6 KB

Changelog

[0.4.3] - 2026-08-06

Fixed

  • withCycles no longer releases a reservation after a recognized terminal commit rejection. The guarded function has already spent the resource, so returning its reserved budget would undercount known spend.
  • StreamReservation.commit() still surfaces a recognized terminal rejection, but keeps the handle finalized. A broad caller catch can no longer turn the failed settlement into a release of known spend.
  • withCycles now releases only when the guarded function itself fails. Post-action settlement/setup failures never return budget for work that already ran; a failing or invalid actual callback falls back to the validated estimate and records metadata.actual_source="estimate".
  • A configuration that disables estimate fallback without providing actual is rejected before a reservation is created or the guarded function runs.
  • StreamReservation.commit() now replaces a non-finite, negative, fractional, or unsafe-integer actual with the validated estimate and an actual_source="estimate" marker instead of journaling an invalid amount.

Tests and docs

  • Regression tests pin no-release behavior for lifecycle, post-action, and streaming paths.
  • README settlement tables and error guidance now distinguish handler failure (release) from post-action commit rejection (never release).
  • The development lockfile updates brace-expansion to 5.0.9, clearing the current high-severity expansion DoS advisories; published runtime dependencies are unchanged.

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog 1.1.0.

[Unreleased]

[0.4.2] - 2026-07-28

Fixed

  • Heartbeat transport exceptions from both withCycles and reserveForStream are now reported with the reservation ID, error detail, and same-key retry disposition. Heartbeats remain non-fatal and retain their existing recovery timing, but a lost extend response is no longer invisible to operators.
  • Known actual usage is journaled before the first commit request. Commit and event settlement now require exact schema-valid HTTP 200/201 success; malformed 2xx responses retain the original idempotency key and durable record.
  • Journal filenames now use v2-<sha256(exact UTF-8 reservation id)>.json, with collision-safe legacy migration. Contradictory retryable 4xx envelopes retain the record.
  • Pull-request and release CI now run every shared durable-recovery and guarantee-boundary scenario; publishing is gated on conformance.
  • CommitResponse and its wire mapper now expose the optional cycles_evidence reference.
  • Unsupported or structurally invalid journal records are quarantined without blocking valid replay, and the conformance adapter reports the exact native test it executed instead of copying runner-owned oracle outcomes.

[0.4.1] - 2026-07-27

Fixed

  • Final lease-response and timing conformance (supersedes the older fallback wording below). Both server-authoritative and fieldless fallback scheduling count only a complete, schema-valid HTTP 200 create/extend response as success; malformed or non-200 2xx responses remain ambiguous and are recovered with the same idempotency key. Create performs one same-key recovery attempt, the enforced timeout covers the whole attempt, post-receipt setup time is deducted from the first delay, and reliable RTT samples gathered before a rolling-upgrade server starts sending remaining_ttl_ms remain part of the safety budget.
  • Heartbeat extend drift (P1 liveness). Per the protocol spec, extend_by_ms extends relative to the current expires_at_ms, and the server caps extension_count at max_extensions (default 10). Both heartbeats (withCycles and reserveForStream) beat every max(ttlMs/2, 1000) ms and extended by the full ttlMs on every beat, so expiry drifted outward by ttl/2 per beat — a process crash left the budget reserved until the drifted expiry (zombie budget lockup) — and the extension budget burned twice as fast as needed, so runs longer than about max_extensions * ttl/2 lost heartbeat protection mid-flight. Both heartbeats now use measured-grant lead accounting (v2.3): each beat computes a rigorous lower bound on the expiry lead, leadMin = grantsSum − (now − anchor), where grants are differences of successive expires_at_ms values returned by the server (same server clock frame; a 2xx without a numeric expires_at_ms counts as the requested ttl; negative grants count as 0) and the elapsed term is client-monotonic — no cross-clock arithmetic anywhere, and leadMin starts at 0 because the initial grant has no safe same-clock anchor to measure against (see the next entry), so the bound never overstates the real lead. A beat is skipped only when leadMin ≥ 1.5 × lastGrant (the last measured grant); otherwise it extends by the requested ttl_ms — the server owns clamping. On the fallback path (create response without remaining_ttl_ms — see the round-5 entry under Added) the first beat fires immediately (delay 0): spec review round 4 confirmed that any bounded first-beat delay can outlive a small capped lease (tenant policy max_reservation_ttl_ms caps grants silently and the create response then carries no effective-TTL signal), so the first extend primes the lease with a real, measurable grant right away instead of gambling on an unknowable one. The 0 delay applies to the first schedule only — a transiently failed first attempt retries after the full held interval min(requestedTtl/2, 30 s) with the same idempotency key, never hot-loops. After each applied grant the cadence re-derives per a regime split (round 4): grant-derived cadence is only valid when the grant is a real per-extend amount. Under a maximum-lead clamp (the server holds expires_at_ms ≈ now + L), successive expires_at_ms differences measure elapsed time, not granted lease — deriving the cadence from them would collapse the interval to the 500 ms floor and burn max_extensions in seconds. A grant that is non-positive, or both below 0.9 × the requested ttl and within the 0.75 ×1.25 × band of the elapsed time since the last applied extend, is therefore treated as lead-clamped: the cadence holds at min(requestedTtl/2, 30 s), a once-per-heartbeat console.warn reports that the server appears to clamp lease lead and the extension budget will deplete, and the heartbeat keeps extending every beat (lastGrant ≈ elapsed keeps leadMin below the skip threshold — exactly the desired liveness behavior). The band must be two-sided (Rust-port finding, adopted fleet-wide): after a leadMin skip the next grant arrives across a doubled gap, so a genuine grant-clamped server (cadence grant/2) also shows grant ≈ elapsed exactly once — an upper bound alone would classify it as lead-clamped and the hold would self-sustain (at the held cadence the grant stays elapsed forever, decaying the lease to a lapse: a 15 s-per-extend lease banks only +15 s per 30 s of held-cadence wall time). With the band, a real maximum-lead clamp tracks any gap with ratio ≈ 1 and stays held, while the post-skip real grant lands in the hold once: at the held cadence its ratio falls to ≈ 0.5, exits the band, and the cadence re-tightens. Round 5 then established that regime detection from (grant, elapsed) alone is formally undecidable — any per-extend grant in the sticky window [0.75 × min(ttl/2, 30 s), 0.9 × ttl) reproduces the elapsed gap at the held cadence and misclassifies permanently (e.g. ttl 24 s with +10 s grants: held cadence 12 s, ratio 10/12 ≈ 0.83 sits inside the band forever while the lease erodes to a lapse) — so this heuristic is a best-effort fallback for servers that clamp only the per-extend delta; the server-authoritative remaining_ttl_ms (see Added) is the normative path. A real per-extend grant re-derives the cadence as clamp(grant/2, 500 ms, requestedTtl/2) — a server that clamps the grant size automatically tightens the cadence (24 h request granted 1 h per extend → 30 min beats), with no false skips because the skip threshold scales with the same measured grant. The 500 ms interval floor cannot starve liveness: it binds only when the server grants less than the spec's own minimum ttl_ms per extend. A failed extend is retried on the next beat at the current cadence reusing the same idempotency key, so a lost response cannot double-extend; a success regenerates the key. Permanent rejections — HTTP 410, RESERVATION_EXPIRED, RESERVATION_FINALIZED, MAX_EXTENSIONS_EXCEEDED, TENANT_CLOSED (tenant closure is irreversible), or NOT_FOUND (a 404'd reservation never comes back) — now stop the heartbeat for good (previously it retried doomed extends forever); transient failures log a warning and keep retrying. ctx.expiresAtMs is still updated from the authoritative extend response. (Replaces the alternate-beat scheme first shipped on this branch — adversarial review showed it put every steady-state attempt at exactly ttl/2 lead and its 1 s interval floor guaranteed a lapse for spec-legal ttl < 2000 — the lead-estimate scheme that followed it, which counted an unmeasurable initial +ttl of lead and leaned on the HTTP Date header for correctness, and the v2.2 revision, which kept a Date-hinted bounded first-beat delay and let elapsed-sized grants tighten the cadence.)
  • HTTP Date header fully demoted out of the heartbeat (spec review rounds 3–4). An earlier revision of this branch recovered an "effective TTL" as expires_at_ms − Date (via CyclesResponse.serverDateMs) and used it for the beat interval, the lead arithmetic, the skip threshold, and extend_by_ms; round 3 demoted it to a first-beat cadence hint. The Date header is not a safe same-clock anchor for expires_at_ms: RFC 9110 defines Date as a whole-second, best-effort origination timestamp that intermediaries may generate or replace, and in the reference server expires_at_ms comes from Redis TIME while Date comes from the servlet container — two different clocks. Round 4's immediate first beat removes the last consumer of the hint: the first measured grant now arrives at ~t=0, strictly better than any estimate, so computeEffectiveTtlMs and its plumbing are deleted outright. CyclesResponse.serverDateMs remains as a general-purpose response accessor; the heartbeat no longer reads it. Every quantity in the heartbeat — first-beat timing, lead accounting, skip threshold, cadence, and extend_by_ms — derives from measured grants, monotonic elapsed time, or the requested ttl. Never from Date.

Added

  • Server-authoritative heartbeat scheduling via remaining_ttl_ms (spec PR #148, settled at head dd60c27). The protocol adds remaining_ttl_ms (integer, int64, ≥ 0) to both ReservationCreateResponse and ReservationExtendResponse: the remaining reservation lifetime in ms at response evaluation, in the same clock snapshot as expires_at_ms, present on successful live-reservation responses (absent on dry-run/DENY and on older servers; cycles-server is implementing emission in parallel). Both heartbeats implement the spec's PRIMARY ALGORITHM, normative whenever the field is present. Success predicate: only a schema-valid HTTP 200 ReservationExtendResponse (status: "ACTIVE", integer expires_at_ms ≥ 0, optional integer remaining_ttl_ms ≥ 0) counts as an observed success on this path; any other or malformed 2xx is ambiguous — never used to schedule from stale state — and is recovered like a transient failure with the same idempotency key. Scheduling, recomputed from every schema-valid response alone (expiry differences are never accumulated, and the heuristic leadMin skip check is bypassed — the schedule is exact, so a heuristic skip could push a beat past the real lease): rtt is each individual HTTP attempt's monotonic response_received − attempt_sent (max tracked per heartbeat); lead_floor = max(0, remaining_ttl_ms − rtt); request_timeout_budget is the client's enforced finite per-attempt bound (connectTimeout + readTimeout, which CyclesClient applies to every request via AbortSignal.timeout); attempt_budget = max(request_timeout_budget, 1 s, 2 × maxRtt); safety_margin = max(1 s, 2 × maxRtt); retry_reserve = 2 × attempt_budget + safety_margin; next_delay = max(0, lead_floor − retry_reserve) after response receipt. Budgets and margins round up, leads and delays round down; arithmetic is overflow-safe (saturating to Infinity — an unknown or unbounded timeout makes attempt_budget infinite and next_delay 0). Zero-delay guard: a schema-valid success producing next_delay = 0 permits exactly one immediate fresh attempt (new idempotency key); if that success also yields 0, the heartbeat stops and surfaces that the lease is shorter than the retry-safety budget. Unavailable/unreliable attempt timing forces lead_floor = 0 into the same guard — never a silent downgrade to the fieldless fallback. Recovery (timeout, connection error, 5xx, 429, ambiguous 2xx): current_lead_estimate = max(0, last lead_floor − monotonic elapsed since the schema-valid response that established it); retry_window = current_lead_estimate − attempt_budget − safety_margin (unclamped). A negative window means no complete retry plus margin provably fits: stop and surface. Otherwise non-429 failures retry with the same key after min(30 s, current_lead_estimate/4, retry_window); 429 converts Retry-After delta-seconds to ms (overflow-safe) and retries after exactly that delay only when it fits the window — missing, invalid, or window-exceeding values stop and surface (never an earlier retry that violates throttling). Repeated recovery is allowed: every failed or ambiguous attempt recomputes lead and window from the same last schema-valid response before deciding again; a zero window permits one immediate retry, and a progress guard stops the loop if neither elapsed time nor the window moves between consecutive failures. Any other 4xx stops and surfaces without ever rotating the idempotency key on an unchanged request; the permanent-stop codes are unchanged. First beat: derived from the create response's remaining_ttl_ms with the same formula, using the create call's own measured rtt — no immediate prime, so no max_extensions slot is wasted under a max-lead clamp (same-key create/extend replays are safe to schedule from: the server recomputes remaining_ttl_ms at replay-response construction time). The grants/lead bookkeeping keeps running underneath, so if the field disappears mid-flight (proxy strips it, mixed-version fleet) the v2.3 heuristic takes over seamlessly; servers that never send it get the unchanged fallback behavior.
  • metadata.actual_source = "estimate" marker on estimate-fallback commits. When cfg.actual is not configured and useEstimateIfActualNotProvided is left at its default, withCycles commits the estimate as the actual; previously that estimate was recorded as measured spend with no trace. The commit body now carries actual_source: "estimate" in metadata (merged with any commit metadata set via ctx.commitMetadata; created when absent) plus a console.debug note, and the marker flows into the POST /v1/events fallback body, which copies commit metadata. The default fallback behavior itself is unchanged. reserveForStream is unaffected: handle.commit(actual, ...) always takes an explicit caller-provided actual, so no fallback (and no marker) exists there.

[0.4.0] - 2026-07-27

Durable commit retries. Previously a commit that failed transiently lived only in a floating in-memory promise: process.exit(), a crash, or a signal dropped it, and once the reservation's grace period elapsed the server's expiry sweep returned the reserved budget to the pool, permanently under-counting spend that had already happened. Ports the full design from cycles-client-python v0.5.0 (PR runcycles/cycles-client-python#89, three review rounds).

Added

  • src/journal.ts: file-per-commit CommitJournal (atomic unique-temp-file write, idempotent replay). Config: journalEnabled (default true), journalDir (default ~/.runcycles/commit-journal), retryFlushTimeout (default 10 s); env CYCLES_JOURNAL_ENABLED, CYCLES_JOURNAL_DIR, CYCLES_RETRY_FLUSH_TIMEOUT. Records are partitioned into per-identity subdirectories (directories 0700, files 0600 where supported) keyed by a non-secret PBKDF2-HMAC-SHA256 fingerprint of the server plus principal — the configured tenant when set (rotation-safe), else the API key. The derivation is byte-compatible with the Python SDK, so same-tenant clients in both languages share an identity directory and can settle each other's records. The first engine created per identity replays surviving entries; corrupt files are renamed *.corrupt; a persisted not_before_ms floor makes Retry-After waits survive restarts.
  • Event fallback: a commit answered RESERVATION_EXPIRED (budget already returned to the pool) is recovered via POST /v1/events, reusing the commit idempotency key with metadata.recovered_reservation_id / recovery_reason markers and no overage_policy (spec default ALLOW_IF_AVAILABLE never rejects). Applies to withCycles and the streaming adapter.
  • Rate-limit awareness end to end: 429 / LIMIT_EXCEEDED on the first commit attempt schedules a retry instead of releasing the reservation, passing the server's Retry-After into the engine; on retried attempts the journal entry is retained and the next attempt waits at least Retry-After.
  • Authentication failures (401/403) on any commit attempt or event fallback journal the spend instead of releasing or discarding it.
  • CommitRetryEngine.scheduleEvent() and flush(timeoutMs?).
  • flushPendingCommits(timeoutMs?) — public, exported from the package root: waits (bounded) for all in-flight background commit retries across every engine in the process, including the engines withCycles and reserveForStream create internally. Defaults to the maximum retryFlushTimeout among engines. Call it before returning a handler response in serverless environments.

Changed

  • StreamReservation.commit() no longer throws on transient failures. Transport errors, 5xx, 429, 401/403, and post-expiry commits are journaled and retried in the background (with the /v1/events fallback once expired) and resolve normally with finalized remaining true. Only genuine rejections (e.g. UNIT_MISMATCH) still reset finalized and throw so the caller can correct and retry or release. Previously every failure threw and reset finalized, leaving spend recovery entirely to the caller.
  • Retry-engine promises are tracked (awaitable via flush()) instead of floating; retries that exhaust or fail non-retryably retain their journal entry (transient/auth) or discard it (genuine rejection) instead of silently dropping the spend record.
  • With retryEnabled: false, failed commits are journaled for next-run replay instead of silently dropped (the old drop behavior remains only when the journal is also disabled).
  • Unclassifiable 4xx commit responses no longer release or discard spend. A 4xx is treated as a genuine rejection (release in withCycles, throw in StreamReservation.commit(), journal discard in the retry engine) only when it carries a recognized protocol error code; codeless, mangled, or forward-compat unknown codes are journaled and retained with an error log instead. HTTP 410 by status alone is now classified as RESERVATION_EXPIRED (catches bodyless 410s) and recovered via the /v1/events fallback.
  • Honored server delays are clamped to 1 hour (Retry-After passed to schedule(), stashed from a 429, or a restored journal not_before_ms floor) — a mangled header or corrupted timestamp cannot park a spend record for days or overflow Node's 2^31-1 ms setTimeout limit.
  • Cross-SDK journal parse strictness (Python/Java parity): records with mode: null or array-valued commit_body/event_fallback_body are quarantined as *.corrupt instead of coerced; an empty event_fallback_body on an expired commit is treated as absent (journal retained, no empty /v1/events post). A whitespace-only configured tenant now falls back to the API key as the journal identity principal (the raw untrimmed tenant is still used when non-blank).
  • journal.record() also tightens permissions (0700, best-effort) on the base journal directory, and loadPending() garbage-collects *.tmp files older than one hour left behind by crashed writers.

[0.3.4] - 2026-07-24

Protocol error handling, response-mapping correctness, and release-pipeline hardening. This is the first published release after 0.3.1; the changes previously documented as 0.3.2 and 0.3.3 are included here because those versions were never tagged or published.

Added

  • TENANT_CLOSED error-code support introduced in runtime spec v0.1.25.13 (runcycles/cycles-protocol#125): new ErrorCode.TENANT_CLOSED enum member, TenantClosedError class (thrown at reservation time by withCycles / lifecycle / reserveForStream via buildProtocolException; commit-time client errors are handled/released internally by withCycles, and StreamReservation.commit() throws generic CyclesError), and CyclesProtocolError.isTenantClosed() helper. The code is non-retryable and remains backward compatible with servers that return unknown future error codes.
  • LIMIT_EXCEEDED error-code support per runtime spec v0.1.25.12 (revision 2026-07-04): HTTP 429 rate-limit responses (public evidence/JWKS endpoints) carry error=LIMIT_EXCEEDED plus Retry-After / X-RateLimit-Reset headers. New ErrorCode.LIMIT_EXCEEDED enum member in spec declaration order (after MAX_EXTENSIONS_EXCEEDED; TENANT_CLOSED relocated after it so the enum mirrors the spec exactly). Classified retryable by both isRetryableErrorCode and CyclesProtocolError.isRetryable() — 429 is transient and the spec instructs retry after the indicated delay; the status-based rule only covers ≥500, so the code-based classification carries it (this also preserves the prior errorCodeFromString → UNKNOWN → retryable fallback behavior). Enum-only by design, matching the BUDGET_FROZEN/BUDGET_CLOSED pattern: not a reservation-lifecycle denial, so no exception class or buildProtocolException mapping.
  • Retry-After header exposure: the client now captures the HTTP Retry-After header (how 429 rate-limit responses carry the delay per the spec) and exposes it as CyclesResponse.retryAfterMsHeader (seconds → ms; non-integer forms ignored gracefully). buildProtocolException falls back to it for retryAfterMs when the body carries no retry_after_ms field (body wins when both are present). No auto-retry behavior change — the delay is surfaced, not consumed.
  • Regression coverage confirms listReservations forwards and URL-encodes the additive from / to, expires_from / expires_to, and finalized_from / finalized_to ISO-8601 query parameters. The existing params?: Record<string, string> API already accepted them.

Changed

  • npm publish now uses npm Trusted Publishing (OIDC) instead of the long-lived NPM_TOKEN secret (NODE_AUTH_TOKEN removed from the publish job; the job already had id-token: write and upgrades npm, which OIDC requires at >= 11.5.1). The trusted publisher must be configured for the runcycles package on npmjs.com before the next tagged release. Mirrors the same change in cycles-mcp-server, whose v0.3.0 release initially failed on an expired token.
  • package.json repository.url normalized to git+https://... per npm pkg fix, which also makes it match the exact form npm's trusted-publisher repository check expects.
  • Refreshed the vendored cycles-protocol-v0.yaml contract fixture from v0.1.24 to the current v0.1.25.15 and aligned the exact ErrorCode contract assertion with LIMIT_EXCEEDED and TENANT_CLOSED.

Security

  • Forced transitive esbuild to >= 0.28.1 via npm overrides, resolving Dependabot alert #9 (low severity, dev-only: arbitrary file read via the esbuild development server on Windows; tsup pins esbuild ^0.27.0 so no direct range reaches the patched version). Remove the override once tsup allows esbuild >= 0.28.
  • Updated test-only transitive fast-uri from 3.1.2 to 3.1.4, resolving the high-severity host-confusion advisories reported through the Ajv contract-test toolchain. The dependency is not included in the published package.

Fixed

  • eventCreateResponseFromWire now maps the declared EventCreateResponse.charged field. Previously, the effective charge on ALLOW_IF_AVAILABLE-capped events was silently lost and always appeared as undefined.
  • README error-handling docs no longer describe CyclesTransportError as thrown on network failure — the SDK never constructs it. Reservation-time transport failures surface as CyclesProtocolError with status: -1 (withCycles / reserveForStream) or as CyclesResponse with isTransportError / status: -1 (programmatic client); commit-time failures are retried in the background by withCycles, while StreamReservation.commit() throws and resets finalized for caller retry or release. The class remains exported for use in user code; a new "Transport failures (status -1)" README subsection documents the actual behavior.
  • examples/vercel-ai-sdk chat route no longer mixes AI SDK v4 and v5 APIs (it compiled under neither while package.json pins "ai": "^4.0.0"): now pure v4 — Message type and convertToCoreMessages replace v5's UIMessage / convertToModelMessages. runcycles usage unchanged.

Notes

  • Library changes are additive or bug fixes; there is no breaking API or wire-format change.
  • Evidence/JWKS endpoints and the remaining additive response-mapping work are outside this release and remain tracked in #134.
  • 339 tests pass; coverage is 98.61% statements and 99.81% lines. Lint, typecheck, build, dependency audit, and package dry-run are clean.

[0.3.1] - 2026-05-07

npm metadata refresh for category-search discovery. No code changes — bundle and runtime behavior are identical to 0.3.0.

Changed

  • package.json: rewrote description to lead with the cost / action / audit pillars ("TypeScript AI agent runtime control — enforce LLM cost limits, action permissions, and audit trails for agents before execution.") and expanded keywords from 15 to 26. Drops legacy keywords (billing, metering, api-client, ai, llm, agents, token-budget, spend-limit) in favor of category-search variants (ai-agent, agent-budget, budget-control, cost-enforcement, spending-limit, llm-cost, runtime-authority, action-control, action-authority, audit-trail, audit, compliance, multi-tenant) plus framework targeting (langchain, langgraph, openai-agents, vercel-ai-sdk, mcp).

[0.3.0] - 2026-04-27

Java parity: dynamic subject and action fields on withCycles.

Added

  • Dynamic subject + action fields on withCycles config — tenant, workspace, app, workflow, agent, toolset, actionKind, and actionName now accept (...args: TArgs) => string | undefined in addition to a static string. Callables are resolved against the wrapped function's per-call args; returning undefined falls through to the client-config default (subject) or "unknown" (action). Static strings unchanged. Java parity with cycles-spring-boot-starter#50. (#72)

[0.2.0] - 2026-03-24

Bug fixes, support 0.1.24 spec.

Added

  • Add badges to README for npm, CI, and license (#24)
  • Add documentation links section to README (#25)
  • Add budget and extension error codes, charged amount to event response (#29)

Changed

  • Document nested withCycles behavior and recommended patterns (#26)
  • Claude/analyze spring issue 29 v biy9 (#27)
  • Change default overage policy from REJECT to ALLOW_IF_AVAILABLE (#28)
  • chore: bump version to 0.2.0 for protocol v0.1.24 (#30)

[0.1.2] - 2026-03-19

Fix type safety in WithCyclesConfig generics.

Added

  • Add AUDIT.md documenting protocol conformance (#19)
  • Add AWS Bedrock and Google Gemini budget governance examples (#20)
  • Add parent README for examples directory (#21)
  • Add API key creation guide to documentation and examples (#22)

Fixed

  • Fix type safety in WithCyclesConfig generics and add compile-time type tests (#23)

[0.1.1] - 2026-03-13

Updates and bug and stability fixes, more SDK examples.

Added

  • Add manual workflow_dispatch trigger to CI publish (#4)
  • Add comprehensive test coverage for lifecycle, streaming, and error handling (#7)
  • Add comprehensive examples for Cycles budget governance (#9)
  • Claude/expand ai examples zj dwy (#10)
  • Add ESLint with typescript-eslint/recommended and coverage thresholds (#12)
  • Add lint and coverage enforcement to CI (#13)
  • Add test for commit retry exhaustion warning (#18)

Changed

  • Comprehensive README rewrite for npm publication (#5)
  • Optimize initialization and add async disposal support (#6)
  • Update TEST_COVERAGE_ANALYSIS.md with final coverage results (#8)
  • Document withCycles client caching behavior in default client section (#11)
  • Document commit rollback behavior for failed commits in streaming sec… (#14)
  • Warn on commit retry exhaustion in CommitRetryEngine (#15)

Removed

  • Remove dead code: unused constants, validateReservationId, makeClient (#16)
  • Remove CyclesTransportError from public exports (#17)

[0.1.0] - 2026-03-13

Initial release.

Added

  • Add TypeScript client for Cycles budget-management protocol (#1)
  • Add comprehensive mapper functions for wire format conversion (#2)
  • Add CI/CD pipeline and improve package metadata (#3)