Skip to content

Releases: luantaraschi/lull

v0.3.0

Choose a tag to compare

@luantaraschi luantaraschi released this 19 Aug 02:53

Stop guessing how long to wait.

Added

  • A typing event. Feed it whatever presence your channel already emits, with runtime.typing({ conversationId }) or { type: 'typing', at } on the core, and an open turn stays open while the person composes. It never opens a turn, and maxWaitMs still caps it, so somebody who types without ever sending still gets an answer.
  • policyFor on createRuntime, for conversations that need different patience. It runs before the conversation is locked, so it may read a database.
  • npm run bench:sweep and Choosing quietMs.

Why this release exists

The 2500ms default was never measured. It is now. Across a thousand simulated conversations with realistic typing gaps, that default has the bot answering before the person has finished in 47% of bursts. At 6000ms it is 14%, and it avoids more model calls at the same time, so in that range waiting longer wins on both axes. Past 8000ms nothing further merges and the extra wait is pure latency.

A reasonable default for a messaging channel is 5000 to 6000. Better still, send the typing event and let the channel tell you.

Compatibility

deadline() now reads max(lastMessageAt, lastTypingAt). State written by earlier versions carries no lastTypingAt and keeps working unchanged.

v0.2.0

Choose a tag to compare

@luantaraschi luantaraschi released this 19 Aug 02:27

Shared state, a documented API, and a page you can run the library on.

Added

  • redisStore(), from @luantaraschi/lull/redis. State shared across instances, with locks taken via SET NX PX and released with a compare-and-delete script, so a critical section that outlives its own TTL cannot delete the lock its successor is holding. The client is typed structurally, so the package still has zero runtime dependencies and works with ioredis or a six line shim over another client.
  • A serverless recipe (npm run example:serverless) for platforms where no process stays alive: drive the pure core from a stateless handler and let an external scheduler deliver the ticks.
  • An API reference covering every export, option and event.
  • A showcase page that runs this library's own reducer in the browser, compiled from src/core at deploy time, so the demo cannot drift from the code.

Changed

  • CI now checks formatting, enforces a coverage floor, and runs publint and attw against the packed tarball. The published shape used to be verified by hand, which is to say rarely.

Installing from the repository works today (npm i luantaraschi/lull), including both subpaths. The npm release is still pending.

v0.1.0

Choose a tag to compare

@luantaraschi luantaraschi released this 18 Aug 20:22

First release.

lull is the part every chat agent rewrites badly. It waits for a conversation to fall quiet, then hands you one turn instead of four.

What it handles:

  • Fragmented messages. Balloons are coalesced into a single turn after quietMs of silence, capped by maxWaitMs. Across 1,000 simulated conversations that is 71.3% fewer model calls (npm run bench, reproducible).
  • Redelivered webhooks. The same messageId twice produces one event, tracked in a bounded window.
  • Human takeover. The bot goes quiet for a TTL, and messages arriving meanwhile are dropped rather than queued.
  • Session expiry. The next turn after inactivity carries isNewSession: true.

The core is a pure reducer that never reads the clock, creates a timer, or touches the network. It returns effects as data, and the facade executes them. That makes concurrency behaviour testable as arithmetic: 30 tests at this tag, three of them property-based, none of them sleeping.

See the CHANGELOG for the full list and the known limitations.