@flow-engineer/flow 0.0.1 → 0.2.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.
Files changed (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +595 -0
  3. package/coding-agents/skill.md +321 -0
  4. package/coding-agents/test-skill.md +223 -0
  5. package/coding-agents/tools-skill.md +89 -0
  6. package/dist/flow-messaging/src/client.d.ts +34 -0
  7. package/dist/flow-messaging/src/client.js +130 -0
  8. package/dist/flow-messaging/src/index.d.ts +37 -0
  9. package/dist/flow-messaging/src/index.js +409 -0
  10. package/dist/simulators/src/index.d.ts +2 -0
  11. package/dist/simulators/src/index.js +2 -0
  12. package/dist/simulators/src/page.html +1245 -0
  13. package/dist/simulators/src/person.html +100 -0
  14. package/dist/simulators/src/platforms.d.ts +29 -0
  15. package/dist/simulators/src/platforms.js +72 -0
  16. package/dist/simulators/src/server.d.ts +201 -0
  17. package/dist/simulators/src/server.js +759 -0
  18. package/dist/src/agent.d.ts +504 -0
  19. package/dist/src/agent.js +818 -0
  20. package/dist/src/channels.d.ts +178 -0
  21. package/dist/src/channels.js +243 -0
  22. package/dist/src/conversation.d.ts +361 -0
  23. package/dist/src/conversation.js +1487 -0
  24. package/dist/src/db.d.ts +18 -0
  25. package/dist/src/db.js +57 -0
  26. package/dist/src/events.d.ts +17 -0
  27. package/dist/src/events.js +20 -0
  28. package/dist/src/http.d.ts +45 -0
  29. package/dist/src/http.js +269 -0
  30. package/dist/src/index.d.ts +14 -0
  31. package/dist/src/index.js +11 -0
  32. package/dist/src/model.d.ts +21 -0
  33. package/dist/src/model.js +96 -0
  34. package/dist/src/names.d.ts +17 -0
  35. package/dist/src/names.js +38 -0
  36. package/dist/src/nudges.d.ts +23 -0
  37. package/dist/src/nudges.js +117 -0
  38. package/dist/src/postgres.d.ts +20 -0
  39. package/dist/src/postgres.js +62 -0
  40. package/dist/src/schedules.d.ts +60 -0
  41. package/dist/src/schedules.js +249 -0
  42. package/dist/src/skills.d.ts +12 -0
  43. package/dist/src/skills.js +69 -0
  44. package/dist/src/store.d.ts +214 -0
  45. package/dist/src/store.js +448 -0
  46. package/dist/src/talk.d.ts +57 -0
  47. package/dist/src/talk.js +166 -0
  48. package/dist/src/time.d.ts +71 -0
  49. package/dist/src/time.js +170 -0
  50. package/dist/src/tools.d.ts +43 -0
  51. package/dist/src/tools.js +26 -0
  52. package/dist/src/traces.d.ts +31 -0
  53. package/dist/src/traces.js +303 -0
  54. package/dist/src/types.d.ts +260 -0
  55. package/dist/src/types.js +2 -0
  56. package/dist/testing/src/check-endpoint.d.ts +31 -0
  57. package/dist/testing/src/check-endpoint.js +158 -0
  58. package/dist/testing/src/chinwag.d.ts +2 -0
  59. package/dist/testing/src/chinwag.js +6 -0
  60. package/dist/testing/src/cli.d.ts +2 -0
  61. package/dist/testing/src/cli.js +563 -0
  62. package/dist/testing/src/client.d.ts +115 -0
  63. package/dist/testing/src/client.js +955 -0
  64. package/dist/testing/src/http-agent.d.ts +47 -0
  65. package/dist/testing/src/http-agent.js +155 -0
  66. package/dist/testing/src/index.d.ts +3 -0
  67. package/dist/testing/src/index.js +2 -0
  68. package/dist/testing/src/new.d.ts +9 -0
  69. package/dist/testing/src/new.js +122 -0
  70. package/dist/testing/src/print-run.d.ts +3 -0
  71. package/dist/testing/src/print-run.js +22 -0
  72. package/dist/testing/src/protocol.d.ts +159 -0
  73. package/dist/testing/src/protocol.js +28 -0
  74. package/dist/testing/src/report.d.ts +74 -0
  75. package/dist/testing/src/report.js +90 -0
  76. package/dist/testing/src/show.d.ts +36 -0
  77. package/dist/testing/src/show.js +280 -0
  78. package/dist/testing/src/suite.d.ts +87 -0
  79. package/dist/testing/src/suite.js +198 -0
  80. package/examples/cafe/agent/AGENTS.md +10 -0
  81. package/examples/cafe/agent/skills/allergens/SKILL.md +7 -0
  82. package/examples/cafe/src/agent.ts +39 -0
  83. package/examples/cafe/src/main.ts +16 -0
  84. package/examples/cafe/src/tools.ts +62 -0
  85. package/examples/cafe/tests/scenarios/latte-correction.yaml +30 -0
  86. package/examples/cafe/tests/scenarios/order-fails.yaml +26 -0
  87. package/examples/cafe/tests/tools/get_menu.yaml +14 -0
  88. package/examples/cafe/tests/tools/place_order.yaml +29 -0
  89. package/examples/http-agent/server.mjs +32 -0
  90. package/examples/http-agent/tests/scenarios/opening-hours.yaml +16 -0
  91. package/flow-messaging/README.md +152 -0
  92. package/package.json +62 -8
  93. package/spec/agent-endpoint.openapi.yaml +148 -0
  94. package/spec/nudges.openapi.yaml +167 -0
  95. package/spec/request.schema.json +121 -0
  96. package/spec/response.schema.json +284 -0
@@ -0,0 +1,504 @@
1
+ import { type Channel, type ChannelSummary } from "./channels.ts";
2
+ import { Conversation, type Profile, type TranscriptEntry } from "./conversation.ts";
3
+ import { type Database } from "./db.ts";
4
+ import { type EventSink } from "./events.ts";
5
+ import { type AgentFiles } from "./skills.ts";
6
+ import { type ConversationRow, type QuietHours, type StoreApi, type TimeZoneSource } from "./store.ts";
7
+ import { type Tool } from "./tools.ts";
8
+ import { type TracesOptions } from "./traces.ts";
9
+ import type { ChannelInfo, Deliver, Incoming, Model, Outbound, Template, TemplateDefinition, Transcribe } from "./types.ts";
10
+ export interface AgentOptions {
11
+ /** Folder holding AGENTS.md and skills/<name>/SKILL.md. Give this or `files`. */
12
+ dir?: string;
13
+ /** That folder's files as text, by path ("AGENTS.md", "skills/<name>/SKILL.md"), for hosts with no file system. */
14
+ files?: AgentFiles;
15
+ model: Model;
16
+ /**
17
+ * Speech to text for voice notes, with the provider you choose: the agent reads the transcript,
18
+ * marked as a voice note. Without it, it is told a voice note came that it can't listen to.
19
+ */
20
+ transcribe?: Transcribe;
21
+ tools?: Tool[];
22
+ /**
23
+ * Added to the system prompt at the start of every turn, before the model is called: keep it to
24
+ * small facts at hand. It runs on every turn, so a lookup here (an API call, a slow query) delays
25
+ * every reply; give the agent `profile` or a tool for those, which it calls only when it needs them.
26
+ */
27
+ context?: (conversationId: string) => Promise<string> | string;
28
+ /**
29
+ * What the business knows about the person (their account, plan, preferences), as the built-in
30
+ * tool get_user_profile: no input, always about the person in the conversation, and run only when
31
+ * the agent calls it. A tool of yours with that name is used instead.
32
+ */
33
+ profile?: Profile;
34
+ /**
35
+ * Answer some messages without the model: return the reply, "" to send nothing, or null to let
36
+ * the agent reply. `message.channel` is the channel it came in on, by the name in `channels`
37
+ * ("chat" from receive() without a `channelName`). It sees a channel's STOP, START and HELP
38
+ * first, with `message.keyword` set (null for other messages), to keep its own state; Flow Framework
39
+ * still keeps or lifts the STOP whatever it returns or throws, and sends its own reply unless
40
+ * this returns one. The message and the reply are kept in the history (the agent reads them at
41
+ * its next turn) and the transcript, and are `message.in` and `message.out` events marked
42
+ * `answered: true`.
43
+ */
44
+ intercept?: (conversationId: string, text: string, message: {
45
+ keyword: Keyword | null;
46
+ channel: string;
47
+ }) => string | null;
48
+ /**
49
+ * Put every skill's full text in the prompt instead of loading skills on demand. Saves a model
50
+ * round trip per skill; worth it when the skills are few and short.
51
+ */
52
+ inlineSkills?: boolean;
53
+ /**
54
+ * Live status lines (share_updates) on channels that can show them, web and app UIs. Default on;
55
+ * pass a string to replace Flow Framework's guidance on when and what to share.
56
+ */
57
+ shareUpdates?: boolean | string;
58
+ /**
59
+ * The longest a reply waits for a tool that sets no `deadlineMs` of its own; past it the tool
60
+ * moves to the background. Default 60,000 ms. Before that, the reply waits for all of a
61
+ * response's tools unless the person writes again or `partialAfterMs` passes.
62
+ */
63
+ deadlineMs?: number;
64
+ /** When one tool of a response is done and the others are still running this long after, the finished results go back to the agent. Default 5,000 ms. */
65
+ partialAfterMs?: number;
66
+ /** Quiet time after a message before the reply starts, to catch quick bursts. Default 300 ms. */
67
+ debounceMs?: number;
68
+ maxSteps?: number;
69
+ /**
70
+ * How much of a long conversation each model call carries. The latest messages are kept, older
71
+ * ones are left out (the agent is told so). `maxTokens` is the whole call's budget, roughly: default 60% of the model's
72
+ * `contextTokens` when it gives one, else 50,000. A tool result is cut to `toolResultChars`
73
+ * characters (default 12,000).
74
+ */
75
+ history?: {
76
+ maxTokens?: number;
77
+ toolResultChars?: number;
78
+ };
79
+ events?: EventSink[];
80
+ /** This agent's id in the database (several agents can share one). Default "default". */
81
+ id?: string;
82
+ /** Where Flow Framework keeps its state. Default: SQLite in memory (gone on restart). */
83
+ database?: Database;
84
+ /**
85
+ * Instead of `database`: a store that keeps the state elsewhere, such as a client of a store
86
+ * service that holds the database, so this process never holds its credentials. `id` is then
87
+ * the store's to know.
88
+ */
89
+ store?: StoreApi;
90
+ /** The channels people reach the agent on: whatsapp(...), sms(...), customUI(...), each with its own name. */
91
+ channels?: Channel[];
92
+ /**
93
+ * The owner's own key for a person (a member id), from a channel identity, or null when there is
94
+ * none. Identities with the same key are one person.
95
+ */
96
+ resolve?: (identity: {
97
+ channel: string;
98
+ address: string;
99
+ }) => Promise<string | null> | string | null;
100
+ /**
101
+ * The calling code ("+91", "+44", "+1") for phone numbers that come without one. Set it when
102
+ * your SMS provider gives national numbers ("098765 43210"): on every phone channel but
103
+ * WhatsApp (whose numbers are always full), a number with no "+" or "00" is then read as this
104
+ * country's, its trunk 0 dropped (Italy's +39 keeps it), so it joins the same number on
105
+ * WhatsApp. Without it, bare digits are taken as international.
106
+ */
107
+ defaultCountryCode?: string;
108
+ /**
109
+ * The template sent when a nudge meets someone's closed WhatsApp window and no other template fits
110
+ * ("You have a new message"): the nudge is then held until they reply, and a `template.missing`
111
+ * event says which message wanted a template of its own. Declare it in `templates`. Without it a
112
+ * nudge outside the window is refused (outside_window), as before.
113
+ */
114
+ fallbackTemplate?: Template;
115
+ /**
116
+ * Quiet hours in each person's own time ("22:00" to "07:00", the default), when what the business
117
+ * starts waits: a nudge is held until they end (or the person writes), then the agent writes one
118
+ * message for everything held. A person may set their own; `false` turns them off. They apply only
119
+ * where the person's time zone is known: without it nothing is held.
120
+ */
121
+ quietHours?: QuietHours | false;
122
+ /**
123
+ * The business's time zone (IANA): the time the agent is told when the person's zone is not known,
124
+ * and the time a business-started schedule is shown in. Never taken as the person's own: set
125
+ * theirs with setTimeZone, or the agent asks them.
126
+ */
127
+ timeZone?: string;
128
+ /** How often due scheduled messages are looked for. Default 5,000 ms; 0 turns the timer off. */
129
+ scheduleCheckMs?: number;
130
+ /**
131
+ * How long a provider's message id is kept, so a webhook retried within it is taken once.
132
+ * Older ones are deleted, hourly, by one runner, on the scheduled messages' timer. Default 7.
133
+ */
134
+ keepMessageIdsDays?: number;
135
+ /** The clock (tests move it). */
136
+ now?: () => number;
137
+ /**
138
+ * The people who may write from the test playground (`simulate`), and no one else. Each is a
139
+ * real person to the agent, by their phone number: the same conversation whether they write from
140
+ * the playground or, once a channel is live, from the provider.
141
+ */
142
+ testPeople?: TestPerson[];
143
+ /**
144
+ * The WhatsApp templates the owner set up with Meta. A nudge with one of them is checked against
145
+ * it before it goes (values that don't fit throw), the agent is told what it said, and the
146
+ * playground shows it as WhatsApp would. A template not declared here is sent as it is.
147
+ */
148
+ templates?: TemplateDefinition[];
149
+ /**
150
+ * Send every turn to a traces endpoint as a trace. Default: from the environment, on when
151
+ * FLOW_FRAMEWORK_TRACES_KEY is set (address FLOW_FRAMEWORK_TRACES_URL, else https://flow.engineer/observe; FLOW_FRAMEWORK_TRACES_CONTENT=1 for content),
152
+ * never under `node --test`.
153
+ */
154
+ traces?: TracesOptions;
155
+ }
156
+ export interface TestPerson {
157
+ name: string;
158
+ /** Their phone number; on channels that are not phone channels they are `test:<number>`. */
159
+ phone: string;
160
+ }
161
+ /** Where the playground shows what the agent sends to a test person who wrote from it. */
162
+ export type PlaygroundSink = (delivery: {
163
+ conversationId: string;
164
+ person: TestPerson;
165
+ channel: string;
166
+ outbound: Outbound;
167
+ }) => Promise<void> | void;
168
+ export interface AgentStatus {
169
+ busy: boolean;
170
+ /** Milliseconds since the last thing ran anywhere; 0 while busy. */
171
+ idleForMs: number;
172
+ conversations: number;
173
+ backgroundTasks: number;
174
+ }
175
+ export interface ReceiveOptions {
176
+ /** What the channel can show. Default: plain text only (no reactions, no status). */
177
+ channel?: ChannelInfo;
178
+ /** The channel's own id for the message; Flow Framework makes one (m1, m2, ...) otherwise. */
179
+ messageId?: string;
180
+ /** The channel's name. Default "chat". */
181
+ channelName?: string;
182
+ }
183
+ export interface NudgeInput {
184
+ /** The person, by a channel identity they have written from. */
185
+ to: {
186
+ channel: string;
187
+ address: string;
188
+ };
189
+ /** Sent as written, with no model call. */
190
+ text?: string;
191
+ /** The agent gets a turn with this note and writes the message itself, or sends nothing. */
192
+ prompt?: string;
193
+ /** A template the owner already set up with the channel, sent as is (also outside WhatsApp's window). */
194
+ template?: Template;
195
+ /** Send it later, at this time (ms since the epoch), as a scheduled message. */
196
+ at?: number;
197
+ /** The same key twice sends once, also when both calls come at once: the second gets the first's result. */
198
+ idempotencyKey?: string;
199
+ /** Wait for the end of their quiet hours (default true); false sends it now, quiet hours or not. */
200
+ respectQuietHours?: boolean;
201
+ /** A nudge held for quiet hours past this (ms since the epoch; default 24 hours on) is dropped as expired. */
202
+ expiresAt?: number;
203
+ }
204
+ export interface NudgeResult {
205
+ id: string;
206
+ /**
207
+ * sent; silent (the agent chose not to); dropped (a no-contact rule, or their STOP); refused
208
+ * (outside_window, unknown_person); failed (it could not be sent, or the model failed: `reason`
209
+ * says why, and the person was sent nothing about it); scheduled; held (for their quiet hours,
210
+ * until they end or the person writes); or expired (held past its expiresAt, never sent).
211
+ */
212
+ status: NudgeStatus;
213
+ reason?: string;
214
+ }
215
+ export type NudgeStatus = "sent" | "silent" | "dropped" | "refused" | "failed" | "scheduled" | "held" | "expired";
216
+ /** A scheduled message, as the playground lists it. */
217
+ export interface ScheduledItem {
218
+ id: string;
219
+ message: string;
220
+ origin: "person" | "business";
221
+ mode: "message" | "check";
222
+ nextRunAt: number | null;
223
+ timeZone: string;
224
+ respectsQuietHours: boolean;
225
+ summary: string;
226
+ }
227
+ /** A nudge waiting for their quiet hours to end, or for their reply (a closed window). */
228
+ export interface HeldItem {
229
+ id: string;
230
+ channel: string;
231
+ kind: "text" | "prompt" | "template";
232
+ what: string;
233
+ reason: string;
234
+ heldAt: number | null;
235
+ /** When their quiet hours end; null when it waits for their reply. */
236
+ holdUntil: number | null;
237
+ expiresAt: number | null;
238
+ }
239
+ /** What the business and the person set for someone, as the agent goes by it. */
240
+ export interface Contact {
241
+ timeZone: string | null;
242
+ timeZoneSource: TimeZoneSource | null;
243
+ /** The quiet hours that apply to them: their own, the agent's, or none ("off", or no time zone known). */
244
+ quietHours: QuietHours | null;
245
+ quietHoursSource: "person" | "agent" | "off" | "no_time_zone";
246
+ /** They texted STOP. */
247
+ stop: boolean;
248
+ /** Nothing the business starts until each of these (ms since the epoch). */
249
+ pauses: Array<{
250
+ id: string;
251
+ until: number | null;
252
+ }>;
253
+ /** What they asked not to be messaged about, in their words. */
254
+ topics: Array<{
255
+ id: string;
256
+ rule: string;
257
+ until: number | null;
258
+ }>;
259
+ }
260
+ /** A nudge as it stands now: `queued` until it is sent or settled, else how it settled. */
261
+ export interface NudgeState {
262
+ id: string;
263
+ status: NudgeStatus | "queued";
264
+ reason?: string;
265
+ }
266
+ export declare class Agent {
267
+ private readonly conversations;
268
+ private readonly options;
269
+ private readonly settings;
270
+ private readonly store;
271
+ private readonly startedAt;
272
+ /** Conversations in the store, known once resume() ran. */
273
+ private readonly rows;
274
+ private resumed;
275
+ private readonly arriving;
276
+ /** Callers waiting for a queued nudge to settle: its first call, and any with the same key. */
277
+ private readonly nudgeWaiters;
278
+ private readonly now;
279
+ private readonly channelsByName;
280
+ /** Where each identity (channel, address) last wrote from; sends go back the same way. */
281
+ private readonly via;
282
+ private playgroundSink;
283
+ /** Before this, this runner does not ask for the pruning claim again (it, or another runner, had it). */
284
+ private pruneAfter;
285
+ /** `defaultCountryCode`'s digits. */
286
+ private readonly countryCode;
287
+ private readonly traces;
288
+ constructor(options: AgentOptions);
289
+ /**
290
+ * Loads what the database holds and carries on: turns cut off by a restart run again, events not
291
+ * yet handled are handled, background tools that were running are reported as interrupted. A
292
+ * durable database needs this before the first message.
293
+ */
294
+ resume(): Promise<void>;
295
+ /**
296
+ * A message from a person on a channel set up in `channels`. Returns the conversation and the id
297
+ * the agent knows the message by, or null when `intercept` or a keyword answered it. Give the
298
+ * provider's own `messageId`: the same one again (a webhook retried) is not taken twice.
299
+ */
300
+ message(input: {
301
+ channel: string;
302
+ from: string;
303
+ message: string | Incoming;
304
+ messageId?: string;
305
+ }): Promise<{
306
+ conversationId: string;
307
+ messageId: string;
308
+ } | null>;
309
+ /**
310
+ * A message from a test person (by their phone number) in the test playground: exactly what
311
+ * message() does for the same person writing on that channel, but its replies go to the
312
+ * playground. Anyone not in `testPeople` is refused.
313
+ */
314
+ simulate(input: {
315
+ person: string;
316
+ channel: string;
317
+ message: string | Incoming;
318
+ messageId?: string;
319
+ }): Promise<{
320
+ conversationId: string;
321
+ messageId: string;
322
+ } | null>;
323
+ /** They wrote, so they are there: what was held for their quiet hours goes now, in this turn. */
324
+ private released;
325
+ /**
326
+ * Held nudges whose quiet hours are over (or every one of a conversation whose person just wrote)
327
+ * go on; ones past their expiresAt are settled as expired. Released together, they reach one turn.
328
+ */
329
+ private releaseHeld;
330
+ /** Sends the traces of finished turns still waiting for the traces endpoint; call it before the process exits. */
331
+ flushTraces(): Promise<void>;
332
+ /** The templates declared in `templates`. */
333
+ templates(): TemplateDefinition[];
334
+ /** The people declared in `testPeople`. */
335
+ testPeople(): TestPerson[];
336
+ /** A test person's conversation, by their phone number, or null if they have not written yet. */
337
+ testConversation(phone: string): Promise<string | null>;
338
+ /** Where the playground shows what is sent to test people who wrote from it; null detaches it. */
339
+ attachPlayground(sink: PlaygroundSink | null): void;
340
+ /** A conversation as the person saw it, from the store: their messages, the agent's, and its tool calls. */
341
+ transcript(conversationId: string): Promise<TranscriptEntry[]>;
342
+ /**
343
+ * Forgets a person: a turn in progress stops, and everything kept about them goes (history,
344
+ * inbox, background tools, schedules and their runs, nudges, no-contact rules, identities and
345
+ * the conversation). Their next message starts from nothing.
346
+ */
347
+ forgetPerson(conversationId: string): Promise<void>;
348
+ private channelOf;
349
+ /** Messages from one address are taken in order, so a new person is made only once. */
350
+ private take;
351
+ private arrive;
352
+ /**
353
+ * The agent's `intercept`, told the message's channel and whether it is a keyword. A keyword is
354
+ * handled the same whatever the hook does, so its error there is logged, not thrown.
355
+ */
356
+ private intercepted;
357
+ /**
358
+ * A STOP, START or HELP keyword, answered with no model call: what to send back (the hook's own
359
+ * reply, else the channel's, else "" for nothing), and for STOP and START the line the agent
360
+ * learns it by and their write, which holds the message's id. Null for HELP with no reply: the
361
+ * hook's answer, or the agent, takes it like any message.
362
+ */
363
+ private keyword;
364
+ /** The identity a message comes from, made (and linked to its person's conversation) the first time it writes. */
365
+ private conversationFor;
366
+ /**
367
+ * The key the real person has: what the owner's resolve() gives their phone number (on the
368
+ * agent's first phone channel, else this one), else their phone number. So the playground and
369
+ * the provider reach one conversation, on every channel.
370
+ */
371
+ private testPersonKey;
372
+ private testPerson;
373
+ /** A test person's address on a channel: their number on phone channels, else `test:<number>`. */
374
+ private testAddress;
375
+ /** Sends to one identity: to the playground if it last wrote from there, else through the channel's provider. */
376
+ private deliverer;
377
+ /**
378
+ * A message from a person; replies go to `deliver`, the channel it came in on. Returns the id
379
+ * the agent knows the message by, or null when `intercept` or a keyword answered it. The conversation is
380
+ * this id alone: use message() and `channels` for one person across several channels. When
381
+ * `channelName` is one of the agent's channels, its definition (what it shows, its params) is
382
+ * used and replies go to `deliver`: that is how the simulators play an agent's own channels.
383
+ */
384
+ receive(conversationId: string, message: string | Incoming, deliver: Deliver, options?: ReceiveOptions): string | null;
385
+ /**
386
+ * The person reacted to one of the agent's messages; the agent learns it at its next turn. The
387
+ * conversation is loaded from the store when it is not in memory (after a restart).
388
+ */
389
+ reacted(conversationId: string, emoji: string, toText: string): void;
390
+ /** The channel reports that a message did not reach the person; the agent hears about it, also after a restart. */
391
+ undelivered(conversationId: string, text: string, reason: string): void;
392
+ /** Acts on a conversation now when it is in memory, else once it is loaded from the store; never on one the store does not hold. */
393
+ private withConversation;
394
+ conversation(conversationId: string): Conversation | undefined;
395
+ /**
396
+ * A message the business starts. Resolves once it is settled: sent, silent (a prompt nudge the
397
+ * agent chose not to send), dropped (a no-contact rule covers it), refused (outside_window,
398
+ * unknown_person) or failed; or scheduled, for one with `at`.
399
+ */
400
+ /**
401
+ * The person's time zone as the business knows it (an IANA name, "Asia/Kolkata"), by a channel
402
+ * identity they have written from, like a nudge's `to`. Schedules and the agent's time use it from
403
+ * then on. Throws for a name that is not a zone; refused (unknown_person) for someone who never wrote.
404
+ */
405
+ setTimeZone(input: {
406
+ to: {
407
+ channel: string;
408
+ address: string;
409
+ };
410
+ timeZone: string;
411
+ }): Promise<{
412
+ status: "set";
413
+ } | {
414
+ status: "refused";
415
+ reason: "unknown_person";
416
+ }>;
417
+ /**
418
+ * Their own quiet hours, as the business knows them: `{ from, to }`, "off" for none, or null to go
419
+ * back to the agent's. Refused (unknown_person) for someone who never wrote.
420
+ */
421
+ setQuietHours(input: {
422
+ to: {
423
+ channel: string;
424
+ address: string;
425
+ };
426
+ quietHours: QuietHours | "off" | null;
427
+ }): Promise<{
428
+ status: "set";
429
+ } | {
430
+ status: "refused";
431
+ reason: "unknown_person";
432
+ }>;
433
+ /** What the business and the person set for someone, by a channel identity they wrote from; null for someone who never wrote. */
434
+ contact(to: {
435
+ channel: string;
436
+ address: string;
437
+ }): Promise<Contact | null>;
438
+ /** A window-closed prompt nudge got no template from the agent: held until they reply, and the fallback goes now. */
439
+ private windowClosed;
440
+ /** The fallback template for a held nudge, once, and the event that says it wanted a template of its own. */
441
+ private sendFallback;
442
+ /**
443
+ * What is waiting to go to a person: their scheduled messages (theirs and the business's) and the
444
+ * nudges held for their quiet hours or a closed window. The playground shows it.
445
+ */
446
+ outbox(conversationId: string): Promise<{
447
+ schedules: ScheduledItem[];
448
+ held: HeldItem[];
449
+ }>;
450
+ /** Whether the agent has a profile (get_user_profile), so the test cloud knows to play it. */
451
+ hasProfile(): boolean;
452
+ nudge(input: NudgeInput): Promise<NudgeResult>;
453
+ /**
454
+ * Records a nudge and resolves at once, without waiting for its send or turn: `queued`, or what
455
+ * was decided as it was recorded (dropped, refused, scheduled). The same idempotency key gets the
456
+ * first nudge's state now. `nudgeState(id)` reads it later.
457
+ */
458
+ recordNudge(input: NudgeInput): Promise<NudgeState>;
459
+ /** A nudge's state now, or null for an id the store does not hold. */
460
+ nudgeState(id: string): Promise<NudgeState | null>;
461
+ /** A recorded nudge as `nudge()` returns it: settled, or (still queued) once it settles. */
462
+ private outcome;
463
+ private waitFor;
464
+ private release;
465
+ /**
466
+ * Looks for due scheduled messages once and hands each to its conversation. Returns how many.
467
+ * Once an hour, one runner also deletes provider message ids older than `keepMessageIdsDays`.
468
+ */
469
+ runDue(): Promise<number>;
470
+ /** Deletes provider message ids past their keep, in batches, when this runner claims it (at most hourly). */
471
+ private pruneMessageIds;
472
+ /** The agent's channels without their sends: what the simulators and the test cloud play. */
473
+ channels(): ChannelSummary[];
474
+ status(): AgentStatus;
475
+ /** The conversation in memory, made (and loaded from the store, if it is there) when needed. */
476
+ private open;
477
+ /** An inbox row for a conversation: handed to it in memory, or found when it loads. */
478
+ private wake;
479
+ private links;
480
+ /** What a channel can show: its adapter's, else what the conversation was last told. */
481
+ private infoOf;
482
+ private settle;
483
+ /** Their STOP, if they texted one. */
484
+ private stopOf;
485
+ /** Their STOP, or a pause in force: either way nothing the business starts goes now. */
486
+ private blockOf;
487
+ /** When their quiet hours end, if they are in them now; null when not, or when their time zone is not known. */
488
+ private quietEnd;
489
+ private result;
490
+ private mustBeResumed;
491
+ }
492
+ type Keyword = "stop" | "start" | "help";
493
+ /**
494
+ * A phone number as one string however the channel gave it, in E.164: formatting dropped, a 00
495
+ * prefix read as +, and digits with no + taken as international (WhatsApp gives "15550100100" for
496
+ * "+1 555 010 0100"), or, given a `countryCode`, as that country's national number. "" when there
497
+ * are no digits (not a phone number).
498
+ */
499
+ export declare function normalizePhone(address: string, countryCode?: string): string;
500
+ export declare function createAgent(options: AgentOptions): Agent;
501
+ export declare const DEFAULT_QUIET_HOURS: QuietHours;
502
+ /** The quiet hours that apply to a person: their own, else the agent's (the default unless it turned them off). */
503
+ export declare function quietHoursOf(row: Pick<ConversationRow, "quietHours">, agent: QuietHours | false | undefined): QuietHours | null;
504
+ export {};