@dudousxd/nestjs-agent-store-drizzle 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,8 @@ pnpm add @dudousxd/nestjs-agent-store-drizzle drizzle-orm
17
17
 
18
18
  The host app owns the connection and passes in an already-opened Drizzle handle — this module never
19
19
  opens one itself. **SQLite, Postgres and MySQL** all work — CI runs every store suite on
20
- better-sqlite3, node-postgres (Postgres 16) and mysql2 (MySQL 8.4), and the other Drizzle drivers of
20
+ better-sqlite3, node-postgres (Postgres 16) and mysql2 (MySQL 8.4), the independent-approval suites
21
+ also on postgres.js, and the other Drizzle drivers of
21
22
  each dialect speak the same query builder. Build the handle with the schema object for your dialect;
22
23
  every store reads the dialect off the handle.
23
24
 
@@ -146,42 +147,49 @@ EXISTS`, and an explicit add-column pass for columns this package introduced aft
146
147
  since the `CREATE TABLE` guard is inert against a table that already exists. Calling it on boot is
147
148
  enough; it never drops a column or changes a type.
148
149
 
149
- On your own drizzle-kit migrations, those additive statements are yours to write:
150
+ On your own drizzle-kit migrations, those additive statements are yours to write. The simplest way
151
+ to keep them right is to let drizzle-kit write them: re-export the package's schema for your dialect
152
+ (`agentSchema`, `pgAgentSchema` or `mysqlAgentSchema`) from the schema file drizzle-kit reads, and
153
+ `drizzle-kit generate` diffs the agent tables like your own. `agentSchemaDdl(dialect)` returns the
154
+ complete target DDL (tables and indexes) if you would rather compare by hand.
155
+
156
+ For reference, on SQLite, the columns added to the original chat tables since they first shipped
157
+ (all nullable; adjust the types to your dialect):
150
158
 
151
159
  ```sql
152
160
  ALTER TABLE agent_thread ADD COLUMN default_agent TEXT;
153
- ALTER TABLE agent_message ADD COLUMN run_id TEXT;
161
+ ALTER TABLE agent_thread ADD COLUMN model TEXT;
162
+ ALTER TABLE agent_thread ADD COLUMN queue_pause TEXT;
154
163
  ALTER TABLE agent_message ADD COLUMN attachments TEXT;
155
- ALTER TABLE agent_run ADD COLUMN parent_run_id TEXT;
164
+ ALTER TABLE agent_message ADD COLUMN agent_name TEXT;
165
+ ALTER TABLE agent_message ADD COLUMN run_id TEXT;
156
166
  ALTER TABLE agent_message ADD COLUMN reasoning TEXT;
157
167
  ALTER TABLE agent_message ADD COLUMN reasoning_ms INTEGER;
158
168
  ALTER TABLE agent_message ADD COLUMN ui TEXT;
159
169
  ALTER TABLE agent_message ADD COLUMN feedback TEXT;
160
- ALTER TABLE agent_thread ADD COLUMN model TEXT;
170
+ ALTER TABLE agent_message ADD COLUMN seq INTEGER;
171
+ ALTER TABLE agent_message ADD COLUMN action_proposal_outcome TEXT;
172
+ ALTER TABLE agent_tool_call ADD COLUMN run_id TEXT;
173
+ ALTER TABLE agent_tool_call ADD COLUMN confirmation TEXT;
161
174
  ALTER TABLE agent_tool_call ADD COLUMN approver TEXT;
162
175
  ALTER TABLE agent_tool_call ADD COLUMN expires_at INTEGER;
163
176
  ALTER TABLE agent_tool_call ADD COLUMN remember INTEGER;
164
177
  ALTER TABLE agent_tool_call ADD COLUMN decided_via TEXT;
178
+ ALTER TABLE agent_tool_call ADD COLUMN proposal_id TEXT;
179
+ ALTER TABLE agent_token_usage ADD COLUMN cache_write_tokens INTEGER;
180
+ ALTER TABLE agent_token_usage ADD COLUMN cache_read_tokens INTEGER;
181
+ ALTER TABLE agent_token_usage ADD COLUMN cost_usd REAL;
182
+ ALTER TABLE agent_model_pricing ADD COLUMN cache_write_price_per_1m REAL;
183
+ ALTER TABLE agent_model_pricing ADD COLUMN cache_read_price_per_1m REAL;
165
184
  CREATE INDEX agent_tool_call_message_idx ON agent_tool_call (message_id);
166
- -- the chat message queue
167
- ALTER TABLE agent_thread ADD COLUMN queue_pause TEXT;
168
- CREATE TABLE agent_queued_message (
169
- id TEXT PRIMARY KEY NOT NULL,
170
- thread_id TEXT NOT NULL REFERENCES agent_thread(id) ON DELETE CASCADE,
171
- actor TEXT NOT NULL,
172
- content TEXT NOT NULL,
173
- attachments TEXT,
174
- agent_name TEXT,
175
- model TEXT,
176
- page_context TEXT,
177
- interrupt INTEGER NOT NULL DEFAULT 0,
178
- position INTEGER NOT NULL,
179
- created_at INTEGER NOT NULL,
180
- updated_at INTEGER NOT NULL
181
- );
182
- CREATE INDEX agent_queued_message_thread_position_idx ON agent_queued_message (thread_id, position);
183
185
  ```
184
186
 
187
+ Tables added since then are new tables, not alterations. Take their `CREATE TABLE` and
188
+ `CREATE INDEX` statements from `agentSchemaDdl(dialect)`: `agent_queued_message` (the chat message
189
+ queue), `agent_run`, `agent_memory`, `agent_stream_frame`, `agent_confirm_token`,
190
+ `rag_ingestion_log` and `agent_action_proposal`. Later columns on those tables (for example the
191
+ proposal discovery and delivery columns described below) are added the same way.
192
+
185
193
  The index is not optional on Postgres: `agent_tool_call.message_id` carries a foreign key, and
186
194
  Postgres — unlike MySQL — does not index one for you, while both message-scoped reads (the thread
187
195
  reader's `IN (…)` and `truncateFrom`'s delete) filter on it.
@@ -234,6 +242,104 @@ a gateway-reported `costUsd` wins when present, otherwise a cache-aware estimate
234
242
  the input rate). See the [root README](https://github.com/DavideCarvalho/nestjs-agent#cost--governance)
235
243
  for the full model.
236
244
 
245
+ ## Action confirmations
246
+
247
+ Action preflight confirmations persist in the nullable `agent_tool_call.confirmation` JSON column.
248
+ The schema helper adds this column to existing databases. Hosts managing their own migrations must
249
+ add it before upgrading (`JSONB` on PostgreSQL, `JSON` on MySQL, JSON text on SQLite). The stored
250
+ confirmation is returned in `StoredMessage.approvals` so a reload uses the same wording.
251
+
252
+ ## Durable action proposals
253
+
254
+ `DrizzleAgentStore` also implements the optional core `ActionProposalStore` capability.
255
+ Its `createActionProposal`, scoped get/list, immutable decision, lease claim/renewal,
256
+ and settlement methods persist to the independent `agent_action_proposal` table;
257
+ no persisted thread, message, or run is required. The standalone
258
+ `DrizzleActionProposalStore` exposes the same methods when only this capability is needed.
259
+ Both accept an optional second constructor argument `{ clock: () => number }` for a
260
+ trusted server clock (default `Date.now`); never pass a request timestamp as that clock.
261
+
262
+ Creation is replay-safe against the original canonical JSON snapshot. A proposal ID
263
+ is globally unique, and every read or mutation checks tenant, actor, and thread scope.
264
+ Approval and queued execution commit together in one version-fenced SQL update.
265
+ Claims/recovery increment a generation and issue a new token; renewal and settlement
266
+ require both and an unexpired lease. Expiry is inclusive (`now >= expiresAt`).
267
+ A contended caller transaction can return `conflict` after bounded CAS retries;
268
+ retry from a fresh transaction when its repeatable-read snapshot is stale.
269
+ The stable `idempotencyKey` survives recovery: delivery is at least once, so the tool
270
+ must enforce idempotency for its external effect. This storage capability does not
271
+ start a worker or change the agent loop.
272
+
273
+ For existing databases, run `ensureAgentSchema(db)` to add the new table and its
274
+ scope/creation-order index without altering existing data. Lists filter and limit in
275
+ SQL (default 100, maximum 1000), with UTF16 lexical ID ordering for equal creation
276
+ times. Logical proposal IDs are limited to 255 UTF16 code units by the core contract.
277
+ If you own migrations,
278
+ include `agent_action_proposal` from `agentSchema`, `pgAgentSchema`, or
279
+ `mysqlAgentSchema` (or the corresponding `agentSchemaDdl(dialect)` statements).
280
+ The physical primary key and scope key are SHA-256 digests; original identifiers
281
+ remain in the canonical JSON text snapshot and are matched exactly, including
282
+ trailing spaces,
283
+ independently of MySQL collation padding. Text storage preserves valid JSON strings
284
+ containing NUL or lone surrogates on every dialect. Decision audit and execution
285
+ work share
286
+ the snapshot column, fenced by the numeric `version` column. Existing table schemas
287
+ and fixture callers remain compatible.
288
+
289
+ Worker discovery is available on `DrizzleAgentStore` and `DrizzleActionProposalStore`
290
+ through the privileged `claimNextActionProposal({ workerId, leaseMs })` and
291
+ `expireActionProposals({ limit })` methods. Do not expose these unscoped methods on
292
+ public routes. Discovery selects at most 32 queued or expired-lease candidates;
293
+ expiry selects at most `limit` (1–1000) pending proposals due at the trusted store
294
+ clock. Existing scoped, version-fenced mutations decide which worker wins.
295
+
296
+ Upgrading an existing proposal table requires the additive nullable
297
+ `execution_status`, `lease_expires_at`, and `proposal_expires_at` columns, the
298
+ `discovery_index_version` column with default 0, and the execution/expiry indexes
299
+ from the exported schema. `ensureAgentSchema` adds these definitions; hosts using
300
+ Drizzle migrations must include them in their forward migration. Stop old writers,
301
+ apply the DDL, then repeat the bounded backfill before starting new workers:
302
+
303
+ ```ts
304
+ while (await store.backfillActionProposalDiscoveryIndex({ limit: 1000 })) {
305
+ // Each batch derives metadata from the canonical payload under its row version.
306
+ }
307
+ ```
308
+
309
+ New writes maintain index version 1 and all discovery metadata atomically with the
310
+ proposal snapshot. Backfill increments the row version without changing the
311
+ proposal payload, timestamps, or audit. It does not invoke transitions or execute
312
+ tools. This upgrade does not support mixed old and new writers: old writers cannot
313
+ maintain the new indexes. Discovery deliberately excludes rows awaiting backfill;
314
+ it never performs an implicit, unbounded migration.
315
+
316
+ ## Independent approval runtime
317
+
318
+ `actionApprovalMode: 'independent'` requires proposal discovery, atomic replacement,
319
+ terminal outcome admission and the thread queue on the same database. This adapter
320
+ advertises admission support for better-sqlite3, node-postgres, postgres.js, mysql2 and libSQL;
321
+ other Drizzle drivers retain blocking behavior and fail independent configuration.
322
+ Synchronous SQLite transaction callbacks complete synchronously, including both
323
+ assistant insertion and the delivery compare-and-set. PostgreSQL/MySQL admission
324
+ locks the proposal and conversation before checking the delivery lease again.
325
+
326
+ The updated schemas add nullable `delivery_status`, `delivery_lease_expires_at`,
327
+ `replacement_group_key`, `outcome_id_key` and their indexes to proposals,
328
+ `action_proposal_outcome` to messages, `proposal_id` to tool calls and
329
+ `ui_capabilities` to queued messages. `ensureAgentSchema` adds missing columns and
330
+ indexes; applications owning drizzle-kit migrations must include the same additions.
331
+ Outcomes use escaped TEXT/LONGTEXT metadata to preserve JSON strings exactly.
332
+
333
+ Delivery writes one assistant fact and its UI together with its admitted marker,
334
+ waits while a user turn owns the conversation and deduplicates retries. Replacement
335
+ only closes pending proposals in the same exact scope, tool and replacement group.
336
+ An existing transaction passed as the database handle remains the transaction
337
+ owner: a caller rollback also rolls back replacement or admission. External side
338
+ effects remain at least once; use the stable tool-context idempotency key.
339
+
340
+ See [the shared setup and rollout guide](../../docs/independent-approvals.md) for
341
+ policies, text/channel decisions, remembered approvals and component capabilities.
342
+
237
343
  ## License
238
344
 
239
345
  MIT © Davide Carvalho