@getfamiliar/shared 0.1.2

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.
Files changed (83) hide show
  1. package/build/AgentRun.d.ts +187 -0
  2. package/build/AgentRun.js +1 -0
  3. package/build/AgentRunBus.d.ts +147 -0
  4. package/build/AgentRunBus.js +362 -0
  5. package/build/Calendar.d.ts +450 -0
  6. package/build/Calendar.js +37 -0
  7. package/build/ChatMessage.d.ts +53 -0
  8. package/build/ChatMessage.js +11 -0
  9. package/build/ChatMessageBus.d.ts +110 -0
  10. package/build/ChatMessageBus.js +257 -0
  11. package/build/Config.d.ts +83 -0
  12. package/build/Config.js +1 -0
  13. package/build/ContainerTool.d.ts +34 -0
  14. package/build/ContainerTool.js +1 -0
  15. package/build/CronExpression.d.ts +53 -0
  16. package/build/CronExpression.js +77 -0
  17. package/build/Event.d.ts +280 -0
  18. package/build/Event.js +14 -0
  19. package/build/EventBus.d.ts +198 -0
  20. package/build/EventBus.js +482 -0
  21. package/build/HandlerCatalog.d.ts +75 -0
  22. package/build/HandlerCatalog.js +165 -0
  23. package/build/InferenceEvent.d.ts +30 -0
  24. package/build/InferenceEvent.js +1 -0
  25. package/build/InferenceEventBus.d.ts +39 -0
  26. package/build/InferenceEventBus.js +80 -0
  27. package/build/Mail.d.ts +250 -0
  28. package/build/Mail.js +73 -0
  29. package/build/MailStyleTemplate.d.ts +71 -0
  30. package/build/MailStyleTemplate.js +47 -0
  31. package/build/ModelMetaData.d.ts +63 -0
  32. package/build/ModelMetaData.js +12 -0
  33. package/build/PathGlob.d.ts +20 -0
  34. package/build/PathGlob.js +33 -0
  35. package/build/Plugin.d.ts +709 -0
  36. package/build/Plugin.js +29 -0
  37. package/build/PostgresConnection.d.ts +75 -0
  38. package/build/PostgresConnection.js +162 -0
  39. package/build/ScheduledSubagent.d.ts +49 -0
  40. package/build/ScheduledSubagent.js +1 -0
  41. package/build/ScheduledSubagentBus.d.ts +94 -0
  42. package/build/ScheduledSubagentBus.js +187 -0
  43. package/build/Schema.d.ts +84 -0
  44. package/build/Schema.js +570 -0
  45. package/build/StepResult.d.ts +103 -0
  46. package/build/StepResult.js +1 -0
  47. package/build/StepResultBus.d.ts +64 -0
  48. package/build/StepResultBus.js +169 -0
  49. package/build/Timezone.d.ts +58 -0
  50. package/build/Timezone.js +84 -0
  51. package/build/ToolCall.d.ts +25 -0
  52. package/build/ToolCall.js +1 -0
  53. package/build/ToolCallBus.d.ts +65 -0
  54. package/build/ToolCallBus.js +110 -0
  55. package/build/ToolLevel.d.ts +20 -0
  56. package/build/ToolLevel.js +2 -0
  57. package/build/ToolRunner.d.ts +118 -0
  58. package/build/ToolRunner.js +192 -0
  59. package/build/ToolsExpressionUtils.d.ts +80 -0
  60. package/build/ToolsExpressionUtils.js +102 -0
  61. package/build/WorkspaceFile.d.ts +104 -0
  62. package/build/WorkspaceFile.js +7 -0
  63. package/build/estimateTokens.d.ts +16 -0
  64. package/build/estimateTokens.js +18 -0
  65. package/build/index.d.ts +37 -0
  66. package/build/index.js +26 -0
  67. package/build/logging/Logger.d.ts +66 -0
  68. package/build/logging/Logger.js +90 -0
  69. package/build/markdownHtml.d.ts +14 -0
  70. package/build/markdownHtml.js +21 -0
  71. package/build/markdownTerminal.d.ts +14 -0
  72. package/build/markdownTerminal.js +376 -0
  73. package/build/testing/MockAgentRunBus.d.ts +52 -0
  74. package/build/testing/MockAgentRunBus.js +175 -0
  75. package/build/testing/MockBusStore.d.ts +22 -0
  76. package/build/testing/MockBusStore.js +22 -0
  77. package/build/testing/MockEventBus.d.ts +20 -0
  78. package/build/testing/MockEventBus.js +57 -0
  79. package/build/testing/MockLogger.d.ts +52 -0
  80. package/build/testing/MockLogger.js +65 -0
  81. package/build/testing/index.d.ts +4 -0
  82. package/build/testing/index.js +4 -0
  83. package/package.json +63 -0
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Lifecycle states for an `events` row.
3
+ *
4
+ * Events are the immutable record of "the world said this happened".
5
+ * They no longer participate in any worker state machine themselves —
6
+ * the actual work runs in `agentruns` rows that reference the event.
7
+ *
8
+ * Transitions:
9
+ *
10
+ * - `pending`: row inserted by a host plugin via {@link EventBus.add}.
11
+ * - `running`: the container's input-event watcher has spawned the root
12
+ * agentrun for this event.
13
+ * - `done` / `failed`: set reactively when the last `pending` /
14
+ * `running` agentrun for this event settles. `failed` wins if any
15
+ * agentrun for the event has failed.
16
+ */
17
+ export type EventState = "pending" | "running" | "done" | "failed";
18
+ /**
19
+ * Standard priority tiers for events. Higher = processed first.
20
+ * Producers should pick one of these rather than passing a bare number,
21
+ * unless they have a specific reason. The SQL default for `events.priority`
22
+ * is 50, matching `EVENT_PRIORITY.ASYNC`.
23
+ */
24
+ export declare const EVENT_PRIORITY: {
25
+ /** Direct user chat (telegram, cli-chat). Preempts async work. */
26
+ readonly CHAT: 100;
27
+ /** Asynchronous external input — mail, group chat. Matches the SQL default. */
28
+ readonly ASYNC: 50;
29
+ /** Cron-driven background work. Yields to chat and async. */
30
+ readonly BACKGROUND: 10;
31
+ };
32
+ /**
33
+ * Shape of a row in the `events` table after JSON-decoding from postgres.
34
+ * The numeric `id` is kept as a string because postgres `bigint` exceeds
35
+ * JavaScript's safe integer range.
36
+ */
37
+ export interface EventRow {
38
+ /**
39
+ * Database primary key. `bigserial` is returned as a string because
40
+ * postgres `bigint` exceeds JavaScript's safe integer range.
41
+ */
42
+ readonly id: string;
43
+ /**
44
+ * Event topic, matching `\w+(:\w+)*` (e.g. `mail`, `chat:whatsapp`,
45
+ * `chat:telegram:group:reaction`). Each `:`-separated segment maps
46
+ * to a folder when the container resolves the handler markdown.
47
+ */
48
+ readonly topic: string;
49
+ /**
50
+ * Higher = processed first within a queue. FIFO within priority.
51
+ * Default 50. Inherited by all agentruns spawned for this event.
52
+ */
53
+ readonly priority: number;
54
+ /** Lifecycle state — see {@link EventState}. */
55
+ readonly state: EventState;
56
+ /**
57
+ * Event-specific data; arbitrary JSON. Schema is owned by the
58
+ * producing plugin / topic, not by the bus.
59
+ */
60
+ readonly payload: unknown;
61
+ /**
62
+ * Globally unique key for dedup. `null` when the producer didn't
63
+ * supply one (then no dedup happens).
64
+ */
65
+ readonly idempotencyKey: string | null;
66
+ /**
67
+ * Whether this event is a chat message from the user. When `true`,
68
+ * `EventBus.add` mirrors `prompt` into `chatmessages` (role
69
+ * `'user'`) in the same transaction as the event INSERT, so the
70
+ * agent picks the message up via chat history rather than via the
71
+ * agentrun's `prompt` field.
72
+ */
73
+ readonly isChat: boolean;
74
+ /**
75
+ * Channel the assistant should reply on (e.g. `"cli"`, `"telegram"`).
76
+ * Stamped host-side at emit time, falling back to
77
+ * `DEFAULT_CHAT_CHANNEL_ID`. The container never reads this field;
78
+ * routing happens via JOIN in the chatmessages trigger and in
79
+ * `ChatMessageBus`. May be `null` for non-chat events whose source
80
+ * plugin didn't set a default routing target.
81
+ */
82
+ readonly preferredChatChannelId: string | null;
83
+ /**
84
+ * The event's primary user-visible text, written by the emitter.
85
+ * Always populated:
86
+ *
87
+ * - For chat events (`isChat=true`): the user's chat message. Also
88
+ * mirrored into `chatmessages.text_content` (role `'user'`) by
89
+ * `EventBus.add`, which is the channel the AgentRunner consumes.
90
+ * - For non-chat events: a first-person framing of what happened
91
+ * ("A new mail from Anna arrived: …"). The EventWatcher copies
92
+ * it into the root agentrun's `prompt` so the AgentRunner can
93
+ * append it as the trailing user message.
94
+ */
95
+ readonly prompt: string;
96
+ /**
97
+ * Override for the root agentrun's handler. `null` means the
98
+ * input-event watcher uses `'index'` (i.e. resolves
99
+ * `<topic>/index.md`). Set by the emitter via
100
+ * {@link NewEvent.startHandler}; never mutated after insert.
101
+ */
102
+ readonly startHandler: string | null;
103
+ /**
104
+ * `true` when this event was emitted by a trusted user-input source
105
+ * (the operator at the local terminal via cli-chat, the operator on
106
+ * Telegram). Stamped at emit time, never mutated. Propagated verbatim
107
+ * to the root agentrun and to every child agentrun spawned via
108
+ * `schedule_subagent` / `start_subagent`, so future system tools can gate
109
+ * risky reads / writes
110
+ * (editing SOUL.md, etc.) on whether the run descends from a trusted
111
+ * input. `false` for everything else (mail, webhooks, cron-driven
112
+ * workflows).
113
+ */
114
+ readonly privileged: boolean;
115
+ /**
116
+ * When `true` and the event terminates in `failed`, the host writes
117
+ * a `role='assistant'` chatmessage `"Something went wrong processing
118
+ * the chat message: <error>"` to this event before signalling settle.
119
+ * Lets chat plugins surface failures through the ordinary chat
120
+ * subscription path without each needing its own catch-block
121
+ * rendering. No-op on `done`.
122
+ */
123
+ readonly outputChatOnFailure: boolean;
124
+ /** Insert timestamp — postgres `now()` at INSERT. */
125
+ readonly createdAt: Date;
126
+ /** Last `update()` timestamp — bumped to `now()` on every update. */
127
+ readonly updatedAt: Date;
128
+ }
129
+ /**
130
+ * One auxiliary file to attach to an event at emit time. The host writes
131
+ * each file under `<scratchDir>/<eventId>/<name>` inside the same
132
+ * transaction that inserts the event row, so the container watcher only
133
+ * ever observes events whose files are already on disk at
134
+ * `/scratch/<event-id>/<name>` inside both the agent and every MCP
135
+ * container.
136
+ *
137
+ * Two arms:
138
+ * - `contents` — Buffer the host writes directly. Use when bytes are
139
+ * already in memory (e.g. just decoded from a base64 MCP response).
140
+ * - `sourcePath` — absolute host path to a file the plugin has already
141
+ * written to disk. The host **moves** the file into the scratch dir
142
+ * (`fs.rename`, falling back to copy+unlink across filesystems); the
143
+ * plugin gives up ownership. Use for large files the plugin doesn't
144
+ * want to re-buffer in memory.
145
+ *
146
+ * `name` is a basename. Path separators and `..` segments are rejected
147
+ * at emit; on collision within the same emit, the second file overwrites
148
+ * the first (caller's responsibility to keep names unique).
149
+ */
150
+ export type EventFile = {
151
+ readonly name: string;
152
+ readonly contents: Buffer;
153
+ } | {
154
+ readonly name: string;
155
+ readonly sourcePath: string;
156
+ };
157
+ /**
158
+ * Input shape for {@link EventBus.add}.
159
+ *
160
+ * `prompt` carries the event's primary user-visible text — for chat
161
+ * events it's the user's message; for everything else it's a
162
+ * first-person framing of what happened ("A new mail from Anna
163
+ * arrived: …"). `EventBus.add` validates that the value is non-empty
164
+ * after trimming, so plugins that bypass the type system still surface
165
+ * the bug at emit time instead of leaving the AgentRunner with an
166
+ * empty messages array.
167
+ *
168
+ * `payload` is optional structured supplementary data (Telegram
169
+ * `update_id`, WhatsApp `group_jid`, etc.) — it never carries the
170
+ * agent-visible text directly any more.
171
+ *
172
+ * `isChat` is just a flag. When `true`, `EventBus.add` mirrors
173
+ * `prompt` into the `chatmessages` table so the AgentRunner picks the
174
+ * message up via chat history; the agentrun's own `prompt` field
175
+ * stays null for chat events to avoid double-counting the trailing
176
+ * turn. The flag does not change the input shape — both arms have
177
+ * identical fields — so this stays a single interface rather than a
178
+ * discriminated union.
179
+ */
180
+ export interface NewEvent {
181
+ readonly topic: string;
182
+ /**
183
+ * Primary user-visible text. Required, non-empty after trimming.
184
+ * See {@link NewEvent} for how it's persisted on each arm.
185
+ */
186
+ readonly prompt: string;
187
+ /** Optional structured supplementary data; arbitrary JSON. */
188
+ readonly payload?: unknown;
189
+ /** Higher = processed first; default 50. */
190
+ readonly priority?: number;
191
+ /** Initial state; default `"pending"`. */
192
+ readonly state?: EventState;
193
+ /** Globally unique key for dedup; null = no dedup. */
194
+ readonly idempotencyKey?: string;
195
+ /**
196
+ * Whether this event is a chat message from the user. Default
197
+ * `false`. See {@link NewEvent} for the persistence consequences.
198
+ */
199
+ readonly isChat?: boolean;
200
+ /**
201
+ * Channel id the assistant should reply on. Plugins typically set
202
+ * this to their own channel name (e.g. `"cli"`). When omitted, the
203
+ * host's `HostContextImpl.emit` stamps `DEFAULT_CHAT_CHANNEL_ID`.
204
+ *
205
+ * If the producer is not a chat plugin, this should be `null` or omitted.
206
+ */
207
+ readonly preferredChatChannelId?: string | null;
208
+ /**
209
+ * Whether this event originates from a trusted user-input source.
210
+ * Default `false` — the SQL column default covers omission. See
211
+ * {@link EventRow.privileged} for the trust-model rationale.
212
+ */
213
+ readonly privileged?: boolean;
214
+ /**
215
+ * When `true` and the event terminates in `failed`, the host writes
216
+ * a `role='assistant'` chatmessage carrying the failure text to this
217
+ * event before `handle.settled` rejects. Default `false` — the SQL
218
+ * column default covers omission. Chat plugins (cli-chat, telegram)
219
+ * set this to `true` so users see failures through the same chat
220
+ * subscription path that delivers normal replies. See
221
+ * {@link EventRow.outputChatOnFailure}.
222
+ */
223
+ readonly outputChatOnFailure?: boolean;
224
+ /**
225
+ * When set, EventBus.add inserts a `role='user'` chatmessage with
226
+ * this text in the same transaction as the event INSERT.
227
+ *
228
+ * Atomicity matters: a post-emit chatmessages INSERT acquires an
229
+ * FK row-lock on the events row that conflicts with the input-event
230
+ * watcher's `FOR UPDATE SKIP LOCKED` claim. If the watcher races
231
+ * the chatmessage write, `SKIP LOCKED` skips the row and — because
232
+ * the events_new NOTIFY has already been consumed — the event sits
233
+ * pending forever. Doing the chatmessage INSERT inside the same
234
+ * transaction closes the window: the FK lock is released before
235
+ * NOTIFY events_new fires post-commit.
236
+ *
237
+ * Use this for plugin emitters that want a chatmessage text
238
+ * different from `prompt` (e.g. cli-chat persists the slash-
239
+ * prefixed `/topic/handler …` line while the handler sees only
240
+ * the trailing prompt text). When unset and `isChat: true`, the
241
+ * existing path inserts `prompt` itself.
242
+ */
243
+ readonly userChatMessage?: string;
244
+ /**
245
+ * Override which handler markdown the root agentrun runs. When
246
+ * omitted, the input-event watcher uses `'index'` (i.e. resolves
247
+ * `<topic>/index.md`). Useful when an emitter wants to skip the
248
+ * triage `index.md` and invoke a specific handler directly.
249
+ *
250
+ * Pass the basename without `.md` (e.g. `'analyze'` resolves to
251
+ * `<topic>/analyze.md`). The agentrun fails loud at handler-load
252
+ * time if no such file exists — there is no shape validation
253
+ * here, mirroring how `topic` is only regex-checked, not
254
+ * existence-checked, at emit time.
255
+ */
256
+ readonly startHandler?: string;
257
+ /**
258
+ * Auxiliary files to stage at `/scratch/<event-id>/<name>` for this
259
+ * event. Visible to the agent and to every MCP container under the
260
+ * same absolute path. Staging happens atomically with the event
261
+ * INSERT (inside the same transaction), so the container watcher
262
+ * never sees an event whose files aren't yet on disk. See {@link
263
+ * EventFile} for the two payload shapes.
264
+ */
265
+ readonly files?: readonly EventFile[];
266
+ }
267
+ /** Patch shape for {@link EventBus.update}. */
268
+ export interface EventPatch {
269
+ readonly state?: EventState;
270
+ readonly payload?: unknown;
271
+ readonly priority?: number;
272
+ readonly preferredChatChannelId?: string | null;
273
+ }
274
+ /** Filter for {@link EventBus.waitForNext}. */
275
+ export interface EventFilter {
276
+ /** If set, only events whose topic is in this list will match. */
277
+ readonly topics?: readonly string[];
278
+ /** If set, only events in one of these states match (default `["pending"]`). */
279
+ readonly states?: readonly EventState[];
280
+ }
package/build/Event.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Standard priority tiers for events. Higher = processed first.
3
+ * Producers should pick one of these rather than passing a bare number,
4
+ * unless they have a specific reason. The SQL default for `events.priority`
5
+ * is 50, matching `EVENT_PRIORITY.ASYNC`.
6
+ */
7
+ export const EVENT_PRIORITY = {
8
+ /** Direct user chat (telegram, cli-chat). Preempts async work. */
9
+ CHAT: 100,
10
+ /** Asynchronous external input — mail, group chat. Matches the SQL default. */
11
+ ASYNC: 50,
12
+ /** Cron-driven background work. Yields to chat and async. */
13
+ BACKGROUND: 10,
14
+ };
@@ -0,0 +1,198 @@
1
+ import type { EventFilter, EventPatch, EventRow, EventState, NewEvent } from "./Event.js";
2
+ import type { Logger } from "./logging/Logger.js";
3
+ import type { PostgresConnection } from "./PostgresConnection.js";
4
+ /**
5
+ * Thrown by {@link EventBus.add} when an event's `idempotencyKey`
6
+ * collides with one already in the `events` table (Postgres unique
7
+ * violation `23505` on `events_idempotency_key_key`).
8
+ *
9
+ * The error is deliberately typed rather than swallowed: an emitter
10
+ * that re-derives the same key on a re-walk (the ms365 mail delta poll,
11
+ * the calendar poll, chat compaction, …) is expected to `catch` this
12
+ * and treat the collision as a no-op — the event already exists. Other
13
+ * emitters that should never collide let it propagate as a real fault.
14
+ */
15
+ export declare class DuplicateIdempotencyKeyError extends Error {
16
+ /** The colliding idempotency key, for logging by the catching emitter. */
17
+ readonly idempotencyKey: string;
18
+ /**
19
+ * @param idempotencyKey - The key that already existed in `events`.
20
+ */
21
+ constructor(idempotencyKey: string);
22
+ }
23
+ /**
24
+ * Domain client for the `events` table that backs the host ↔ container bus.
25
+ *
26
+ * Uses an injected {@link PostgresConnection} for pool + LISTEN access;
27
+ * does not own connection lifecycle. Several `EventBus` instances can
28
+ * share a connection, and the connection can be reused by other domain
29
+ * clients (e.g. {@link AgentRunBus}) without opening more sockets.
30
+ *
31
+ * Listens on `events_new` for wake-ups — the channel that fires on
32
+ * INSERT into `events`. State updates (which fire on `events_state`)
33
+ * are not consumed here; host plugins that need to wait for an event to
34
+ * settle should subscribe to that channel directly via
35
+ * {@link PostgresConnection.listen}.
36
+ *
37
+ * `waitForNext()` does an initial query before sleeping on the listen
38
+ * channel, so events that already existed when the call started are
39
+ * picked up immediately.
40
+ *
41
+ * The bus is *not* a multi-consumer queue: two waiters may observe the
42
+ * same event. Caller is responsible for transitioning state via
43
+ * {@link update} after handling.
44
+ */
45
+ export declare class EventBus {
46
+ private readonly connection;
47
+ private readonly log;
48
+ private notifyWaiters;
49
+ private listenInstalled;
50
+ private readonly listenHandler;
51
+ /**
52
+ * @param connection - Shared pg pool / LISTEN handle.
53
+ * @param log - Optional logger; when set, NOTIFY arrivals are
54
+ * debug-logged. Omitted on the host-side bus that runs inside
55
+ * one-shot CLI commands so they stay quiet by default.
56
+ */
57
+ constructor(connection: PostgresConnection, log?: Logger);
58
+ /**
59
+ * Apply the events + agentruns schema (idempotent). Safe to call on
60
+ * every daemon start. The same SQL bundle is consumed by
61
+ * {@link AgentRunBus.installSchema}, so calling either is sufficient.
62
+ */
63
+ installSchema(): Promise<void>;
64
+ /**
65
+ * Insert a new event and return the persisted row.
66
+ *
67
+ * Transactional: when `event.isChat === true`, a `chatmessages`
68
+ * row (role `'user'`) carrying the same `prompt` text is inserted
69
+ * in the same transaction. Both NOTIFY triggers fire post-COMMIT,
70
+ * so listeners always observe consistent state.
71
+ *
72
+ * Validation: `prompt` must be a non-empty string after trimming —
73
+ * for any event, chat or not. The single check exists at this
74
+ * boundary so plugins that bypass the type system (untyped JS,
75
+ * unsafe casts) still surface the bug at emit time instead of
76
+ * leaving the AgentRunner with an empty messages array.
77
+ *
78
+ * `beforeCommit` runs **inside the transaction** after the event
79
+ * row (and any chat mirror) is inserted but before COMMIT. Use it
80
+ * for filesystem side-effects that must be atomic with the INSERT
81
+ * from the watcher's perspective — most importantly, staging
82
+ * `event.files` under `<scratchDir>/<row.id>/` so the container
83
+ * never observes an event without its scratch files in place.
84
+ * Throwing from `beforeCommit` rolls the INSERT back; cleaning up
85
+ * any partial filesystem state is the callback's responsibility.
86
+ *
87
+ * @throws {DuplicateIdempotencyKeyError} If `idempotencyKey` collides
88
+ * with an existing event. Emitters that re-derive stable keys on a
89
+ * re-walk are expected to catch this and treat it as a no-op.
90
+ * @throws If `topic` does not match `\w+(:\w+)*`, if `prompt` is
91
+ * missing or whitespace-only, or if `beforeCommit` throws.
92
+ */
93
+ add(event: NewEvent, beforeCommit?: (row: EventRow) => Promise<void>): Promise<EventRow>;
94
+ /**
95
+ * Fetch one event by id. Returns `undefined` if not found. Mirrors
96
+ * {@link import("./StepResultBus").StepResultBus.getById} so
97
+ * notification consumers can promote a payload-only id into a full
98
+ * typed row without a hand-written query.
99
+ */
100
+ getById(id: string): Promise<EventRow | undefined>;
101
+ /**
102
+ * Fetch all event rows whose `updated_at` is `>= since`, ordered by
103
+ * `(updated_at, id)`. Used by the polling-based report layer to
104
+ * pick up new inserts and state transitions without subscribing to
105
+ * NOTIFY. Returns an empty array when nothing changed since the
106
+ * anchor.
107
+ */
108
+ listSince(since: Date): Promise<EventRow[]>;
109
+ /**
110
+ * Fetch the most recently created N event rows, newest first. Used by
111
+ * `./cli.sh events list` to surface recent bus activity for operators
112
+ * looking for an event id to inspect with `events report <id>`.
113
+ *
114
+ * @param limit Maximum number of rows to return. Must be a positive integer.
115
+ * @returns Up to `limit` event rows ordered by descending `(created_at, id)`.
116
+ */
117
+ listLatest(limit: number): Promise<EventRow[]>;
118
+ /**
119
+ * Fetch the most recently created N event rows whose topic, start_handler,
120
+ * prompt, or payload (cast to text) case-insensitively contains `needle`.
121
+ * Used by `./cli.sh events list <search>` so the operator can grep the
122
+ * recent bus without piping through `grep`.
123
+ *
124
+ * Substring match via `ILIKE '%needle%'`; the needle is escaped for the
125
+ * SQL `LIKE` metacharacters `%`, `_`, and `\`. No index is added — at
126
+ * interactive scale a sequential scan is fine.
127
+ *
128
+ * @param limit Maximum number of rows to return. Must be a positive integer.
129
+ * @param needle Case-insensitive substring to search for.
130
+ * @returns Up to `limit` matching event rows ordered by descending `(created_at, id)`.
131
+ */
132
+ searchLatest(limit: number, needle: string): Promise<EventRow[]>;
133
+ /**
134
+ * Latest N event rows matching every supplied filter. Drives the
135
+ * agent-facing `event_list` reflection tool, which lets the agent
136
+ * narrow by state, date window, and free-text needle simultaneously
137
+ * — combinations the older {@link listLatest} / {@link searchLatest}
138
+ * pair can't express. Both of those stay as the CLI's call sites.
139
+ *
140
+ * Substring match (when `search` is set) is the same ILIKE-against-
141
+ * `(topic, start_handler, prompt, payload::text)` predicate
142
+ * {@link searchLatest} uses. `dateFrom` is inclusive, `dateTo` is
143
+ * exclusive (`created_at >= $from AND created_at < $to`), so the
144
+ * agent can pass two midnights to get a single local day without
145
+ * worrying about the boundary instant.
146
+ */
147
+ listFiltered(filter: {
148
+ readonly limit: number;
149
+ readonly search?: string;
150
+ readonly state?: EventState;
151
+ readonly dateFrom?: Date;
152
+ readonly dateTo?: Date;
153
+ }): Promise<EventRow[]>;
154
+ /**
155
+ * Patch one or more fields on an existing event by id. Always bumps
156
+ * `updated_at`. No-op if `patch` contains no recognized fields.
157
+ */
158
+ update(id: string, patch: EventPatch): Promise<void>;
159
+ /**
160
+ * Block until an event matching `filter` is in the table. Returns
161
+ * the highest-priority such event (FIFO within priority). Subscribes
162
+ * to `LISTEN events_new` on first call so subsequent waits sleep
163
+ * until a NOTIFY arrives instead of polling.
164
+ *
165
+ * **Read-only**: does not mutate the row. For multi-consumer queues,
166
+ * use {@link waitAndClaim} instead, which atomically transitions the
167
+ * row's state on read.
168
+ *
169
+ * @param filter - Topic / state filter; default `{ states: ["pending"] }`.
170
+ * @param signal - Optional AbortSignal to cancel the wait.
171
+ * @throws If aborted via `signal`.
172
+ */
173
+ waitForNext(filter?: EventFilter, signal?: AbortSignal): Promise<EventRow>;
174
+ /**
175
+ * Atomically transition the highest-priority event in `fromState` to
176
+ * `toState` and return the updated row. Race-safe across multiple
177
+ * concurrent workers via `FOR UPDATE SKIP LOCKED`.
178
+ *
179
+ * Returns `undefined` if no row is in `fromState`. Non-blocking — for
180
+ * blocking semantics, use {@link waitAndClaim}.
181
+ */
182
+ claim(fromState: EventState, toState: EventState): Promise<EventRow | undefined>;
183
+ /**
184
+ * Block until an event in `fromState` is available, then atomically
185
+ * claim it (transition to `toState`) and return it. Combines
186
+ * {@link claim} with the LISTEN/NOTIFY wait loop used by
187
+ * {@link waitForNext}.
188
+ *
189
+ * @throws If aborted via `signal`.
190
+ */
191
+ waitAndClaim(fromState: EventState, toState: EventState, signal?: AbortSignal): Promise<EventRow>;
192
+ /** Subscribe this bus's wake-handler to {@link EVENTS_NEW_CHANNEL}. */
193
+ private ensureListening;
194
+ /** Resolve as soon as the next NOTIFY arrives or `signal` aborts. */
195
+ private waitForNotification;
196
+ /** Single SELECT for the highest-priority matching pending event. */
197
+ private queryOne;
198
+ }