Releases: luantaraschi/lull
Release list
v0.3.0
Stop guessing how long to wait.
Added
- A
typingevent. Feed it whatever presence your channel already emits, withruntime.typing({ conversationId })or{ type: 'typing', at }on the core, and an open turn stays open while the person composes. It never opens a turn, andmaxWaitMsstill caps it, so somebody who types without ever sending still gets an answer. policyForoncreateRuntime, for conversations that need different patience. It runs before the conversation is locked, so it may read a database.npm run bench:sweepand 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
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 viaSET NX PXand 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/coreat deploy time, so the demo cannot drift from the code.
Changed
- CI now checks formatting, enforces a coverage floor, and runs
publintandattwagainst 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
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
quietMsof silence, capped bymaxWaitMs. Across 1,000 simulated conversations that is 71.3% fewer model calls (npm run bench, reproducible). - Redelivered webhooks. The same
messageIdtwice 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.