Change Log
4.7.0 July 6, 2026
@warlock.js/ai gains the prompt compiler — systemPrompt().refined({ model, criteria, store }) lazily rewrites human-authored prompt text into a model-optimized version, pinned like a lockfile with machine-enforced placeholder parity and an always-safe fallback to the original — plus ai.prompts.validate({ criteria }) to grade a prompt against your own rules. @warlock.js/fs gains an ergonomic async fs facade — fs.files.* / fs.dirs.* grouping, lazy File / Directory handles, read-modify-write helpers (edit / editJson / mergeJson), recursive walk, and zero-dependency schema-validated JSON. @warlock.js/cascade lands a driver-correctness pass — a dozen real Postgres/MongoDB driver fixes (multi-row findAndUpdate, where-scoped update / unset / deleteOne, pivot detach, MongoDB with() eager-loading, per-subclass global scopes) plus new lockForUpdate({ skipLocked }) row locking (FOR UPDATE SKIP LOCKED).
- Added
systemPrompt().refined({ model, criteria, store })— the prompt compiler. Humans keep writing human prompt text; the refined wrapper lazily rewrites it into a model-optimized version on first agent use and pins the result like a lockfile (re-compiled only when the source text, refiner model,criteria, or recipe version change — never silently).await refined.refine()returns the compiled template string (placeholders intact — routes / previews / warmup / CI; throwsPromptRefinementErroron failure) andawait refined.refinePrompt()returns a composable prompt withmeta.refinedFrom/meta.refinerModelprovenance (register it todifforiginal vs refined). Placeholder parity is machine-enforced (one repair re-ask, then rejected); the lazy agent path never throws — it warns once and serves the original. - Added
ai.prompts.validate({ criteria })— validate a prompt against your own rules. Passcriteria(a string or a list of short rules) and, when ajudgemodel is supplied, it replaces the built-in quality rubric so the judge'sscore/issuesreflect your criteria (a failed rule is named inissues). Advisory only — never flips the deterministicok; folded into thejudgeCachekey so different rules re-run.
- Added
fsshorthand facade — an async, ergonomic surface over the primitives:fs.files.*(file ops),fs.dirs.*(directory ops), lazyfs.file(path)/fs.dir(path)handles (File/Directoryclasses), andfs.exists(path)(type-agnostic). Delegates to the existing*Asyncprimitives; synchronous callers keep using the bare primitives (thebare = sync/*Async = asynccharter is unchanged) - Added New file ops on the facade:
append/prepend/appendLine/appendJsonLine(NDJSON),size,isEmpty,ensure(create-if-missing, never truncates),touch,edit(read → transform → write),editJson,mergeJson(shallow, or{ deep: true }),ensureJson(get-or-create),checksumMatches,readLines(streaming async iterator), and an EXDEV-safemove(creates the destination parent, falls back to copy+unlink across devices) - Added New directory ops on the facade:
empty(emptyDir),size(recursive byte sum),count,isEmpty,walk(constant-memory async iterator of{ path, name, type }), arecursiveoption onlist/listFiles/listDirs, andhash(stable directory fingerprint) - Added
fs.files.getJson(path, { schema })— validate parsed JSON against any Standard Schema validator (seal / zod / valibot) with zero dependency (calls the schema's own~standard.validate); throwsJsonSchemaValidationErroron failure.{ default }returns a fallback when the file is missing - Added
File/Directoryhandles are lazy (no IO in the constructor) and immutable (copy/move/rename/copyTo/moveToreturn a NEW handle); pure-path helpersname/basename/extension/parent()and childfile(...)/dir(...);Directory.listFiles()/listDirs()returnFile[]/Directory[] - Added
fs.hashnamespace —fs.hash.string/fs.hash.buffer(sync, pure/in-memory) andfs.hash.file/fs.hash.dir(async, read from disk) - Added
fs.files.get()is overloaded: a text read returnsstring(no cast); pass{ encoding: null }for aBuffer
- Added
lockForUpdate({ skipLocked?, noWait? })— row locking on SELECT (FOR UPDATE [SKIP LOCKED | NOWAIT]), the concurrent job-queue claim shape; Postgres-only, the MongoDB driver throws - Added
DatabaseDriverContract.supportsSqlSerialization— capability flag (defaulttrue);falseroutes the MigrationRunner through direct migration-driver execution - Fixed Postgres model-level
sum/avg/min/max/distinct/countDistinct/pluck/valueno longer return0/undefined— the hydration callback is reset before reading, matching MongoDB - Fixed Postgres
Model.findAndUpdate/Model.atomicnow update every matching row instead of one arbitrary row (a hiddenLIMIT 1; MongoDB was already multi-row) - Fixed Postgres query-builder
update()/unset()now honor the chainedwherefilter — previously they updated the whole table - Fixed Postgres query-builder
deleteOne()deletes exactly one row — the internallimit(1)was silently ignored, deleting every matching row - Fixed Postgres pivot
detach(ids)(andsync/toggle) works — the driver translates Mongo-style filter operators ($in,$nin,$eq,$ne,$gt,$gte,$lt,$lte) instead of binding the operator object literally - Fixed CHECK constraints are no longer silently dropped on the MigrationRunner SQL path — the Postgres serializer emits
ADD CONSTRAINT ... CHECKforthis.check(...)and column.check(...) - Fixed MongoDB
with()eager loading is no longer a silent no-op —get()runs the relation loader, same wiring as Postgres - Fixed MongoDB pipelines order
$matchbefore$project(SQL semantics), soselect()beforewhere()no longer strips the filter column and returns[]— fixes pivotattachde-duplication andsync/toggledeltas - Fixed The MigrationRunner works on MongoDB — migrations execute directly through the migration driver;
exportSQLstays SQL-only with a clear unsupported error - Fixed MongoDB
dropIndex(table, name)honors the literal index name — the string form is no longer rewritten to<name>_1(the columns-array form keeps the convention name) - Fixed
addGlobalScope/addLocalScoperegister per-subclass — a scope added on one model (e.g. a soft-deletenotDeleted) no longer leaks onto every other model
- Added Non-interactive scaffolding —
create-warlock <name> --yes(with--db,--pm,--features,--ai,--git,--jwt) scaffolds the entire app in a single command, no prompts - Added
--db=none/--no-dband a None option in the database prompt — scaffold with no database: the driver, its package, andsrc/config/database.tsare all skipped - Changed Starter models drop the baked-in
globalColumnsSchemaaudit columns (createdBy/updatedBy/deletedBy/isActive) — global columns are left to the developer
4.6.1 July 1, 2026
A production-hardening patch across @warlock.js/core, @warlock.js/logger, and @warlock.js/cascade — a fatal boot error now fails loudly instead of exiting 0, native Postgres array columns (TEXT[] / JSONB[]) work with no configuration, and a nested transaction() joins the active transaction instead of opening an isolated one that can't see its writes.
- Fixed Native Postgres array columns (
TEXT[]/JSONB[], fromarrayText()/arrayJson()) are now auto-detected by introspecting the schema on connect and bound as raw arrays — no more "malformed array literal" on insert and no need to hand-listnativeArrayColumns(which stays as an optional per-connection override, now consulted per-table) - Fixed
transaction()now flat-nests: a nestedtransaction()joins the active one (same session, sees its uncommitted writes) instead of opening a second, independent transaction — fixes phantom foreign-key violations when a service that opens its own transaction is called inside an outer one (e.g. a seeder creating a row, then a service inserting a child that references it). MongoDB joins too, replacing its "nested not supported" throw
- Fixed a fatal
uncaughtExceptionat production boot (e.g. a config file that throws) is no longer swallowed into a silentexit 0— bootstrap now wires the crash handler to exit non-zero in production sowarlock startsurfaces the failure; the dev server still logs-and-continues for HMR
- Changed
captureAnyUnhandledRejection()now exits the process non-zero after anuncaughtException(and prints the stack toconsole.errorwhen no terminal channel is configured) so a fatal error at boot is never silently swallowed into a cleanexit 0— opt out with{ exitOnUncaughtException: false }where the process recovers on its own (e.g. a dev server using HMR).unhandledRejectionis unchanged (logged aterror, never exits).
4.6.0 July 1, 2026
The AI framework closes every remaining capability gap. Output modalities land — ai.image(), ai.speech(), and ai.transcribe(), plus the new @warlock.js/ai-live package for ai.realtime() duplex voice + ai.video(). RAG gains first-party vector stores and document loaders (ai.rag.pgVectorStore on Postgres/pgvector, ai.rag.loadWeb / loadPdf / loadHtml / loadText). Durable mid-run crash-resume comes to agents and planners (agent.resume() / planner.resume() via an opt-in durable store). And provider breadth doubles with four new OpenAI-compatible adapters — @warlock.js/ai-mistral, ai-groq, ai-deepseek, and ai-xai.
- Added
ai.image(params)— image generation, the first verb of the output-modality track (Theme I). Wraps anImageModelContractin the uniform never-throws{ data, error, usage, report }envelope, with cost-truth (per-token forgpt-image, per-image for DALL·E / Imagen) folded into the sameUsage.costrollup and atype: "image"report routed to observers. Ships on the OpenAI + Google adapters. - Added
SDKAdapterContract.image?(config)— the image-model capability seam, mirroringembedder?(). AddsImageModelContract,GeneratedImage(discriminatedbase64|url),ImageModelPricing, andImageGenerationOptions. - Added
MockSDK().image(...)+MockImageModel— deterministic image doubles (scriptable responses, recorded calls, pricing) for tests. - Added
ai.speech(params)+ai.transcribe(params)— text-to-speech and speech-to-text, the audio verbs of the modality track. Same uniform never-throws envelope + cost-truth (per-character / per-minute / per-token). NewSpeechModelContract/TranscriptionModelContractonSDKAdapterContract.speech?()/transcribe?(), plusMockSpeechModel/MockTranscriptionModel. - Added
ai.audioFromFile(path)/ai.audioFromBuffer(bytes, mediaType)/ai.audioMediaTypeForFilename(name)— non-AI utilities that package audio (WhatsApp.ogg/.opus, iOS.m4a, …) into theAudioInputshapeai.transcribeconsumes. - Added
ai.rag.pgVectorStore({ client })— a Postgres + pgvector vector store satisfyingVectorStoreContract(upsert / query / removeNamespace), with anensureSchema()DDL helper and a lazypgoptional peer. - Added
ai.rag.loadText/loadHtml/loadWeb/loadPdf— document loaders producingRagDocuments for.index().loadWebis SSRF-safe (routes throughguardedFetch/OutboundPolicy);loadPdfuses a lazypdf-parseoptional peer. - Added Durable mid-run crash-resume — opt-in
durable: { store, deleteOnComplete? }onai.agent/ai.plannerwith a stablerunId+agent.resume(runId)/planner.resume(runId). Per-trip (agent) / per-node (planner) checkpoints reuseai.snapshot.{memory,pg,redis}; drift detection viaAgentDriftError/PlannerDriftError(bypass with{ force: true }); completed work never re-runs its tools and usage is never double-counted. - Added **
ai.rag.*namespace** now also carrieschunk,cacheVectorStore,pgVectorStore,loadText/loadHtml/loadWeb/loadPdf,bm25Rank,reciprocalRankFusion,hybridRank,multiQuery(previously standalone-only exports), forai.*-namespace consistency.
- Added First release — the live & generative rich-media add-on for
@warlock.js/ai, kept in its own package so core text/image/speech stay dependency-light. A side-effect import (import "@warlock.js/ai-live") mountsai.video+ai.realtimeonto the sharedAifacade. - Added
ai.video(params)— text-to-video (Sora / Veo / Kling-class); the provider's async submit→poll job hidden behind the uniform never-throws{ data, error, usage, report }envelope, with per-second cost-truth folded intoUsage.costand atype: "video"report routed to observers. - Added
ai.realtime(options)— a stateful duplex voice session over a pluggableRealtimeTransport:sendAudio/sendText/events()out,close()→RealtimeReportfor the cost/observability surfaces. - Added Contracts —
VideoModelContract,GeneratedVideo,VideoModelPricing,VideoOptions;RealtimeSession,RealtimeTransport,RealtimeConnection,RealtimeEvent,RealtimeReport,RealtimeOptions. - Added Mocks —
MockVideoModel+MockRealtimeTransportfor deterministic, HTTP- and socket-free tests (scripted responses / event streams, recorded calls).
- Added
openai.image({ name })— image generation for thegpt-image-*(token-metered) anddall-e-*(per-image) families, for use withai.image(). A non-image model id is rejected at construction. - Added PDF + audio input.
pdfandaudiocontent parts now map to OpenAIfile(base64file_data) andinput_audio(wav/mp3) parts — opt in withmodel({ pdf: true })/{ audio: true }. A remote-URL pdf/audio source raises a typedInvalidRequestErrorup front. - Added
openai.speech({ name })— text-to-speech for thetts-1/tts-1-hd/gpt-4o-mini-ttsfamilies (audio.speech.create), for use withai.speech(). - Added
openai.transcribe({ name })— speech-to-text for thewhisper-1/gpt-4o-transcribefamilies (audio.transcriptions.create), for use withai.transcribe().whisper-1defaults toverbose_json(duration + segments); a non-TTS/STT model id is rejected at construction. - Fixed Non-text content parts are no longer coerced to
image_url. The message mapper now branches per modality (image →image_url, pdf →file, audio →input_audio) instead of forcing every attachment through the image path.
- Added First release.
MistralSDK— a thin wrapper over@warlock.js/ai-openaithat points one internalOpenAISDKat Mistral's OpenAI-compatible endpoint (https://api.mistral.ai/v1) withprovider: "mistral", delegating transport, streaming, tool calls, structured output, error wrapping, and token accounting to the battle-tested adapter. Exposes.model(),.embedder()(mistral-embed), and.count(). - Added Mistral-aware capability inference —
visionis auto-set for thepixtralfamily and recent multimodal generations (mistral-large,mistral-medium,ministral-3);reasoningfor themagistralfamily and the hybridmistral-smallgeneration. An explicitvision/reasoningon.model()always wins. Exported asinferVisionCapability/inferReasoningCapability, with the-latestaliases grouped underMISTRAL_MODELS. - Added Default pricing registry (
MISTRAL_DEFAULT_PRICING, USD per 1,000,000 tokens) merged under any caller-suppliedpricingso cost truth works out of the box; per-model > SDK-level > default >undefined. Noimage()— Mistral has no OpenAI-compatible image endpoint.
- Added
google.image({ name })— Imagen (imagen-*) image generation for use withai.image(). Per-image-metered; when every candidate is safety-filtered the run surfaces a typedContentFilterError. A non-Imagen model id is rejected at construction. - Fixed PDF + audio input are now explicitly mapped and tested. The content-part mapper documents and proves that
pdf/audioparts route to GeminiinlineData(thepdf/audiocapabilities the adapter advertises are backed by a real mapper, not an accident of the image path), and the remote-URL rejection now names the actual modality instead of always saying "images".
- Added release-hygiene tests: version↔changelog invariant + generator-stub import check
- Added
router.routeCount()exposes the number of registered routes as a boot/readiness signal - Added
health.addRoutesRegisteredCheck(getRouteCount)registers a readiness check that reports not-ready when a booted HTTP app has zero routes - Added seeders now receive a
{ track }context —track(model),track(models[]), andtrack(table, id)register created records (each call returns its argument so it can be chained inline);recordsCreatedis auto-derived from the track count - Added
seed_recordstable (created via the newSeedRecordsTableMigration) records every tracked seed reference within the same transaction the seed runs in; only the last run's refs are kept per seeder - Added
warlock seed --drop [name]undoes a seed: deletes its tracked records in reverse run/insertion order inside a transaction, then resets the matching seeds-log rows soonce: trueseeds re-run; scope to one seeder with--drop=<name> - Added
Seeder.dependsOnis now resolved — seeders are topologically sorted so dependencies run before dependents, layered over the numericordertie-break; throwsUnknownSeederDependencyErrorfor a missing dependency andSeederDependencyCycleErrorfor a cycle - Added seeders receive an injectable clock and a meaningful batch size —
run({ track, now, batchSize });now()(default() => new Date()) drives both seed data and the seeds-log timestamps so historical/back-fill runs are deterministic, andbatchSizesurfaces the seeder's ownbatchSizeforModel.createMany(rows, { batchSize }) - Added repository-level aggregation —
aggregate(),sum(),avg(),min(),max(), andgroupBy()onRepositoryManager, each reusingfilterBy(and its operator-injection guard),where, and scopes before the aggregate, exactly likecount() - Added
warlock doctor— a read-only diagnostics command that runs routes / config / connectors / optional-peers / health / release-hygiene checks and prints a pass/warn/fail report (exits non-zero on any failure, never opens a DB/cache/socket connection) - Added
warlock routes— a read-only command that lists the registered HTTP routes as a verb-colored table (method / path / name / action / middleware-count / source); filter with--method/--path/--name, or emit the normalized rows as JSON with--json. Boots app code to register routes but starts no connectors - Changed
Seeder.runnow receives aSeedContext(run(ctx)) — backward compatible, an existing zero-argrun()keeps working unchanged - Fixed route-module load/registration failures are no longer swallowed: a route file that throws on import or registration now surfaces loudly instead of silently 404'ing the whole surface
- Fixed
ModuleLoader.loadModulerethrows after logging (wrapped in a newModuleLoadErrorcarrying the failing file + cause), so a broken module aborts boot and is caught loudly by the HMR batch-reload handler in dev - Fixed
ModuleLoader.loadAllaggregates per-file failures and throws anAggregateErrorat the end, so one broken module no longer hides the others - Fixed
router.withSourceFilerethrows the callback error after logging instead of consuming it with a bareconsole.log(thetry/finallysource-file stack cleanup is preserved)
- Added Fast bulk
Model.createMany(data, options?: { batchSize?; bulk? })— both paths chunk bybatchSize(default 500);bulk: trueroutes each chunk to the driver's native multi-rowinsertManyfor 10–100× throughput (skips per-row hooks/events; default path preserves them) - Added
IdGeneratorContract.generateNextIds({ table, count })— reserve a contiguous block of auto-increment ids in a SINGLE atomic op (MongoDB).Model.createMany(default + bulk) now reserves one id block per chunk instead of one counter round-trip per row; engages only for fixed-increment, auto-generated, id-less rows (random-increment or caller-supplied-id rows fall back to per-row generation) - Added
QueryBuilder.groupByDate(column, unit, aggregates?)— portable date-bucketedGROUP BY(day/week/month/year) across Postgresdate_truncand MongoDB$dateTrunc - Added
$agg.sum(expr)now also accepts a typed column expression ($expr.mul/$expr.add/$expr.sub/$expr.div/$expr.col/$expr.lit) so you can sumprice * quantity; bare-string payload is unchanged. Added$agg.sumRaw(expression)raw escape hatch (PostgresSUM(<raw>); throws on MongoDB) - Added Column-expression DSL grouped under a single
$exprobject (mirroring$agg) —$expr.col/$expr.lit/$expr.mul/$expr.add/$expr.sub/$expr.div/$expr.raw— plusisColumnExpression/toColumnExpressionand theColumnExpression/ColumnExpressionInputtypes - Added MongoDB id counter (
MasterMind) now has a lazily-ensured unique index on{ collection: 1 }plus a bounded retry on duplicate-key (E11000), closing the cold-start race where two concurrent first inserts into a new collection could reserve overlapping ids/blocks - Added
$agg.countDistinct(field)— a cross-driver grouped distinct-count aggregate (PostgresCOUNT(DISTINCT col); MongoDB$addToSetin$groupfinalized with$sizein the renaming$project) - Added
Model.raw<T>(sql, params)— typed, transaction-aware raw query that auto-joins the activetransaction()scope and returnsRawQueryResult<T> - Added
DataSource.raw<T>(sql, params)— thin transaction-aware passthrough todriver.query - Added Postgres connection option
nativeArrayColumns— opt out listed columns (JSONB[]/TEXT[]/…) from JSON-text encoding so genuine native-array columns keep their{...}literal form - Changed
DriverContract.query<T>()is now typedPromise<RawQueryResult<T>>(newrows+rowCountresult type) instead ofPromise<any> - Fixed Postgres
json/jsonbcolumns no longer corrupt: object-arrays, string-arrays, mixed arrays, empty[](previously stored as{}), and plain objects are now JSON-encoded before binding instead of falling through to a Postgres array literal; the same encoding is applied on the UPDATE$setpath. The pgvector all-number array form is preserved. - Fixed Insert no longer overwrites a caller-supplied
createdAt— a backdated value (imports/migrations) is now honored, mirroring the upsert guard, whileupdatedAtis always stamped at persist time - Fixed Insert validation now whitelists the system columns (
id/_id/timestamps/deletedAt) like the update path, so a backdatedcreatedAtsurvives strictstrip/failmode instead of being dropped before reaching the writer - Fixed Corrected the MongoDB id-generator docs that falsely claimed the counter write "participates in active transactions" — it is a standalone, immediately-durable write (no transaction session is attached), so a rolled-back insert leaves the consumed id as a gap, exactly like SQL
SERIAL
- Added First release. DeepSeek adapter for
@warlock.js/ai— a thin wrapper over@warlock.js/ai-openai'sOpenAISDKpinned tohttps://api.deepseek.com(provider: "deepseek"), so all wire behavior (streaming, tool calls, structured output, error wrapping) is inherited unchanged. - Added
DeepSeekSDK—.model()/.embedder()/.image()/.count()delegated to the wrapped client.baseURLandproviderare optional (default to DeepSeek's endpoint / label); every otheropenaiClientOptionsvalue is forwarded verbatim. - Added DeepSeek-specific capability inference (
inferReasoningCapability/inferVisionCapability) —reasoningis auto-truefordeepseek-reasonerand the*-protier, auto-falsefordeepseek-chat/*-flash;visionisfalsefor every id (no documented vision surface). An explicitreasoning/visionper model always wins. - Added Built-in DeepSeek pricing defaults (USD per 1M tokens) for
deepseek-chat,deepseek-reasoner,deepseek-v4-flash,deepseek-v4-pro, sousage.costis computed out of the box; overridable per model or per SDK. - Added
DEEPSEEK_CHAT_MODELS— informational list of the documented chat model ids.
- Added First release. xAI Grok adapter for
@warlock.js/ai— a thin wrapper over@warlock.js/ai-openai'sOpenAISDKpinned tohttps://api.x.ai/v1(provider: "xai"), so all wire behavior (streaming, tool calls, structured output, error wrapping) is inherited unchanged. - Added
XaiSDK—.model()/.embedder()/.image()/.count()delegated to the wrapped client.baseURLandproviderare optional (default to xAI's endpoint / label); every otheropenaiClientOptionsvalue is forwarded verbatim. - Added xAI-specific capability inference (
inferVisionCapability/inferReasoningCapability, exported alongsideXAI_VISION_MODEL_PREFIXES/XAI_REASONING_MODEL_PREFIXES) — Grok ids don't match OpenAI'sgpt-*/o*prefixes, sovisionis auto-trueforgrok-4/grok-2-visionandreasoningforgrok-4/grok-3-mini. An explicitvision/reasoningper model always wins. - Added
XAI_CHAT_MODELS— convenience list of current public Grok chat ids (grok-4,grok-3,grok-3-mini,grok-2-vision,grok-2). - Added Optional per-model pricing registry — resolution at
model()time is per-modelpricing> SDK registry >undefined.
- Added First release.
GroqSDK— a thin wrapper over@warlock.js/ai-openaithat points one internalOpenAISDKat Groq's OpenAI-compatible endpoint (https://api.groq.com/openai/v1) withprovider: "groq", delegating transport, streaming, structured output, error wrapping, and token accounting to the battle-tested adapter. Serves Groq-hosted open models (llama-3.3-70b-versatile,llama-3.1-8b-instant,openai/gpt-oss-*,deepseek-r1-distill-llama-70b) on LPU hardware via.model()and.count().GROQ_BASE_URL/GROQ_PROVIDER/GROQ_KNOWN_MODELSexported. - Added Groq-aware capability inference — because Groq ids are upstream open-weight names, not OpenAI's, the wrapper carries its own lists:
visionis auto-set forgpt-oss/llama-4/llama-3.2-*-vision;reasoningforgpt-oss/deepseek-r1/qwq/qwen3. An explicitvision/reasoning/structuredOutputalways wins. Exported asinferVisionCapability/inferReasoningCapability. - Added Default pricing registry (USD per 1,000,000 tokens) for the known Groq models as the final fallback; resolution per-model > SDK-level
pricing[name]> built-in default >undefined. No embeddings endpoint on Groq (.embedder()is delegated for symmetry but calls fail upstream) and noimage().
- Fixed Warn in development when jobs are registered but
start()is never called — a one-shot deferred check logsN job(s) registered but scheduler.start() was never called, is suppressed oncestart()runs or in production (NODE_ENV=production), and is unref'd so it never holds the process open.
4.5.0 July 1, 2026
The biggest AI release yet — the agent-platform, prompt-unification, and team-identity milestones plus a security-hardening pass, consolidated into one release. @warlock.js/ai grows from a primitive ladder into a full agent platform: ai.rag(), ai.team(), ai.skills(), a unified ai.prompts registry (ai.prompt is now a facade over it), ai.dataset() / ai.vcr(), a planner that runs DAGs and can pause for approval, a generic observe seam, the former ai-human + ai-guard satellites folded into core, and a security pass (SSRF-safe outbound I/O, redaction, attachment policy, SSE serving, streaming structured output). @warlock.js/ai-panoptic becomes batteries-included with a redesigned, auth-gated local dashboard (group-by-type, cost heatmap, timeline, search / filter, restart-persistent store) and first-class team-type traces. The first releases of @warlock.js/ai-tools (ready-made tools + MCP) and @warlock.js/ai-workspace (a policy-jailed coding workspace), plus a broad @warlock.js/core hardening pass.
- Added
ai.rag(config)— retrieval-augmented generation in core: a chunk → embed → retrieve → cite pipeline that reuses your existing embedder and cache, with zero new dependencies. Includes hybrid retrieval (dense + BM25 reciprocal-rank fusion), keyword / LLM rerankers, and multi-query expansion. - Added
ai.team(config)— manager-led multi-agent teams: thin sugar overai.supervisorfor the review-then-fix and test-then-fix shapes. - Added
ai.skills(config)— runtime agent skills with progressive disclosure: a cheap always-injected catalog plus an on-demandloadSkilltool. Adds askillsoption onai.agent. - Added
ai.streamObject(...)— structured-output streaming: partial-object snapshots as tokens arrive, with a strict final parse against the response schema. - Added
ai.serve(executable, options)— serve any agent / workflow / supervisor as an SSE HTTP endpoint. - Added Multimodal attachments —
ContentPartgainspdfandaudiovariants alongside text / image, resolved to provider-ready parts (PDF wired on the Anthropic and Bedrock adapters). - Added Planner DAG execution, re-planning, and plan-only approval — run independent steps concurrently, revise the plan when a step fails, or return a plan for approval before it executes.
- Added Generic
Observerseam — route any flow's run report to pluggable observers (e.g.@warlock.js/ai-panoptic) without coupling core to a backend. - Added
ai.prompts+ai.prompt— a process-wide registry of named, versionedsystemPrompt(...)builders (resolved byname@version/name@tag) withdefine/tag/diff/export/importand a unifiedvalidate(deterministic missing-placeholder check plus an optional Nova-safe LLM-judge);ai.promptis a thin facade over it. - Added
SystemPromptContractidentity + provenance —.meta({ name, version, description, required })(a name auto-registers inai.prompts),.merge(...blocks)/.merge(contract)/.merge(name, { fromVersion }), and deterministicmeta.composedFromlabels. - Added
ai.dataset(options)— filterable, shardable evaluation case sets that feedagent.eval, with baseline / regression detection and CI reporters. - Added
ai.vcr(model, options)— record / replay any model against an on-disk cassette for deterministic, offline tests, withrecordRequestmodes andredactRequest/redactResponse/redactErrorhooks. - Added
ai.agent.judge(config)— judge-safe agent preset (alsoai.agent({ judge: true })): lenient JSON parsing, bounded repair re-asks, and never-throw verdicts on Nova-class models. - Added Human-in-the-loop approval now ships in core (
ai.human.*, formerly@warlock.js/ai-human) — a tool-approval gate plus durable interrupt / resume. - Added Content guardrails now ship in core (
ai.guardrail.*, formerly@warlock.js/ai-guard) — PII / topic / injection / moderation detectors. - Added Orchestrator
sessionLock— per-session turn serialization (default in-process mutex keyed bysessionId, pluggable distributed lock) so concurrent same-session turns can't lose a checkpoint update. - Added Sub-agent trace nesting — a supervisor / team / orchestrator callback that calls
agent.execute()directly now nestscallback → agent → toolwith rolled-up usage / cost. - Added
AgentReport.systemPrompt— the resolved system prompt sent to the model is now recorded on the agent report. - Changed
ai.teamruns reporttype: "team"— a first-classReportType(was"supervisor") so observers distinguish team runs on the wire. - Changed Deterministic parallel workflow state merge — parallel children merge into the parent in declaration order (last-declared wins on a conflicting key) instead of completion order; an optional per-step
mergeStatereducer overrides it. - Changed Safer batch / RAG defaults —
ai.batchwarns once on a large unbounded run (pass an explicitconcurrencyor"unbounded");ai.ragacceptslimits(maxDocuments/maxChunks/maxBytes) that fail before any embedding spend. - Fixed Cancellation propagates through composite tools — a cancelled outer agent now aborts a nested agent / workflow / supervisor invoked via
.asTool()(the run signal threads into the nestedexecute). - Fixed Observer / event-handler errors are surfaced, not swallowed — a throwing observer or
onhandler stays isolated (never crashes the run) but is now warned once / routed to a hook instead of disappearing silently. - Fixed
budget({ maxCostUSD })fail-open closed — a cost cap with no matching model pricing now warns once (naming the model) instead of silently never tripping. - Security Shared
OutboundPolicy+redact()— one SSRF-safe outbound-fetch guard (scheme + host allowlist, post-DNS private-IP deny, max-bytes, timeout, injectable fetch) and one redaction utility, consumed across attachments, URL skills, VCR, and the error path. - Security Attachment trust boundary (
AttachmentPolicy) — remote-text attachment fetch is default-deny (opt in with a policy), local reads honor anallowedRootssandbox, and bare-string local paths warn (staged deprecation). - Security URL skill sources hardened — the manifest fetch runs through
OutboundPolicyand every record is runtime-validated before it enters model context; adds cache-TTL controls. - Security Guardrail coverage documented — input detectors inspect text only; non-text attachment content needs an attachment-level policy.
- Added Zero-setup local dashboard —
dashboard(store, options)serves a loopback-only mini-Langfuse (default127.0.0.1:4319) with no Docker and no account: light / dark / system theme, a two-pane drawer (nested call tree / detail), a metadata panel, colour-coded Title-case type labels, arrow-coded token rollups (↓input · ↑output · total), per-node rollup cost, and the Warlock logo. - Added Dashboard search / filter / grouping — client-side free-text search, status / type / session / prompt filter chips, an errors-only toggle, and group-by Session / Prompt / Type with a per-type aggregate-stats panel (count, failure rate, p50 / p95 latency, tokens, cost).
- Added Cost heatmap, timeline view, and deep-links — each node carries a cost-tinted accent; the drawer toggles between the call tree and a Gantt timeline (critical path highlighted); the open trace + span are reflected in the URL hash for shareable views.
- Added Prompt-version linkage — agent spans stamp
agent.promptName/agent.promptVersion; the dashboard filters and groups by the resolvedname@versionkey. - Added Cache-backed persistent trace store —
createCacheTraceStore(cache, options)persists traces through any@warlock.js/cachedriver, serves reads from an in-memory mirror, and re-hydrates onready()so traces survive a restart;ai.config({ panoptic: { cache } })wires it. - Added Declarative
ai.config({ panoptic })— configure panoptic once; it registers on core's observer registry and starts the dashboard. Per-flowobserve+observeAllopt flows into observation. - Added
ContentCaptureOptions.fullHistory— capture the agent's complete message history on the span; agent content is emitted as a[system, user]chat array; Langfuse gets trace-levelinput/output. - Added
onErrorhook — handle isolated exporter failures onpanoptic()/createCollector(). - Added Pure trace-list helpers exported from the package root (
filterTraces/groupBySession/groupByPrompt/groupByType/aggregateByType/rollupCost/ …) so your own views mirror the dashboard's rules. - Changed Title-case status & type labels across rows, drawer, chips, and group headers (underlying filter keys stay lowercase);
teamis a first-class dashboard type; type chips show only present types; drawer metadata keys are humanized; the group-by toggles became a singleGroupdropdown. - Fixed Isolated exporter failures no longer fail silently — a failing exporter still never crashes the run, but now warns once (or calls
onError). - Security Dashboard hardening — bearer-token auth (
authToken, required when binding off-loopback), aHost-header allowlist (DNS-rebinding guard), and security response headers (nosniff/X-Frame-Options: DENY/ locked-down CSP) on every response. - Security Error redaction — captured error
message/stackare scrubbed of secrets (Bearer tokens, API keys) and a retainedcauseis deep-redacted (auth / cookie headers stripped) before a trace is stored or exported.
- Added **Five ready-made agent tools, attached to the shared
aiobject underai.tools.*via adeclare module "@warlock.js/ai"augmentation, so a bareimport "@warlock.js/ai-tools"makes them available and statically typed. Each returns aToolContractthat drops straight intoai.agent({ tools: [...] }): -ai.tools.webSearch(options)(web_search) — web search via a chosen provider (tavily/brave/serpapi) over the globalfetch; the API key falls back toTAVILY_API_KEY/BRAVE_API_KEY/SERPAPI_API_KEY;maxResultsis clamped per call. -ai.tools.fetchUrl(options?)(fetch_url) — fetch a URL and return its content as readability-extractedtext(default), rawhtml, ormarkdown, with a host allowlist (SSRF guardrail), a byte cap (truncatedflag), and a request timeout. -ai.tools.http(options?)(http_request) — a guarded HTTP/REST client: method + host allowlists enforced before the network call, optionalbaseUrljoin, static-header merge, byte cap, and timeout; JSON-parses a JSON response body. -ai.tools.calculator(options?)** (calculator) — a SAFE arithmetic evaluator (+ - * / % ^, unary signs, parentheses, decimal/scientific literals) implemented with a shunting-yard pass — it never callseval/Function. -ai.tools.dateTime(options?)(date_time) — clock/calendar operations:now/add/diff/formatover ISO-8601 instants, with millisecond-based units and IANA time-zone rendering.diffacceptsfromas an alias for the start instant (isowins when both are set). - Added MCP client —
ai.mcp(server, options?)(Direction A). Connects to an external MCP server over a stdio (node:child_process+node:readline, no dependency) or Streamable HTTP transport, runs theinitializehandshake +tools/list, and adapts each remote tool into a nativeToolContract(its JSON Schema wrapped as a Standard Schema,tools/callasexecute). SupportsnamePrefix,filter, and a per-calltimeoutMs; anisErrorresult surfaces as{ error }data. - Added MCP server —
ai.mcp.serve(source, options)(Direction B). Exposes a built agent / supervisor / orchestrator (or a rawToolContract[]) AS an MCP server:tools/listemits each tool'sinputSchemaviaextractJsonSchemaat the configuredschemaTarget(defaultdraft-2020-12), andtools/callroutes tocontract.invoke(), mappingdatato a text content block anderrorto anisError: trueresult. The stdio transport is auto-pumped overprocess.stdin/process.stdout; the pure protocol core is also exported ascreateServeHandlerfor a host's own HTTP wiring. - Added Typed error classes —
WebToolError,HttpPolicyError,CalculatorError,DateTimeError, andMcpTransportError, each extending the@warlock.js/aiAIErrorbase with atypediscriminator. Every tool follows the errors-as-data contract: failures are thrown insideexecute, wrapped bytool(), and reach the model as{ error }so the agent self-corrects instead of crashing the run. - Added Optional peers, lazily imported. Heavy dependencies — a search provider (
@tavily/core), the readability scraper (@mozilla/readability+jsdom), the MCP SDK (@modelcontextprotocol/sdk), and the JSON-Schema validator (ajv) — are optional peers,import()ed only when the relevant path runs, surfacing a curatednpm installstring when absent rather than crashing at import time. The only required runtime peer is@warlock.js/ai; everything else is Node built-ins + the globalfetch(Node 18+).
- Added
ai.workspace(policy)— the workspace verb, registered on the sharedaiobject via adeclare module "@warlock.js/ai"augmentation + a runtime side-effect on import (no edit to@warlock.js/ai's own source). Exported as theworkspacefactory;WorkspaceCapableAiis the typed view consumers castaithrough. - Added Policy jail — every path is
realpath-resolved and must sit undercwd(or anallowPathsroot);denyPathsglobs are blocked even insidecwd; the shell allow/deny list gates each command's leading executable basename (deny wins, fail-closed); per-command timeout + output byte cap; andprocess.envis never inherited wholesale (opt-inshell.inheritEnv, plus explicitshell.env). - Added Seven agent-facing tools under
ws.tools.*, each aToolContractbuilt on the coretool()factory:read_file,edit_file,write_file,run_shell,run_tests,grep,glob. Each factory takes an optional{ name }(andrun_testsa{ command }) override. - Added
tools.all()— every tool in canonical order — andtools.pick(...names)— a least-privilege subset (e.g.pick("readFile", "grep", "glob")for a reviewer). - Added Direct programmatic methods sharing the same policy seam:
readFile/writeFile/editFile/exec/grep/glob/exists/mkdir/remove. - Added
readonly()— a projection that vends only the read/grep/glob tools and rejects every mutating direct method with aWorkspacePolicyError. - Added
scope(subdir)— a sub-jailed workspace rooted atsubdir(narrowedcwd, same sub-policies and backend selection). - Added Read-before-edit guard —
read_file/readFilereturn a SHA-256 contenthash(via@warlock.js/fshashString);edit_filerequires an exact, uniqueoldString(orreplaceAll) and rejects a mismatchedexpectHashas stale. - Added Backends —
createLocalBackend(default"local";@warlock.js/fsfor IO +node:child_processfor the shell) andcreateMockBackend(in-memoryMap+ scriptedexec, for hermetic disk-free tests), behind theWorkspaceBackendcontract. - Added Policy engine seam exports:
resolveInJail,isCommandAllowed,buildEnv, and theResolvedPathtype. - Added Ops layer export
createOps— the single policy-enforced operation layer both the tools and the direct methods funnel through. - Added Typed errors
WorkspacePolicyError(type: "path-escape" | "denied-command") andWorkspaceEditError(type: "not-found" | "not-unique" | "stale-hash"), both extending the@warlock.js/aiAIErrorbase (codeTOOL_EXEC_FAILED) so failures surface to the agent as tool-error data, never thrown run-killers. - Added Public type surface re-exported from the barrel:
Workspace,WorkspaceTools,WorkspacePolicy,WorkspaceShellPolicy,WorkspaceReadPolicy,WorkspaceBackendType,WorkspaceToolName,WorkspaceOps,WorkspaceBackend(+WorkspaceBackendExecOptions/WorkspaceBackendExecResult), and the per-tool IO shapes (ReadFileInput/Result,EditFileInput/Result,WriteFileInput/Result,RunShellInput/Result,RunTestsInput,GrepInput/Match/Result,GlobInput/Result). - Added
scripts/generate-llms.mjsand the generatedllms.txt/llms-full.txtprojections ofskills/. - Added
skills/use-a-workspace/SKILL.md— building a jailed workspace and operating it (policy, the seven tools, the direct methods,readonly/scope). - Added
skills/build-loop-agent/SKILL.md— wiringws.tools.all()into a coding agent that reads → edits → runs tests until green.
- Changed dev-server update notice now fires immediately on
warlock dev— the check is spawned in parallel with server startup instead of awaiting it, so the notice surfaces as soon as npm responds - Changed raised the update check's npm registry timeout from 2.5s to 30s, so a slow connection no longer drops the notice
- Changed repository lifecycle hooks (
onCreating/onCreate/onUpdating/onSaving/onDeleting/ …) now run oncreate/update/delete— they were defined but never invoked - Changed
repository.list()/all()now honor thesortBy,sortDirection, andpurgeCacheoptions — previously accepted but silently ignored - Fixed
response.sendFile({ filename })andresponse.download()no longer 500 on non-ASCII file names — theContent-Dispositionheader is now RFC 6266-encoded (a sanitized ASCIIfilenamefallback plus an RFC 5987filename*=UTF-8''…), so an Arabic / emoji / UTF-8 download name streams correctly instead of throwing Node'sERR_INVALID_CHAR - Fixed local storage paths are contained to their disk root —
../traversal segments and absolute paths can no longer escape the configured directory - Fixed
storage.putFromUrladds SSRF guards — private / loopback / link-local hosts are rejected and the fetched body is size-capped - Fixed S3 / R2 / DigitalOcean Spaces
url()no longer produces a malformed double-host URL whenurlPrefixis set - Fixed cloud
deleteDirectorypaginates via the list continuation cursor instead of re-listing the first page - Fixed local-storage metadata cache is invalidated on write / delete — it was serving a stale size / modified-time
- Fixed the cloud driver no longer reports a misleading "SDK not installed" error when the AWS SDK is in fact present (driver load race)
- Fixed repository
countCached/countActiveCachednow cache and return correctly — anullcache miss was being returned as the count - Fixed repository
firstCached/lastCachedno longer fetch and cache the entire table to return a single row - Fixed repository boolean filters no longer coerce
false/0totrue - Fixed repository cache keys are now order-independent (stable key serialization)
- Fixed router groups restore prefix / name / middleware state via
try/finallyeven when the group callback throws - Fixed
router.any()/allroutes now match every HTTP verb under the dev server — they previously matched only GET and POST, diverging from production - Fixed the HTTP concurrency limiter releases its slot on every response path (
noContent, redirect, file, buffer) — a throwing or non-sendhandler no longer leaks a permit and permanently 429s the route - Fixed the cached-response middleware replays a hit through
response.replay()instead of re-sending an already-sent reply, preserving status and content-type - Fixed
onSentcache writes in the idempotency and cache middleware are error-handled — a cache-backend failure no longer surfaces as an unhandled rejection - Fixed
X-Forwarded-Foris parsed to its first hop, so IP-filter / rate-limit / idempotency scoping cannot be spoofed with extra header hops - Fixed the
maintenancemiddleware allowlist matches request paths that carry a query string - Fixed use-cases run their
aftermiddleware and broadcast for avoidhandler, and a failed history write no longer fails an otherwise-successful call - Fixed the use-case retry counter reports the correct count on total failure
- Fixed the socket connector no longer double-closes the shared HTTP server during graceful shutdown
- Fixed the cache connector disconnects its drivers on shutdown — an open Redis connection was left dangling
- Fixed generator stubs import
v/Inferfrom@warlock.js/seal(core never re-exported them), so generated models compile and run - Fixed
warlock devhot-reloads when a file is emptied or saved with no trailing newline — a stale no-op-change check was silently dropping those saves before they reached HMR - Removed presigned-upload
maxSizeoption — a presigned PUT URL cannot enforce a size cap, so the option was a false guarantee
- Fixed All upstream
ClientOptionsnow reach the Anthropic client. The SDK constructor peels off the framework-onlyprovider/pricingkeys and forwards the rest (timeout,maxRetries,defaultHeaders, customfetch,baseURL, …) verbatim, instead of dropping everything butapiKey/baseURL.
- Fixed All upstream
ClientOptionsnow reach the OpenAI client. The SDK constructor peels off the framework-onlyprovider/pricingkeys and forwards the rest (timeout,maxRetries,defaultHeaders, customfetch,organization,project, …) verbatim, instead of dropping everything butapiKey/baseURL.
4.4.0 June 21, 2026
@warlock.js/core completes the production lifecycle — a booted hook, a shutdown hook, graceful HTTP draining, and built-in /health + /ready endpoints for zero-downtime deploys. Plus AI fixes across @warlock.js/ai, @warlock.js/ai-openai, and @warlock.js/ai-panoptic (opt-in content capture, corrected Langfuse token accounting).
- Added
Application.onceBooted(cb)— run a callback once the app is fully booted (fires immediately if already booted) - Added
Application.whenBooted()— promise that resolves with the boot context when the app is fully booted - Added
Application.isBooted— whether the app has finished booting - Added
Application.onShutdown(cb)— run teardown once on shutdown, before connectors stop (mirror ofonceBooted) - Added
Application.isShuttingDown— whether shutdown has begun - Added built-in
/health(liveness) and/ready(readiness) endpoints with ahealthcheck registry (health.addCheck) - Added graceful HTTP shutdown — drains in-flight requests on shutdown, bounded by
http.gracefulShutdown.timeout - Added
http.health.*config to toggle or rename the health endpoints - Fixed connector shutdown no longer reverses the connector list in place (could corrupt order on a repeated shutdown)
- Fixed Planner: OpenAI strict structured-output
400. The generated plan schema now lists every property inrequiredand dropsminItems/maxItems, soai.planner()no longer fails against OpenAI strictjson_schemamode. - Fixed Report / result types no longer collapse to
neverunder strict TypeScript. The narrowing report / result types now override the discriminant viaOmit<…>instead of intersection. Type-only — no runtime change.
- Added Opt-in content capture.
panoptic({ captureContent, redactContent })copies the agent prompt / response and each tool's args / result onto spans, surfaced by the console (io), file, OTel (gen_ai.prompt/gen_ai.completion), and Langfuse exporters. Off by default; aContentRedactormasks each value. - Fixed Langfuse token accounting — generations now meter their own usage (rolled-up minus children) and the root execution is metered, so the trace total no longer double-counts nested spans.
- Fixed Strict structured-output compatibility check is now recursive. A schema that omits a
requiredproperty anywhere in the tree degrades to loosejson_objectinstead of400-ing; client-side validation still enforces the full shape.
- Changed Documented
model.uuid— the accessor returns the model's primary id asstring(wheremodel.idisstring | number); the name is historical and performs no UUID validation.
4.3.0 June 21, 2026
Self-update tooling — @warlock.js/core gains the warlock update command and a new-release notice in warlock dev. @warlock.js/ai caps the primitive ladder with ai.orchestrator(), ai.planner(), and ai.memory(), plus a cost-truth pass across every provider. The new @warlock.js/ai-panoptic package adds observability. ⚠ Breaking: snapshot persistence moves from a CacheDriver to the dedicated SnapshotStore.
- Added
warlock update— update every@warlock.js/*package in package.json to its latest version (operator preserved), then run the detected package manager's install - Added dev-server update notice —
warlock devchecks npm on start and prints a one-line notice when a newer@warlock.js/coreis published - Added
devServer.checkForUpdatesconfig flag (defaulttrue) to toggle the dev-server update notice - Added
fetchLatestVersion()andisNewerVersion()registry/version utilities - Fixed
warlock dev --skip-typingsand--skip-healthlong-form flags now work (were silently ignored)
- ⚠ BREAKING Supervisor + workflow snapshot persistence moved from
CacheDriverto the dedicatedSnapshotStorecontract. The per-primitive fallback is nowai.config({ defaultSnapshotStore }). Migration: replacesnapshotStore: cache.driver("redis", { client })withsnapshotStore: ai.snapshot.redis({ client })(andai.snapshot.{memory,pg}for the other tiers). - Added
ai.orchestrator()— stateful session manager over a supervisor: durable session / history / context, drift detection, history compaction, resume, and a command surface (orchestrator.asTool(), a 3-tier event surface, andOrchestratorContract/ config / error types). - Added
ai.checkpoint.{memory,pg,redis}()andai.snapshot.{memory,pg,redis}()— durable orchestrator-session and supervisor / workflow run stores, with matchingdefaultCheckpointStore/defaultSnapshotStoreconfig fields. - Added
ai.memory()— agent-memory store with four tiers: working (in-run scratch), semantic (durable facts), episodic (durable, recency-blended events), and procedural (durable, reinforcement-blended how-tos). Wired into the orchestrator via amemory?field. - Added
ai.planner()— an LLM generates an ordered plan over your registered capabilities, then executes it step-by-step. - Added
ai.spawnSubAgent()— one-shot delegation to a fresh single-use agent with an optional per-task budget; usable from a planner step, a tool, or a workflow. - Added Cost-truth contract surface across all five adapters —
Usage.reasoningTokens, per-channelModelPricing, andModelCallOptions.{reasoning, cacheControl}(ignored by adapters that lack the capability). - Added DX helpers —
ai.router(),ai.fanOut(),ai.batch(),ai.fallbackModel(),ai.mockRouter(),agent.eval()+ built-inai.eval.*scorers, Vitest matchers (registerAiMatchers()), supervisor-level middleware, andai.systemPrompt.fromFile(path). - Added Executables passed in an agent's
tools: [...]are auto-adapted into tools (workflows / supervisors / orchestrators compose directly via.asTool()).
- Added
panoptic()— the one-call subscriber factory: builds a collector, registers exporters, and feeds traces viaattach(),middleware(), orcollect(). - Added Exporters —
consoleExporter(),fileExporter()(JSON-Lines),otelExporter()(GenAI semantic conventions), andlangfuseExporter().@opentelemetry/*andlangfuseare optional peers, lazily imported. - Added
createInMemoryTraceStore()— a queryable in-memory trace store (query/aggregateby runId, sessionId, status, time window; optionalcapacityFIFO cap) that doubles as an exporter. - Added Vendor-neutral trace contracts (
Trace/TraceSpan/CollectorContract/ExporterContract) derived 1:1 from the coreBaseReporttree, plus skills for observing, exporting, and querying traces. - Fixed Failed root runs now carry their error in every export (threaded from the result envelope onto the root span).
- Fixed Per-span exporters now receive every span (the collector walks the finalized tree).
- Fixed
panoptic().middleware()now works on a supervisor (the middleware declares asupervisorhook map).
- Added Usage accounting —
usage.cacheWriteTokensis populated from Anthropic'scache_creation_input_tokens(alongsidecachedTokens);reasoningTokensis left unset because Anthropic bills thinking insideoutput_tokens. - Added Extended thinking —
ModelCallOptions.reasoningmaps to Anthropic'sthinkingbudget (reasoning.effort→ a tiered budget, floored at 1024);temperatureis dropped when thinking is enabled. - Added System-prompt prompt caching —
cacheControl.breakpoints >= 1emits the system prompt withcache_control: { type: "ephemeral" }. - Added Capabilities —
reasoning,promptCaching, andpdfare now advertised;audiostays absent.
- Added Cost-truth capabilities —
reasoning,promptCaching,pdf, andaudioare reported truthfully per model family (inferred from the model id, overridable viabedrock.model(...)). - Added Reasoning / extended thinking —
ModelCallOptions.reasoningmaps to Conversethinkingfor reasoning-capable models, and no-ops elsewhere so unsupported params never reach the wire. - Added Prompt-cache write breakpoints —
cacheControl.breakpointsappends a ConversecachePointblock for caching-capable models. - Added
Usage.cacheWriteTokenspopulated from ConversecacheWriteInputTokens;reasoningTokensis left unset (Bedrock reports no reasoning channel).
- Added
Usage.reasoningTokensis populated from Gemini'sthoughtsTokenCount(alongsidecachedTokens), surfaced only when reported> 0. - Added
ModelCallOptions.reasoningmaps to Gemini'sthinkingConfig(maxTokens→thinkingBudget,effort→ a bucketed budget) for reasoning-capable models. - Added
ModelCapabilitiesnow reportsreasoning,promptCaching,audio, andpdf;cacheControlis accepted as a graceful no-op.
- Added
Usage.reasoningTokensis populated fromcompletion_tokens_details.reasoning_tokens(o-series / gpt-5 hidden reasoning channel), emitted only when> 0. - Added
ModelCallOptions.reasoning.effortmaps to the nativereasoning_effortparam for reasoning-capable models;reasoning.maxTokenshas no Chat Completions equivalent. - Added
ModelCapabilities.reasoningis inferred from the model name (overridable via.model(...));promptCachingis alwaystrue(OpenAI caches automatically), andcacheControlwrite breakpoints are a no-op.
- Added
ModelCapabilities.reasoningis inferred from thinking-capable model tags (overridable viaollama.model({ name, reasoning }));promptCaching/audio/pdfreportfalse. - Added
ModelCallOptions.reasoningmaps onto Ollama's nativethinkflag for reasoning-capable models;reasoning.maxTokensandcacheControlare graceful no-ops.
4.2.11 June 17, 2026
Soft deletes go end-to-end in @warlock.js/cascade. @warlock.js/core adds warlock add notifications, and @warlock.js/notifications gains model-driven column mapping, read-state, and multi-tenant support.
- Added
Migration.createauto-wires thedeletedAtcolumn when the model's delete strategy is"soft"(opt out with{ softDeletes: false }) - Changed Require
@mongez/reinforcements≥ 3.3.0 — the update validator now uses its newwhenhelper for conditional schema fields - Fixed Soft
destroy()now setsdeletedAton the in-memory model — the instance was left stale before - Fixed Update validation no longer strips or rejects the
deletedAtcolumn under strict mode (now whitelisted like the timestamps)
- Added
lowerStage3Decorators()— Vite/Vitest plugin that lowers TC39 Stage-3 decorators with esbuild before oxc / the SSR rewrite mangles them; drop it first inpluginsso model-decorated files load under Vitest 4 / Vite 8. - Added
warlock add notifications— installs@warlock.js/notifications(+ themailfeature), ejectsconfig/notifications.ts, and scaffolds the app-ownedNotificationmodel + migration (idempotent). - Added Notifications connector — a built-in, config-gated connector that lazy-imports
@warlock.js/notifications, so core keeps no hard dependency on it. - Changed
warlock add testnow scaffolds avite.config.tsthat includeslowerStage3Decorators(), so a fresh project can test decorated models out of the box. - Changed
warlock add testtest/test:coveragescripts now run one-shot (vitest run) instead of watch mode — CI-safe by default. - Changed Bumped
@mongez/reinforcementsto 3.3.0 - Fixed
startHttpTestServernow starts early-phase connectors (database, cache, logger, …) before app modules, then late-phase (http, socket) after — mirroring dev/prod boot order; fixes aMissingDataSourceErrorunder the Vitest integration harness.
- Changed In-app column mapping moved onto the model as
static columnMap(recipient/tenant/readAt/isRead); accessors, repository, and channels all derive from it. NewNotificationColumnMaptype. - Changed Read-state is presence-based — declaring
readAt,isRead, or both selects the representation (defaultread_at); the mode-agnosticunreadfilter replacesisRead. - Changed Multi-tenant support — when the model declares a
tenantcolumn, thedatabasechannel reads it off the recipient andcreateFor(...)writes it. - Changed
inApp.list/inApp.listUnreadnow forward full list options (page/limit/orderBy+ filters). - Changed
notificationColumns(model)derives its columns fromcolumnMap; the SQL-vs-MongoDBdataSourcebranch is removed.
- Changed Bumped
@mongez/reinforcementsto 3.3.0
- Changed Bumped
@mongez/reinforcementsto 3.3.0
- Changed Bumped
@mongez/reinforcementsto 3.3.0
- Changed Bumped
@mongez/reinforcementsto 3.3.0
- Changed Bumped
@mongez/reinforcementsto 3.3.0
- Changed Bumped
@mongez/reinforcementsto 3.3.0 (package dependency + project template)
4.2.10 June 17, 2026
Patch: @warlock.js/auth moves its internal @mongez/* utilities from peer to regular dependencies, clearing the install warnings. @warlock.js/logger softens its console timestamp to gray.
- Fixed
@mongez/copper,@mongez/events, and@mongez/reinforcementsare now regulardependenciesinstead ofpeerDependencies— they're framework-internal utilities your app never imports, so declaring them as peers producedunmet peer dependencywarnings on install.
- Changed
ConsoleLog's timestamp (and the↳context arrow) switch from bright-blackgrayto the 256-colorslate— recessive but cleanly legible where bright-black read muddy.
- Changed The project template now pins the latest
@mongez/*versions (@mongez/reinforcements@^3.2.0,@mongez/agent-kit@^1.2.0) so freshly scaffolded apps start on current dependencies. (@warlock.js/*versions are still rewritten to the scaffolder's own version at install time.)
4.2.9 June 17, 2026
Patch: @warlock.js/logger's console output is retuned for scannability — a dimmed time-only timestamp, aligned level columns, and a restored white-on-red fatal badge.
- Changed
ConsoleLogoutput retuned for scannability — a time-onlyHH:mm:ss.SSStimestamp dimmed to gray, fixed-width level tags so the columns align, andfatalrestored to a white-on-bright-red background badge. (FileLog/JSONFileLogkeep the full ISO timestamp.)
4.2.8 June 17, 2026
Patch: @warlock.js/logger now prints each level's name beside its icon (ℹ info, ⚠ warn, ✗ error, …) for at-a-glance reading.
- Changed
ConsoleLognow prints each level's name beside its icon (⚙ debug,ℹ info,⚠ warn,✗ error,✓ success,☠ fatal) for at-a-glance reading.
4.2.7 June 17, 2026
Patch: create-warlock now ships its templates/ folder, fixing the "Something went wrong" error when scaffolding a new project.
- Fixed The published package now ships its
templates/folder, so scaffolding a new project works from the installed package — it was missing from the build, which failed the wizard with "Something went wrong" at the template-copy step.
4.2.6 June 17, 2026
Patch: create-warlock ships its bin folder again, restoring the CLI that was dropped from 4.2.5.
- Fixed The published package now ships its
binfolder again, so thecreate-warlockCLI works from the installed package — it was omitted from the 4.2.5 build.
4.2.5 June 15, 2026
Patch: warlock add notifications now scaffolds the in-app notifications HTTP surface — routes plus a list / mark-read / clear controller, gated by auth.
- Added
warlock add notificationsnow scaffolds the in-app read/dismiss HTTP surface —routes.ts+ anotifications.controller.ts(list / unread-count / mark-read / mark-all-read / clear / delete), gated byauthMiddlewareand recipient-scoped viainApp. Pulls@warlock.js/auth.
4.2.4 June 15, 2026
Patch: corrects a worker-loader build path in @warlock.js/core that 4.2.3 left broken.
- Fixed Fix the worker-loader path in the build entry points — a wrong path in 4.2.3 left the worker entry broken (and blocked the 4.2.3 publish for some packages).
4.2.3 June 15, 2026
Patch: ships the worker scripts as @warlock.js/core build entry points. A wrong path here blocked publishing for some packages — fixed in 4.2.4.
- Fixed Add the worker scripts as build entry points so they ship in the published package.
4.2.2 June 15, 2026
Patch: ships the warlock CLI entry (cli/start) in @warlock.js/core's build.
- Fixed Add
cli/startto the build entry points so thewarlockCLI entry ships in the published package.
4.2.1 June 15, 2026
Patch: @warlock.js/core and @warlock.js/cascade ship their bin folders, restoring the warlock and cascade CLIs dropped from 4.2.0.
- Fixed Ship the
binfolder so thewarlockCLI works from the published package — it was omitted from the 4.2.0 build.
- Fixed Ship the
binfolder so thecascadeCLI works from the published package — it was omitted from the 4.2.0 build.
4.2.0 June 15, 2026
A security overhaul of @warlock.js/auth — brute-force throttling, atomic refresh-token rotation, and CSPRNG secrets (the jwt config gives way to accessToken / refreshToken). Introduces two packages: @warlock.js/notifications (multi-channel notifications) and @warlock.js/access (RBAC + ABAC authorization).
- New Shipped Warlock.js Notifications Package.
- New Shipped Warlock.js Access Package.
- Added
loginThrottleMiddleware— failure-aware brute-force / credential-stuffing protection: counts only failed logins, locks per-account and per-IP, and rejects pre-controller with429(cache-backed, fails open). AddsAuthErrorCodes.TooManyAttempts(EC004). - Added
accessToken/refreshTokenconfiguration blocks, making a separate refresh-token secret first-class. - Added Overridable token storage — register a custom model under
config.auth.accessToken.model/refreshToken.modeland.extend()the exported schemas to add columns (e.g. a multi-tenantorganization_id). - Added
tokenType(access|refresh) claim, stamped on issue and verified on read, so an access token can't be presented as a refresh token. - Added
expires_aton access tokens;warlock auth.cleanupnow purges expired access tokens too. - Fixed Default access-token lifetime was ~3.6 seconds (a numeric
expiresInread as milliseconds) and is now 1 hour. - Fixed Targeted revocation queried
userIdinstead of theuser_idcolumn, so logout / refresh-token removal threw on Postgres and silently no-oped on MongoDB; token queries now route through named model statics. - Fixed Token deletions were fire-and-forget (false success for callers, uncatchable rejections) and are now awaited.
- Fixed The route middleware matched on
userTypeinstead of theuser_typecolumn. - Fixed
revokeAllTokens/revokeTokenFamilyreported an empty set, sotoken.revoked/token.familyRevokednever fired; the revoked rows are now captured before revocation. - Fixed A throwing synchronous auth-event listener no longer turns a completed login into a
500. - Deprecated The
auth.jwt.*configuration block. UseaccessToken/refreshTokeninstead — the legacy shape is still read and mapped forward with a one-time deprecation warning. - Removed Unread
access_tokenscolumnsis_activeandlast_access. - Removed The unused
auth.password.saltconfiguration key. - Security
warlock jwt.generatenow derivesJWT_SECRET/JWT_REFRESH_SECRETfrom a CSPRNG (Random.token) instead ofMath.random(). - Security Refresh-token rotation is atomic — a guarded conditional
UPDATEmeans two concurrent rotations can't both succeed, and a replayed token revokes its entire family.
- Added
log.flush()— awaitable async counterpart toflushSync(), draining every channel viaPromise.allSettledwith per-channel isolation. Implemented byFileLog/JSONFileLog. - Added
SentryLogchannel — forwards entries to Sentry (eventLevelsbecome events, others breadcrumbs;module/actionas tags).@sentry/nodeis an optional, lazily-imported peer. - Added
log.fatal()+fatallevel — ranked strictly aboveerrorfor unrecoverable failures; does not auto-flush or exit. - Added
ConsoleLogrendersfatalwith a☠icon on a bright-red background, distinct fromerror's✗. - Changed
captureAnyUnhandledRejection()now escalatesuncaughtExceptiontolog.fatal(waserror);unhandledRejectionstays aterror. - Changed
LoggingData.typeis now typed asLogLevel(was a duplicated inline union). - Changed
LogContract/LogChannelnow expose an optionalflush?()alongsideflushSync?(). - Fixed
@sentry/nodeis referenced only via local types + an indirect dynamic import, so source-served consumers no longer getTS2307: Cannot find module '@sentry/node'when they don't install the optional peer.
- Changed MongoDB and PostgreSQL drivers now log a failed initial
connect()atlog.fatal(waslog.error) — a boot-time database connection failure is unrecoverable, sofatalkeeps "page on fatal only" alerting clean. Per-query and disconnect failures stay aterror. - Fixed PostgreSQL
increment/decrement(and the*Manyvariants) bound the amount as$1, colliding with the first filter placeholder (SET n = n + $1 WHERE id = $1) so every filtered counter update wrote the wrong number; the amount now binds after the filter params.
- Fixed No-argument tools (declared without an
inputschema) no longer crash on invocation —tool.invokenow skips validation when no schema is present and passes the raw input to the handler.
- Added Opt-in
promptCachingflag on the model config — marks tool definitions withcache_control: { type: "ephemeral" }so multi-trip agents reuse the static tool schemas at the cache-read rate. Off by default.
- Changed Redis driver now logs a failed initial
connect()atlog.fatal(waslog.error) — a boot-time cache connection failure is unrecoverable, sofatalkeeps "page on fatal only" alerting clean.
- Changed
herald-connectorandhttp-connectornow log a failed boot-time connection atlog.fatal(waslog.error) — an unrecoverable broker connection or HTTP port-bind failure makes "page on fatal only" alerting clean; the HTTP connector flushes logs beforeprocess.exit(1). Disconnect / shutdown failures stay aterror.
4.1.15 June 4, 2026
The first public release of Warlock.js — 17 packages published together at 4.1.15. Every change from here on is recorded per package and aggregated on this page.
No changes recorded for this package yet.