@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.
- package/LICENSE +202 -21
- package/README.md +28 -12
- package/coding-agents/skill.md +32 -0
- package/coding-agents/test-skill.md +10 -10
- package/dist/flow-messaging/src/client.d.ts +7 -1
- package/dist/flow-messaging/src/client.js +13 -1
- package/dist/flow-messaging/src/index.d.ts +38 -3
- package/dist/flow-messaging/src/index.js +235 -39
- package/dist/src/names.d.ts +5 -0
- package/dist/src/names.js +5 -0
- package/dist/testing/src/account.d.ts +4 -0
- package/dist/testing/src/account.js +580 -0
- package/dist/testing/src/chat.d.ts +3 -0
- package/dist/testing/src/chat.js +117 -0
- package/dist/testing/src/chinwag.js +1 -1
- package/dist/testing/src/cli.js +42 -13
- package/dist/testing/src/client.d.ts +2 -0
- package/dist/testing/src/client.js +4 -3
- package/dist/testing/src/flow-login.d.ts +143 -0
- package/dist/testing/src/flow-login.js +249 -0
- package/dist/testing/src/new.d.ts +4 -0
- package/dist/testing/src/new.js +38 -13
- package/docs/agents.md +85 -0
- package/examples/cafe/src/agent.ts +6 -2
- package/examples/cafe/src/channels.ts +20 -0
- package/examples/cafe/src/main.ts +17 -3
- package/flow-messaging/README.md +66 -27
- package/package.json +3 -2
- package/spec/agent-endpoint.openapi.yaml +2 -2
- package/spec/nudges.openapi.yaml +2 -2
package/flow-messaging/README.md
CHANGED
|
@@ -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
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
135
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "An open-source runtime for chat agents that feel like a person texting back.",
|
|
5
|
-
"license": "
|
|
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:
|
|
7
|
-
identifier:
|
|
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
|
package/spec/nudges.openapi.yaml
CHANGED
|
@@ -3,8 +3,8 @@ info:
|
|
|
3
3
|
title: Flow Framework nudges over HTTP
|
|
4
4
|
version: 0.1.0
|
|
5
5
|
license:
|
|
6
|
-
name:
|
|
7
|
-
identifier:
|
|
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
|