@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/LICENSE
CHANGED
|
@@ -1,21 +1,202 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Flow Framework (`npm i @flow-engineer/flow`). Formerly chinwag: see [Moving from chinwag](#moving-from-chinwag).
|
|
6
6
|
|
|
7
|
-
[](https://github.com/flow-engineer/flow/blob/main/LICENSE)  
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
@@ -33,6 +33,17 @@ npm start
|
|
|
33
33
|
|
|
34
34
|
`flow new <name>` makes `./<name>/` from the café example (lowercase letters, digits and dashes). Open the link `npm start` prints and text Pip, a café ordering assistant: your starting point. Pick a platform at the top (SMS, WhatsApp, iMessage, Telegram, web), and turn on **Debug** to see every tool call and how fast each message was answered. A model call that fails (a wrong or expired key, say) shows in the chat, in Debug and on the terminal. The café's `src/agent.ts` reads its model id from `CAFE_MODEL` (default: the model for the key you set, see [Models](#models)). The new folder has the coding-agent skills in `.claude/skills/` and an `AGENTS.md` pointing at them, so you can tell Claude Code, Codex or any coding agent what to build.
|
|
35
35
|
|
|
36
|
+
### On your phone, from your laptop
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx @flow-engineer/flow init # the same starter in the current folder; then npm install and the model key
|
|
40
|
+
printf 'hi\n' | npx @flow-engineer/flow chat # talk to it in the terminal: no account
|
|
41
|
+
npx @flow-engineer/flow link # signs in (opens the browser), writes its keys into .env, prints a link
|
|
42
|
+
npm start # open the link on your phone and text the agent
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`flow chat` builds the agent from `src/agent.ts` and writes as its first test person, one message per line of stdin. `flow link` signs in and runs `flow setup` when the folder is not set up yet, then prints the link for `--channel telegram` (the default), `whatsapp` or `imessage`; a channel flow.engineer cannot open yet answers with what to do instead. With `FLOW_MESSAGING_KEY` in `.env`, the starter's `npm start` is on Telegram, plus WhatsApp and iMessage when `.env` has their senders, and connects to Flow Messaging by its outbound event stream, so there is no tunnel or public URL, and messages sent while it was stopped arrive when it starts again ([flow-messaging/README.md](flow-messaging/README.md)). Coding agents: [docs/agents.md](docs/agents.md) has the exit codes and `next` steps.
|
|
46
|
+
|
|
36
47
|
### In a folder of your own
|
|
37
48
|
|
|
38
49
|
```bash
|
|
@@ -67,7 +78,7 @@ echo "OPENROUTER_API_KEY=sk-or-..." > .env
|
|
|
67
78
|
node --env-file=.env src/main.ts
|
|
68
79
|
```
|
|
69
80
|
|
|
70
|
-
Tests run with `npx flow test` (see [Testing](#testing)); `npx flow --help` lists everything the command does.
|
|
81
|
+
Tests run with `npx @flow-engineer/flow test` (see [Testing](#testing)); `npx @flow-engineer/flow --help` lists everything the command does.
|
|
71
82
|
|
|
72
83
|
## How a turn works
|
|
73
84
|
|
|
@@ -356,7 +367,7 @@ expect:
|
|
|
356
367
|
`expect.calls` and `expect.never` name tools that must or must not be called, checked by code from the recorded calls. `now` and `timezone` set the test's clock, and dates in test files are written against it (`{{now + 1d | date}}`), so they never go stale ([coding-agents/test-skill.md](coding-agents/test-skill.md)). `tests/tools/*.yaml` describe how each tool behaves (normal, slow, each error). Run the suite from the agent's folder:
|
|
357
368
|
|
|
358
369
|
```bash
|
|
359
|
-
npx flow test --cloud https://flow.engineer/observe --synthetic --runs 3 --only latte-correction
|
|
370
|
+
npx @flow-engineer/flow test --cloud https://flow.engineer/observe --synthetic --runs 3 --only latte-correction
|
|
360
371
|
```
|
|
361
372
|
|
|
362
373
|
Your agent stays on your machine with its own model and key; the test cloud ([get one](#get-a-test-cloud)) plays the person and the tools and judges each run. The cloud is a hosted service, not part of this package. The command prints pass or fail per run, with the judge's reasons, the cost, and a link to watch each conversation. Before anything is sent it reads every test file as the cloud does, and a mistake stops it with the file and line (`tests/scenarios/latte-correction.yaml:3: person.goal: missing`).
|
|
@@ -415,20 +426,25 @@ Each run's line carries its id (`FAIL latte-correction #1 (whatsapp) trn_…: 6
|
|
|
415
426
|
`--failed` runs again only the scenarios that did not pass in that file (failed, errored or not played), on every channel each plays: a scenario's channels are part of its file, so one channel cannot be picked alone. It goes with `--runs`; with `--only` it stops and says to pick one, since the two would each pick the scenarios. With nothing to run again it prints `Nothing failed in the last session (<id>).` and exits 0 (and says when that session ran only some scenarios); with no file it says to run `flow test` first and exits 1. The loop, for you or a coding agent:
|
|
416
427
|
|
|
417
428
|
```bash
|
|
418
|
-
npx flow test --synthetic
|
|
429
|
+
npx @flow-engineer/flow test --synthetic # the whole suite
|
|
419
430
|
# fix what failed
|
|
420
|
-
npx flow show last
|
|
421
|
-
npx flow test --synthetic --failed
|
|
422
|
-
npx flow test --synthetic
|
|
431
|
+
npx @flow-engineer/flow show last # why: the judge's reasons, the conversation and the tool calls
|
|
432
|
+
npx @flow-engineer/flow test --synthetic --failed # only what did not pass; again until it exits 0
|
|
433
|
+
npx @flow-engineer/flow test --synthetic # the whole suite once more before you finish
|
|
423
434
|
```
|
|
424
435
|
|
|
425
436
|
### Get a test cloud
|
|
426
437
|
|
|
438
|
+
The quick way: `npx @flow-engineer/flow login` (it opens the browser and waits up to 120 s; `npx @flow-engineer/flow login --wait` waits again for the same code, so a coding agent can run it), then `npx @flow-engineer/flow setup` in the agent's folder, which
|
|
439
|
+
writes `FLOW_FRAMEWORK_CLOUD_KEY` (and the traces and Flow Messaging keys, the join code and its
|
|
440
|
+
`FLOW_FRAMEWORK_TRY_URL`, and the shared Telegram, WhatsApp and iMessage senders that are open) into `.env`, filling names `.env` has with no value and setting it to mode 0600; `npx @flow-engineer/flow link` then prints that link to chat with the agent from a phone (and does the sign-in and setup itself when they have not been done). On WhatsApp and iMessage the agent replies only after you write first (WhatsApp: within 24 hours of your last message). The sign-in token is sent only to the server that issued it, over https.
|
|
441
|
+
Always name the package: `npx flow` on its own fetches another npm package. By hand:
|
|
442
|
+
|
|
427
443
|
1. Sign in at https://flow.engineer/observe (Google, GitHub or an email link).
|
|
428
444
|
2. Workspace → New agent.
|
|
429
445
|
3. On the agent's Developers page, "Make a CI key". It starts with `oci_` and is shown once.
|
|
430
446
|
4. Put it in the agent's `.env` as `FLOW_FRAMEWORK_CLOUD_KEY` (never on the command line).
|
|
431
|
-
5. `npx flow test --cloud https://flow.engineer/observe --synthetic` from the agent's folder (`--cloud` is that address by default).
|
|
447
|
+
5. `npx @flow-engineer/flow test --cloud https://flow.engineer/observe --synthetic` from the agent's folder (`--cloud` is that address by default).
|
|
432
448
|
|
|
433
449
|
Your agent runs on your machine with your own model key. The cloud's person simulator, tool simulator and judge are free up to $5 a day per workspace, with 2 sessions at once; test data only (`--synthetic`), and records are deleted after 30 days.
|
|
434
450
|
|
|
@@ -533,7 +549,7 @@ For agents with tools we recommend **Claude Haiku 5.5** (`anthropic/claude-haiku
|
|
|
533
549
|
|
|
534
550
|
## With a coding agent
|
|
535
551
|
|
|
536
|
-
`flow new`
|
|
552
|
+
`flow new` and `flow init` put the skills in your agent's `.claude/skills/`; [docs/agents.md](docs/agents.md) is the short guide from `init` to the phone. They are also in [`coding-agents/`](https://github.com/flow-engineer/flow/tree/main/coding-agents): `skill.md` builds an agent, `tools-skill.md` writes tools and their test files, `test-skill.md` writes scenarios. The café in [`examples/cafe`](https://github.com/flow-engineer/flow/tree/main/examples/cafe) is a complete agent with tests to copy from.
|
|
537
553
|
|
|
538
554
|
## Roadmap
|
|
539
555
|
|
|
@@ -568,7 +584,7 @@ cd agents/my-agent && npm start
|
|
|
568
584
|
|
|
569
585
|
**How is it different from general agent frameworks?** Those give you an agent loop, and a chat is one more thing you build on top. Flow Framework is opinionated about chat: one actor per conversation, double texts folded into one turn, tools that never stall the chat, acknowledgements, channel rules, one person across channels, and schedules built in.
|
|
570
586
|
|
|
571
|
-
**Which channels are real?** WhatsApp, SMS and your own UI (`customUI`) have production definitions today; you plug in any provider by writing `sendMessage`.
|
|
587
|
+
**Which channels are real?** WhatsApp, SMS and your own UI (`customUI`) have production definitions today; you plug in any provider by writing `sendMessage`. Telegram and iMessage are real through Flow Messaging (`fm.telegram()`, `fm.imessage()`, [flow-messaging/README.md](flow-messaging/README.md)); in core they are in the playground for now.
|
|
572
588
|
|
|
573
589
|
**Does it host my agent?** No. It runs in your Node process, on your machine or servers. You pay your model and messaging providers directly.
|
|
574
590
|
|
|
@@ -582,7 +598,7 @@ Flow Framework was published as `chinwag` up to 0.1.13; from 0.2.0 it is `@flow-
|
|
|
582
598
|
|
|
583
599
|
1. `npm uninstall chinwag && npm i @flow-engineer/flow`
|
|
584
600
|
2. Imports: `chinwag` becomes `@flow-engineer/flow`, `chinwag/simulators` becomes `@flow-engineer/flow/simulators`, `chinwag/testing` becomes `@flow-engineer/flow/testing`. The type `ChinwagEvent` is now `FlowEvent` (the old name still works, deprecated).
|
|
585
|
-
3. The command: `npx chinwag ...` becomes `npx flow ...`
|
|
601
|
+
3. The command: `npx chinwag ...` becomes `npx @flow-engineer/flow ...`, and `chinwag ...` in `package.json` scripts becomes `flow ...`. `chinwag` still runs for this release and says it was renamed.
|
|
586
602
|
4. Environment: `CHINWAG_CLOUD`, `CHINWAG_CLOUD_KEY`, `CHINWAG_AGENT_TOKEN`, `CHINWAG_TRACES_KEY`, `CHINWAG_TRACES_URL` and `CHINWAG_TRACES_CONTENT` are now `FLOW_FRAMEWORK_CLOUD`, `FLOW_FRAMEWORK_CLOUD_KEY`, and so on. The old names still work; a new name wins when both are set.
|
|
587
603
|
5. Test results now go to `.flow/last-session.json`; `--failed` and `show last` still read `.chinwag/last-session.json` until the first run writes the new one. Add `.flow/` to `.gitignore`. Files the command keeps on this machine (such as a sign-in) go to `~/.config/flow-framework/`; `~/.config/chinwag/` is still read until then.
|
|
588
604
|
6. Coding-agent skills are now `flow-framework`, `flow-framework-tools` and `flow-framework-tests`: copy them again from `node_modules/@flow-engineer/flow/coding-agents/`.
|
|
@@ -592,4 +608,4 @@ The test cloud, its address and its API are unchanged, and traces keep their `ch
|
|
|
592
608
|
|
|
593
609
|
## License
|
|
594
610
|
|
|
595
|
-
[
|
|
611
|
+
[Apache-2.0](https://github.com/flow-engineer/flow/blob/main/LICENSE)
|
package/coding-agents/skill.md
CHANGED
|
@@ -298,6 +298,38 @@ Open `/playground/?token=...` once (it becomes an HttpOnly cookie and leaves the
|
|
|
298
298
|
with `Authorization: Bearer <token>`. Wrong tokens are limited per address, and every simulated
|
|
299
299
|
message is logged with the person, channel, time and address.
|
|
300
300
|
|
|
301
|
+
## Put it on a phone
|
|
302
|
+
|
|
303
|
+
To chat with the agent from the owner's phone, with no tunnel and no key made by hand, run these
|
|
304
|
+
from the agent's folder (`npx @flow-engineer/flow init` makes a starter in an empty one). Only
|
|
305
|
+
`chat` reads stdin; nothing else asks for input or needs a terminal.
|
|
306
|
+
|
|
307
|
+
```sh
|
|
308
|
+
printf 'hi\nA latte please\n' | npx @flow-engineer/flow chat # locally, no account: one message per line, replies printed
|
|
309
|
+
npx @flow-engineer/flow link --json # signs in and runs setup when needed, then the link; give its say_to_person to the owner
|
|
310
|
+
npx @flow-engineer/flow test --synthetic # the tests, on the key setup wrote
|
|
311
|
+
npm start # the agent; the owner texts it while it runs
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`link` runs `login` first when not signed in: it opens the owner's browser itself and prints the
|
|
315
|
+
link and code at once (with `--json` the first line is `{"status":"pending","verification_uri":...,
|
|
316
|
+
"user_code":...,"next":...}`), waits at most 120 s (`--timeout <s>`), then exits 4 with the code
|
|
317
|
+
kept: give the owner the link and code, and run `link` again (it polls the same code). Then it runs
|
|
318
|
+
`setup`, which writes the test cloud, traces and Flow Messaging keys into `.env`, prints names and
|
|
319
|
+
last 4 characters only, keeps values already there (`--replace` takes the new ones), and is safe to
|
|
320
|
+
run again. `--channel telegram|whatsapp|imessage` picks the channel; a channel flow.engineer cannot
|
|
321
|
+
open yet exits 3 with its `next`. `login`, `setup` and `link` also run on their own.
|
|
322
|
+
|
|
323
|
+
In `src/main.ts`, put `fm.telegram()` from `@flow-engineer/flow/flow-messaging` in the agent's
|
|
324
|
+
channels and call `fm.start(agent, { cursorFile: ".flow/flow-messaging-cursor" })`: with no webhook
|
|
325
|
+
secret it opens Flow Messaging's event stream, an outbound connection, so the laptop needs no public
|
|
326
|
+
URL, and the cursor file lets messages sent while it was stopped arrive when it starts again. The
|
|
327
|
+
starter does this when `.env` has `FLOW_MESSAGING_KEY`. Each error is
|
|
328
|
+
`{"error":{"code","message","next"}}`: run its `next`. Exit codes: 0 done, 1 usage or not signed in,
|
|
329
|
+
2 network, 3 refused, 4 sign-in waiting. Never ask the owner to paste a key into the chat. Always
|
|
330
|
+
name the package (`npx @flow-engineer/flow ...`): `npx flow` on its own fetches another npm package.
|
|
331
|
+
CI never logs in: `setup --ci` says how to give it the cloud key.
|
|
332
|
+
|
|
301
333
|
## Send traces to a dashboard
|
|
302
334
|
|
|
303
335
|
Every turn can go to a traces endpoint as an OTLP/JSON trace, posted to `<url>/v1/traces`, so
|
|
@@ -120,7 +120,7 @@ connects out to the cloud, which plays the person and the tools and judges.
|
|
|
120
120
|
1. Have a test cloud: the hosted one at https://flow.engineer/observe, the default (see "Get a test
|
|
121
121
|
cloud" below), or another with `--cloud <url>` or `FLOW_FRAMEWORK_CLOUD`.
|
|
122
122
|
2. From the agent's folder (it must export `buildAgent` and `tools` from `src/agent.ts`), which reads
|
|
123
|
-
the agent's `.env` itself: `npx flow test --synthetic [--runs 3]` (`npm test -- --synthetic`
|
|
123
|
+
the agent's `.env` itself: `npx @flow-engineer/flow test --synthetic [--runs 3]` (`npm test -- --synthetic`
|
|
124
124
|
in an agent `flow new` made).
|
|
125
125
|
3. It first reads every test file as the cloud does; a mistake stops it with
|
|
126
126
|
`tests/scenarios/<file>.yaml:<line>: <field>: <what is wrong>`, one line each, and nothing is sent.
|
|
@@ -151,17 +151,17 @@ connects out to the cloud, which plays the person and the tools and judges.
|
|
|
151
151
|
|
|
152
152
|
Work in this loop, and never finish on a partial run:
|
|
153
153
|
|
|
154
|
-
1. `npx flow test --synthetic`: the whole suite.
|
|
154
|
+
1. `npx @flow-engineer/flow test --synthetic`: the whole suite.
|
|
155
155
|
2. Fix what failed (the agent, or a test that asks for the wrong thing; say which).
|
|
156
|
-
3. `npx flow test --synthetic --failed`, again until every run in it passes (exit 0).
|
|
157
|
-
4. `npx flow test --synthetic`: the whole suite once more, since a fix can break another scenario.
|
|
156
|
+
3. `npx @flow-engineer/flow test --synthetic --failed`, again until every run in it passes (exit 0).
|
|
157
|
+
4. `npx @flow-engineer/flow test --synthetic`: the whole suite once more, since a fix can break another scenario.
|
|
158
158
|
|
|
159
159
|
### When a run fails
|
|
160
160
|
|
|
161
|
-
1. `npx flow test --synthetic --json > report.json`, and read the runs whose `verdict` is not
|
|
161
|
+
1. `npx @flow-engineer/flow test --synthetic --json > report.json`, and read the runs whose `verdict` is not
|
|
162
162
|
`passed` (`jq '.runs[] | select(.verdict != "passed")' report.json`).
|
|
163
|
-
2. `npx flow show last` (the first run that did not pass; it names the others) or
|
|
164
|
-
`npx flow show <runId>`: the checks with the judge's reasons, then the conversation in time
|
|
163
|
+
2. `npx @flow-engineer/flow show last` (the first run that did not pass; it names the others) or
|
|
164
|
+
`npx @flow-engineer/flow show <runId>`: the checks with the judge's reasons, then the conversation in time
|
|
165
165
|
order with every tool call the simulator played, its input, output and what it changed.
|
|
166
166
|
`--json` gives the whole record.
|
|
167
167
|
3. Decide which is wrong, and say which:
|
|
@@ -169,7 +169,7 @@ Work in this loop, and never finish on a partial run:
|
|
|
169
169
|
- the test: a check asks for the wrong thing, or the scenario cannot reach its goal; fix the YAML;
|
|
170
170
|
- the simulator: a tool output that could not happen, or a person who left the scenario; fix
|
|
171
171
|
`tests/tools/*.yaml` or the scenario's `world` and `person`.
|
|
172
|
-
4. `npx flow test --synthetic --failed`, then `--runs 3 --only <scenario>` on what you fixed, so
|
|
172
|
+
4. `npx @flow-engineer/flow test --synthetic --failed`, then `--runs 3 --only <scenario>` on what you fixed, so
|
|
173
173
|
a pass is not luck.
|
|
174
174
|
5. The whole suite once more.
|
|
175
175
|
|
|
@@ -181,7 +181,7 @@ The owner does these; never ask for the key in chat or print it:
|
|
|
181
181
|
2. Workspace → New agent.
|
|
182
182
|
3. On the agent's Developers page, "Make a CI key". It starts with `oci_` and is shown once.
|
|
183
183
|
4. Put it in the agent's `.env` as `FLOW_FRAMEWORK_CLOUD_KEY` (never on the command line).
|
|
184
|
-
5. `npx flow test --cloud https://flow.engineer/observe --synthetic` from the agent's folder (`--cloud` is that address by default).
|
|
184
|
+
5. `npx @flow-engineer/flow test --cloud https://flow.engineer/observe --synthetic` from the agent's folder (`--cloud` is that address by default).
|
|
185
185
|
|
|
186
186
|
Your agent runs on your machine with your own model key. The cloud's person simulator, tool simulator and judge are free up to $5 a day per workspace, with 2 sessions at once; test data only (`--synthetic`), and records are deleted after 30 days. `--synthetic` is the promise that the test files hold no real customer or member data;
|
|
187
187
|
the CLI refuses a hosted cloud without it and the key. `--environment staging` or `production` is
|
|
@@ -192,7 +192,7 @@ answers, with the session's own token. `--agent-version` (else `GITHUB_SHA`) and
|
|
|
192
192
|
GitHub's branch) are kept with the session. A CI step (GitHub Actions shown):
|
|
193
193
|
|
|
194
194
|
```yaml
|
|
195
|
-
- run: npx flow test --cloud https://flow.engineer/observe --synthetic --environment staging
|
|
195
|
+
- run: npx @flow-engineer/flow test --cloud https://flow.engineer/observe --synthetic --environment staging
|
|
196
196
|
env:
|
|
197
197
|
FLOW_FRAMEWORK_CLOUD_KEY: ${{ secrets.FLOW_FRAMEWORK_CLOUD_KEY }}
|
|
198
198
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
@@ -12,9 +12,15 @@ export declare class FlowMessagingError extends Error {
|
|
|
12
12
|
/** Worth trying again later: rate limits, Flow's or the channel's 5xx, no answer. */
|
|
13
13
|
get retryable(): boolean;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* One of Flow's object IDs as a URL path segment: `<prefix>_` and letters, digits, `_` or `-` (Flow
|
|
17
|
+
* makes `<prefix>_<ULID>`). IDs come from inbound events and callers, and every request carries the
|
|
18
|
+
* key, so anything else (a `/`, `..`, `?`) is refused rather than sent to another path.
|
|
19
|
+
*/
|
|
20
|
+
export declare function idSegment(prefix: string, id: unknown): string;
|
|
15
21
|
export declare class Client {
|
|
16
22
|
private readonly apiKey;
|
|
17
|
-
|
|
23
|
+
readonly baseUrl: string;
|
|
18
24
|
constructor(options: {
|
|
19
25
|
apiKey: string;
|
|
20
26
|
baseUrl: string;
|
|
@@ -32,6 +32,17 @@ export class FlowMessagingError extends Error {
|
|
|
32
32
|
return this.status === 0 || this.status === 429 || this.status >= 500 || (this.type === "idempotency_conflict" && this.channelCode === "in_progress");
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* One of Flow's object IDs as a URL path segment: `<prefix>_` and letters, digits, `_` or `-` (Flow
|
|
37
|
+
* makes `<prefix>_<ULID>`). IDs come from inbound events and callers, and every request carries the
|
|
38
|
+
* key, so anything else (a `/`, `..`, `?`) is refused rather than sent to another path.
|
|
39
|
+
*/
|
|
40
|
+
export function idSegment(prefix, id) {
|
|
41
|
+
if (typeof id !== "string" || !new RegExp(`^${prefix}_[A-Za-z0-9_-]+$`).test(id)) {
|
|
42
|
+
throw new Error(`not a Flow Messaging ${prefix}_ id: ${JSON.stringify(String(id)).slice(0, 80)}`);
|
|
43
|
+
}
|
|
44
|
+
return encodeURIComponent(id);
|
|
45
|
+
}
|
|
35
46
|
export class Client {
|
|
36
47
|
apiKey;
|
|
37
48
|
baseUrl;
|
|
@@ -47,9 +58,10 @@ export class Client {
|
|
|
47
58
|
}
|
|
48
59
|
/** A file's bytes: GET /v1/files/{id} answers them or redirects to a signed URL, which fetch follows without the key. */
|
|
49
60
|
async download(fileId) {
|
|
61
|
+
const path = `/v1/files/${idSegment("file", fileId)}`;
|
|
50
62
|
if (!this.apiKey)
|
|
51
63
|
throw new Error("set FLOW_MESSAGING_KEY (or pass apiKey) to reach Flow Messaging");
|
|
52
|
-
const response = await fetch(
|
|
64
|
+
const response = await fetch(this.baseUrl + path, { headers: { authorization: `Bearer ${this.apiKey}`, "flow-version": API_VERSION }, signal: AbortSignal.timeout(60_000) });
|
|
53
65
|
if (!response.ok)
|
|
54
66
|
throw await errorOf(response);
|
|
55
67
|
return new Uint8Array(await response.arrayBuffer());
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
2
|
import { type Agent, type Channel, type ChannelInfo, type WhatsAppFeature } from "../../src/index.ts";
|
|
3
3
|
export { API_VERSION, FlowMessagingError, verifySignature } from "./client.ts";
|
|
4
4
|
export interface FlowMessagingOptions {
|
|
@@ -12,19 +12,48 @@ export interface FlowMessagingOptions {
|
|
|
12
12
|
export interface FlowChannelOptions {
|
|
13
13
|
/** The Flow sender (`snd_...`) this channel starts conversations from. Default: FLOW_MESSAGING_<CHANNEL>_SENDER. */
|
|
14
14
|
sender?: string;
|
|
15
|
-
/** The agent's name for the channel. Default: whatsapp, telegram or sms. */
|
|
15
|
+
/** The agent's name for the channel. Default: whatsapp, telegram, imessage or sms. */
|
|
16
16
|
name?: string;
|
|
17
17
|
instructions?: string;
|
|
18
18
|
}
|
|
19
|
-
/** What the webhook
|
|
19
|
+
/** What the webhook and the stream need of an agent. */
|
|
20
20
|
export type WebhookAgent = Pick<Agent, "message" | "reacted" | "undelivered">;
|
|
21
|
+
export interface StreamOptions {
|
|
22
|
+
/**
|
|
23
|
+
* A file that keeps the ID of the last event the agent handled, so a restart resumes after it and
|
|
24
|
+
* Flow replays what arrived while the agent was away (for as long as the plan keeps events, 30 days
|
|
25
|
+
* by default). Without it the cursor is in memory: a dropped stream still resumes, a restart starts from now.
|
|
26
|
+
*/
|
|
27
|
+
cursorFile?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Why the stream stopped for good: Flow refused the key or the request, so reconnecting cannot help. */
|
|
30
|
+
export interface StreamStop {
|
|
31
|
+
/** The WebSocket close code, such as 4401 (authentication) or 4403 (permission). */
|
|
32
|
+
code: number;
|
|
33
|
+
type: string;
|
|
34
|
+
message: string;
|
|
35
|
+
}
|
|
36
|
+
export interface StreamHandle {
|
|
37
|
+
/** Closes the stream and waits for the event being handled. */
|
|
38
|
+
close(): Promise<void>;
|
|
39
|
+
/** Resolves when Flow refused the stream for good (a bad or revoked key, say); never after close(). */
|
|
40
|
+
stopped: Promise<StreamStop>;
|
|
41
|
+
}
|
|
21
42
|
/** Telegram as Flow sends it: an inline keyboard of up to 10 buttons (or one link), photos, Markdown, no read receipts for bots. */
|
|
22
43
|
export declare const TELEGRAM: ChannelInfo;
|
|
44
|
+
/**
|
|
45
|
+
* iMessage as Flow sends it: plain text up to 9999 characters, photos, inline replies, tapbacks
|
|
46
|
+
* (other emoji go as emoji reactions), typing and read receipts (the whole conversation). No buttons,
|
|
47
|
+
* lists or templates. No reply window is set: the line only answers people who wrote to it, and a
|
|
48
|
+
* send it will not deliver comes back as Not delivered (outside_window).
|
|
49
|
+
*/
|
|
50
|
+
export declare const IMESSAGE: ChannelInfo;
|
|
23
51
|
export declare function flowMessaging(options?: FlowMessagingOptions): {
|
|
24
52
|
whatsapp: (o?: FlowChannelOptions & {
|
|
25
53
|
disable?: WhatsAppFeature[];
|
|
26
54
|
}) => Channel;
|
|
27
55
|
telegram: (o?: FlowChannelOptions) => Channel;
|
|
56
|
+
imessage: (o?: FlowChannelOptions) => Channel;
|
|
28
57
|
sms: (o?: FlowChannelOptions) => Channel;
|
|
29
58
|
webhook: (agent: WebhookAgent) => (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
30
59
|
receive: (agent: WebhookAgent, request: {
|
|
@@ -34,4 +63,10 @@ export declare function flowMessaging(options?: FlowMessagingOptions): {
|
|
|
34
63
|
status: number;
|
|
35
64
|
body: string;
|
|
36
65
|
}>;
|
|
66
|
+
stream: (agent: WebhookAgent, o?: StreamOptions) => StreamHandle;
|
|
67
|
+
start: (agent: WebhookAgent, o?: StreamOptions & {
|
|
68
|
+
port?: number;
|
|
69
|
+
}) => StreamHandle & {
|
|
70
|
+
mode: "webhook" | "stream";
|
|
71
|
+
};
|
|
37
72
|
};
|