@flow-engineer/flow 0.2.0 → 0.2.1

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.
@@ -1,9 +1,13 @@
1
1
  # Flow Messaging channels
2
2
 
3
- Put a Flow Framework (`npm i @flow-engineer/flow`) agent on real WhatsApp, Telegram and US SMS
3
+ Put a Flow Framework (`npm i @flow-engineer/flow`) agent on real WhatsApp, Telegram, iMessage and US SMS
4
4
  through [Flow Messaging](https://api.flow.engineer) (API version 2026-11-01). `@flow-engineer/flow/flow-messaging` gives you
5
- channels that send through Flow's senders, and a webhook that checks Flow's signed events and hands
6
- them to the agent. It needs nothing beyond the framework (it uses `fetch` and `node:crypto`).
5
+ channels that send through Flow's senders, and two ways to receive Flow's events: the live event
6
+ stream, an outbound WebSocket that needs no public URL, and a webhook that checks Flow's
7
+ signatures. It needs nothing beyond the framework (it uses `fetch`, Node's `WebSocket` and `node:crypto`).
8
+
9
+ On a laptop, `npx @flow-engineer/flow link` signs in, writes `FLOW_MESSAGING_KEY` and the Telegram
10
+ sender into `.env` and prints the link for your phone, so you can skip step 1 below.
7
11
 
8
12
  ```sh
9
13
  npm install @flow-engineer/flow
@@ -26,7 +30,8 @@ Each sender has an ID (`snd_...`), a link to open on your phone, and a join code
26
30
 
27
31
  The sandbox has Telegram today. WhatsApp and iMessage come as they open. A sandbox key lasts 7 days
28
32
  and covers one contact. Live keys (`fk_live_...`) and your own WhatsApp, Telegram or
29
- SMS senders are set up from the dashboard at https://api.flow.engineer.
33
+ SMS senders are set up from the dashboard at https://api.flow.engineer. iMessage lines, Flow's
34
+ shared one included, are arranged with the Flow team.
30
35
 
31
36
  Open the Telegram sandbox sender's link on your phone and send its join code. You are now the
32
37
  sandbox contact your agent can talk to.
@@ -35,31 +40,58 @@ sandbox contact your agent can talk to.
35
40
 
36
41
  ```ts
37
42
  // main.ts
38
- import { createServer } from "node:http";
39
43
  import { createAgent } from "@flow-engineer/flow";
40
44
  import { flowMessaging } from "@flow-engineer/flow/flow-messaging";
41
45
 
42
- const fm = flowMessaging(); // reads FLOW_MESSAGING_KEY and FLOW_MESSAGING_WEBHOOK_SECRET
46
+ const fm = flowMessaging(); // reads FLOW_MESSAGING_KEY (and FLOW_MESSAGING_WEBHOOK_SECRET for a webhook)
43
47
 
44
48
  const agent = createAgent({
45
49
  dir: "./agent", // your agent's folder (AGENTS.md, ...)
46
50
  model: yourModel,
47
51
  // only the channels you have senders for
48
- channels: [fm.telegram(), fm.whatsapp(), fm.sms()],
52
+ channels: [fm.telegram(), fm.whatsapp(), fm.imessage(), fm.sms()],
49
53
  });
50
54
  await agent.resume();
51
55
 
52
- createServer(fm.webhook(agent)).listen(Number(process.env.PORT ?? 8080));
56
+ const live = fm.start(agent, { cursorFile: ".flow/flow-messaging-cursor" });
57
+ live.stopped.then((why) => console.error(`Flow Messaging refused the stream: ${why.message}`));
53
58
  ```
54
59
 
55
- Already have an HTTP server? Hand the raw body and headers to `fm.receive` instead:
60
+ That's it on a laptop: with no webhook secret, `fm.start` opens the event stream (`fm.stream(agent)`
61
+ does only that).
62
+
63
+ ### The event stream
64
+
65
+ `GET /v1/stream` is a WebSocket the agent opens to Flow, with the key offered as a subprotocol
66
+ (Node's `WebSocket` cannot set headers). It asks only for the events the adapter handles and hands
67
+ each, in log order, to the same handler as the webhook.
68
+
69
+ - **Acknowledged once handled.** The stream has no ack frame for events; it resumes from `after`.
70
+ The adapter moves its cursor past an event only once the agent has taken it, so an event that
71
+ fails, or arrives as the socket drops, comes again.
72
+ - **Reconnects.** A dropped socket, Flow's `reconnect` frame (it restarts, or a connection reaches
73
+ 24 hours) and a failed event reconnect with `after` set to the last handled event: at once the
74
+ first time, then backing off to 30 s, or after Flow's `retry_after` (too many streams for the
75
+ key, Flow busy). An event that fails 5 times in a row is passed over, so it cannot hold up every
76
+ later one; it stays in Flow's log (`GET /v1/events`).
77
+ - **Offline messages.** With `cursorFile`, the last handled event's ID is kept on disk, so after a
78
+ restart Flow replays everything that came while the agent was stopped, for as long as your plan
79
+ keeps events (30 days by default). Without it, a dropped stream still resumes, but a restart
80
+ starts from now. A cursor older than Flow's log starts again from now, and says so.
81
+ - **Stops for good** on a refused key or request (close codes 4401, 4403, 4400): `stopped`
82
+ resolves with the code, type and Flow's message. `close()` ends the stream.
83
+ - **Replies go over HTTP**, with their Idempotency-Keys, so they never wait for the stream.
84
+ - The stream cannot send `Flow-Version`, so its events come in the version your app was pinned to.
85
+
86
+ ### Or a webhook
87
+
88
+ Already have an HTTP server? Hand the raw body and headers to `fm.receive`, or serve `fm.webhook(agent)`
89
+ (with a webhook secret set, `fm.start` serves it on `port`, default `PORT` else 8080):
56
90
 
57
91
  ```ts
58
92
  const { status, body } = await fm.receive(agent, { body: rawBody, headers: req.headers });
59
93
  ```
60
94
 
61
- ### 3. Point Flow's webhook at it
62
-
63
95
  Flow posts events to a public HTTPS URL. Create a webhook endpoint for it:
64
96
 
65
97
  ```sh
@@ -72,32 +104,31 @@ curl https://api.flow.engineer/v1/webhook_endpoints \
72
104
  The answer holds the signing secret (`secret`, `whsec_...`), shown once. Save it as
73
105
  `FLOW_MESSAGING_WEBHOOK_SECRET`.
74
106
 
75
- On a laptop there is no public URL. Run a tunnel to the port (for example
76
- `cloudflared tunnel --url http://localhost:8080`) and register that URL.
107
+ On a laptop there is no public URL: use the event stream instead.
77
108
 
78
109
  ## Environment
79
110
 
80
111
  | Variable | What it is |
81
112
  |---|---|
82
113
  | `FLOW_MESSAGING_KEY` | Your app's API key, `fk_test_...` or `fk_live_...`. Keep it on your server. |
83
- | `FLOW_MESSAGING_WEBHOOK_SECRET` | The webhook endpoint's signing secret, `whsec_...`. While you rotate it, give both, comma-separated. |
84
- | `FLOW_MESSAGING_TELEGRAM_SENDER`, `FLOW_MESSAGING_WHATSAPP_SENDER`, `FLOW_MESSAGING_SMS_SENDER` | Each channel's sender, `snd_...`. Needed to start conversations (nudges, templates) and to tell senders apart. Replies to people who wrote first work without it. |
114
+ | `FLOW_MESSAGING_WEBHOOK_SECRET` | The webhook endpoint's signing secret, `whsec_...`. While you rotate it, give both, comma-separated. Without it, `fm.start` uses the event stream. |
115
+ | `FLOW_MESSAGING_TELEGRAM_SENDER`, `FLOW_MESSAGING_WHATSAPP_SENDER`, `FLOW_MESSAGING_IMESSAGE_SENDER`, `FLOW_MESSAGING_SMS_SENDER` | Each channel's sender, `snd_...`. Needed to start conversations (nudges, templates) and to tell senders apart. Replies to people who wrote first work without it. |
85
116
  | `FLOW_MESSAGING_BASE_URL` | Optional. Default `https://api.flow.engineer`. |
86
117
 
87
118
  Each of these can also be passed in code: `flowMessaging({ apiKey, webhookSecret, baseUrl })`, and
88
- `fm.telegram({ sender, name, instructions })` (the same for `whatsapp` and `sms`).
119
+ `fm.telegram({ sender, name, instructions })` (the same for `whatsapp`, `imessage` and `sms`).
89
120
 
90
121
  ## What it maps
91
122
 
92
123
  **Out (agent to person):**
93
124
 
94
- - Text: Markdown on Telegram, plain text on WhatsApp and SMS.
125
+ - Text: Markdown on Telegram, plain text on WhatsApp, iMessage and SMS.
95
126
  - Reply buttons and lists, sent as Flow `buttons`. WhatsApp shows up to 3 reply buttons, or a list
96
- for 4 to 10. Telegram shows an inline keyboard.
97
- - A link: one URL button (WhatsApp's call-to-action button).
127
+ for 4 to 10. Telegram shows an inline keyboard. iMessage has none, so the agent is not offered them.
128
+ - A link: one URL button (WhatsApp's call-to-action button); on iMessage, the URL on its own line.
98
129
  - A photo: a media message with the text as its caption. Flow's button messages have no image
99
130
  header, so a photo with buttons or a link goes just before them.
100
- - Quotes (`reply_to`), reactions, typing, and read receipts (WhatsApp).
131
+ - Quotes (`reply_to`), reactions (tapbacks on iMessage), typing, and read receipts (WhatsApp, iMessage).
101
132
  - WhatsApp templates, by name or `tpl_` ID, with their header (text, or media by URL or file ID),
102
133
  body values (`{{1}}`, `{{2}}`, ...) and button values (URL suffixes, quick-reply payloads).
103
134
 
@@ -122,24 +153,32 @@ Each of these can also be passed in code: `flowMessaging({ apiKey, webhookSecret
122
153
  template.
123
154
  - A permanent refusal (opted out, no consent, ...) reaches the agent once, with "don't resend it".
124
155
  - Signatures are checked: HMAC-SHA256, five minutes' tolerance, and any of the secrets you gave.
125
- - An event Flow delivers twice reaches the agent once. A failure answers 500, so Flow delivers the
126
- event again.
156
+ - An event Flow delivers twice (or by both the stream and a webhook) reaches the agent once. On the
157
+ webhook a failure answers 500, so Flow delivers the event again; on the stream it comes again on
158
+ the next connection.
127
159
  - `contact.deleted` makes it forget the contact.
128
160
  - Keys and message text are never logged.
129
161
 
130
- What it remembers (conversations, contacts, event IDs) is kept in memory. After a restart, the
162
+ What it remembers (conversations, contacts, event IDs) is kept in memory, except the stream's
163
+ `cursorFile`. After a restart, the
131
164
  first send to someone starts from the channel's sender, and Flow puts it into the open conversation
132
165
  if there is one.
133
166
 
134
- The framework has no Telegram channel kind yet, so Telegram is a custom channel. iMessage is not in this
135
- package yet.
167
+ The framework has no Telegram or iMessage channel kind yet, so both are custom channels. On iMessage
168
+ a person is their phone number, or their email address when that is their handle; they are not joined
169
+ with the same number on WhatsApp or SMS.
170
+
171
+ **iMessage replies only.** On Flow's shared iMessage sender (as on WhatsApp after 24 hours) the agent
172
+ can only answer someone who wrote first. A nudge or scheduled message to anyone else fails, and
173
+ reaches the agent as "Not delivered" (`outside_window`). Ask people to write first with the line's
174
+ opt-in link. Text takes up to 9999 characters; there are no buttons, lists or templates.
136
175
 
137
176
  ## Also exported
138
177
 
139
178
  - `verifySignature(body, header, secrets)`: checks a `Flow-Signature` header yourself.
140
179
  - `FlowMessagingError`: the API's typed error (`status`, `type`, `channelCode`, `retryAfter`).
141
180
  - `API_VERSION`: the API version every request asks for, `2026-11-01`.
142
- - `TELEGRAM`: the Telegram channel's limits, as a framework `ChannelInfo`.
181
+ - `TELEGRAM`, `IMESSAGE`: the Telegram and iMessage channels' limits, as framework `ChannelInfo`s.
143
182
 
144
183
  ## Links
145
184
 
@@ -149,4 +188,4 @@ package yet.
149
188
 
150
189
  ## License
151
190
 
152
- MIT, as the rest of this repository.
191
+ Apache-2.0, as the rest of this repository.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@flow-engineer/flow",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "An open-source runtime for chat agents that feel like a person texting back.",
5
- "license": "MIT",
5
+ "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/flow-engineer/flow.git"
@@ -42,6 +42,7 @@
42
42
  "examples/cafe/tests",
43
43
  "examples/http-agent",
44
44
  "flow-messaging/README.md",
45
+ "docs/agents.md",
45
46
  "coding-agents",
46
47
  "spec",
47
48
  "README.md",
@@ -3,8 +3,8 @@ info:
3
3
  title: Flow Framework agent endpoint
4
4
  version: 0.1.0
5
5
  license:
6
- name: MIT
7
- identifier: MIT
6
+ name: Apache-2.0
7
+ identifier: Apache-2.0
8
8
  description: |
9
9
  The HTTP endpoint an agent on any framework, in any language, exposes so Flow Framework's test suite
10
10
  can talk to it: `flow test --agent-url <url>`. Implement this one operation and the test
@@ -3,8 +3,8 @@ info:
3
3
  title: Flow Framework nudges over HTTP
4
4
  version: 0.1.0
5
5
  license:
6
- name: MIT
7
- identifier: MIT
6
+ name: Apache-2.0
7
+ identifier: Apache-2.0
8
8
  description: |
9
9
  The endpoint `nudgeHandler(agent, { token, base })` serves in the agent's own server, so a
10
10
  backend that is a separate service can send nudges (messages the business starts) without a