@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.
- package/LICENSE +21 -0
- package/README.md +595 -0
- package/coding-agents/skill.md +321 -0
- package/coding-agents/test-skill.md +223 -0
- package/coding-agents/tools-skill.md +89 -0
- package/dist/flow-messaging/src/client.d.ts +34 -0
- package/dist/flow-messaging/src/client.js +130 -0
- package/dist/flow-messaging/src/index.d.ts +37 -0
- package/dist/flow-messaging/src/index.js +409 -0
- package/dist/simulators/src/index.d.ts +2 -0
- package/dist/simulators/src/index.js +2 -0
- package/dist/simulators/src/page.html +1245 -0
- package/dist/simulators/src/person.html +100 -0
- package/dist/simulators/src/platforms.d.ts +29 -0
- package/dist/simulators/src/platforms.js +72 -0
- package/dist/simulators/src/server.d.ts +201 -0
- package/dist/simulators/src/server.js +759 -0
- package/dist/src/agent.d.ts +504 -0
- package/dist/src/agent.js +818 -0
- package/dist/src/channels.d.ts +178 -0
- package/dist/src/channels.js +243 -0
- package/dist/src/conversation.d.ts +361 -0
- package/dist/src/conversation.js +1487 -0
- package/dist/src/db.d.ts +18 -0
- package/dist/src/db.js +57 -0
- package/dist/src/events.d.ts +17 -0
- package/dist/src/events.js +20 -0
- package/dist/src/http.d.ts +45 -0
- package/dist/src/http.js +269 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +11 -0
- package/dist/src/model.d.ts +21 -0
- package/dist/src/model.js +96 -0
- package/dist/src/names.d.ts +17 -0
- package/dist/src/names.js +38 -0
- package/dist/src/nudges.d.ts +23 -0
- package/dist/src/nudges.js +117 -0
- package/dist/src/postgres.d.ts +20 -0
- package/dist/src/postgres.js +62 -0
- package/dist/src/schedules.d.ts +60 -0
- package/dist/src/schedules.js +249 -0
- package/dist/src/skills.d.ts +12 -0
- package/dist/src/skills.js +69 -0
- package/dist/src/store.d.ts +214 -0
- package/dist/src/store.js +448 -0
- package/dist/src/talk.d.ts +57 -0
- package/dist/src/talk.js +166 -0
- package/dist/src/time.d.ts +71 -0
- package/dist/src/time.js +170 -0
- package/dist/src/tools.d.ts +43 -0
- package/dist/src/tools.js +26 -0
- package/dist/src/traces.d.ts +31 -0
- package/dist/src/traces.js +303 -0
- package/dist/src/types.d.ts +260 -0
- package/dist/src/types.js +2 -0
- package/dist/testing/src/check-endpoint.d.ts +31 -0
- package/dist/testing/src/check-endpoint.js +158 -0
- package/dist/testing/src/chinwag.d.ts +2 -0
- package/dist/testing/src/chinwag.js +6 -0
- package/dist/testing/src/cli.d.ts +2 -0
- package/dist/testing/src/cli.js +563 -0
- package/dist/testing/src/client.d.ts +115 -0
- package/dist/testing/src/client.js +955 -0
- package/dist/testing/src/http-agent.d.ts +47 -0
- package/dist/testing/src/http-agent.js +155 -0
- package/dist/testing/src/index.d.ts +3 -0
- package/dist/testing/src/index.js +2 -0
- package/dist/testing/src/new.d.ts +9 -0
- package/dist/testing/src/new.js +122 -0
- package/dist/testing/src/print-run.d.ts +3 -0
- package/dist/testing/src/print-run.js +22 -0
- package/dist/testing/src/protocol.d.ts +159 -0
- package/dist/testing/src/protocol.js +28 -0
- package/dist/testing/src/report.d.ts +74 -0
- package/dist/testing/src/report.js +90 -0
- package/dist/testing/src/show.d.ts +36 -0
- package/dist/testing/src/show.js +280 -0
- package/dist/testing/src/suite.d.ts +87 -0
- package/dist/testing/src/suite.js +198 -0
- package/examples/cafe/agent/AGENTS.md +10 -0
- package/examples/cafe/agent/skills/allergens/SKILL.md +7 -0
- package/examples/cafe/src/agent.ts +39 -0
- package/examples/cafe/src/main.ts +16 -0
- package/examples/cafe/src/tools.ts +62 -0
- package/examples/cafe/tests/scenarios/latte-correction.yaml +30 -0
- package/examples/cafe/tests/scenarios/order-fails.yaml +26 -0
- package/examples/cafe/tests/tools/get_menu.yaml +14 -0
- package/examples/cafe/tests/tools/place_order.yaml +29 -0
- package/examples/http-agent/server.mjs +32 -0
- package/examples/http-agent/tests/scenarios/opening-hours.yaml +16 -0
- package/flow-messaging/README.md +152 -0
- package/package.json +62 -8
- package/spec/agent-endpoint.openapi.yaml +148 -0
- package/spec/nudges.openapi.yaml +167 -0
- package/spec/request.schema.json +121 -0
- package/spec/response.schema.json +284 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>{{title}} · one person</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { --bg: #f4f4f1; --panel: #ffffff; --ink: #1c1c1a; --quiet: #6b6b66; --line: #e2e1dc; --accent: #2f6fed; }
|
|
9
|
+
@media (prefers-color-scheme: dark) {
|
|
10
|
+
:root:not([data-theme="light"]) { --bg: #161615; --panel: #1f1f1d; --ink: #ecebe6; --quiet: #9a9a94; --line: #33332f; }
|
|
11
|
+
}
|
|
12
|
+
* { box-sizing: border-box; }
|
|
13
|
+
body { margin: 0; font: 14px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--ink); }
|
|
14
|
+
button, select { font: inherit; }
|
|
15
|
+
.top { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--line); background: var(--panel); flex-wrap: wrap; }
|
|
16
|
+
.top h1 { font-size: 15px; margin: 0; font-weight: 600; }
|
|
17
|
+
.top h1 small { color: var(--quiet); font-weight: 400; }
|
|
18
|
+
.spacer { flex: 1; }
|
|
19
|
+
label { display: flex; align-items: center; gap: 6px; color: var(--quiet); }
|
|
20
|
+
select { padding: 6px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--bg); color: var(--ink); max-width: 240px; }
|
|
21
|
+
.note code { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 1px 6px; }
|
|
22
|
+
.ghost { padding: 6px 10px; border: 1px solid var(--line); border-radius: 8px; background: transparent; color: var(--ink); cursor: pointer; }
|
|
23
|
+
.ghost[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
|
|
24
|
+
main { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 400px); justify-content: center; gap: 16px; padding: 16px; height: calc(100vh - 58px); }
|
|
25
|
+
@media (max-width: 820px) { main { grid-auto-flow: row; grid-auto-columns: auto; height: auto; } .device { height: 640px; } }
|
|
26
|
+
.device { display: flex; flex-direction: column; gap: 6px; min-height: 0; }
|
|
27
|
+
.device h2 { margin: 0; font-size: 12px; font-weight: 500; color: var(--quiet); text-align: center; }
|
|
28
|
+
iframe { flex: 1; width: 100%; border: 1px solid var(--line); border-radius: 28px; background: var(--panel); min-height: 0; }
|
|
29
|
+
.note { padding: 24px; text-align: center; color: var(--quiet); }
|
|
30
|
+
</style>
|
|
31
|
+
</head>
|
|
32
|
+
<body>
|
|
33
|
+
<div class="top">
|
|
34
|
+
<h1>{{title}} <small>· one person on every channel</small></h1>
|
|
35
|
+
<div class="spacer"></div>
|
|
36
|
+
<label>Test person <select id="person" aria-label="Test person"></select></label>
|
|
37
|
+
<button class="ghost" id="debug" type="button" aria-pressed="false" title="Show the agent's tool calls in each conversation">Debug</button>
|
|
38
|
+
<button class="ghost" id="clear" type="button" title="Forget this person: their history, schedules and everything the agent kept about them">Clear history</button>
|
|
39
|
+
</div>
|
|
40
|
+
<main id="devices"></main>
|
|
41
|
+
<script>
|
|
42
|
+
// Where the simulators are mounted ("" on their own port).
|
|
43
|
+
const BASE = "{{base}}";
|
|
44
|
+
const $ = (id) => document.getElementById(id);
|
|
45
|
+
const params = new URLSearchParams(location.search);
|
|
46
|
+
const remember = (k, v) => { try { localStorage.setItem(k, v); } catch {} };
|
|
47
|
+
const recall = (k) => { try { return localStorage.getItem(k); } catch { return null; } };
|
|
48
|
+
|
|
49
|
+
let person = params.get("person") || recall("chinwag.person");
|
|
50
|
+
let debug = recall("chinwag.debug") === "1";
|
|
51
|
+
let people = [];
|
|
52
|
+
let channels = [];
|
|
53
|
+
|
|
54
|
+
function note(html) {
|
|
55
|
+
$("devices").innerHTML = `<div class='note'>${html}</div>`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function draw() {
|
|
59
|
+
remember("chinwag.person", person);
|
|
60
|
+
$("person").value = person;
|
|
61
|
+
$("debug").setAttribute("aria-pressed", String(debug));
|
|
62
|
+
const devices = $("devices");
|
|
63
|
+
devices.innerHTML = "";
|
|
64
|
+
for (const c of channels) {
|
|
65
|
+
const device = document.createElement("section");
|
|
66
|
+
device.className = "device";
|
|
67
|
+
const h = document.createElement("h2");
|
|
68
|
+
h.textContent = c.name;
|
|
69
|
+
const frame = document.createElement("iframe");
|
|
70
|
+
frame.title = c.name;
|
|
71
|
+
frame.src = `${BASE}/?${new URLSearchParams({ channel: c.name, person, embed: "1", debug: debug ? "1" : "0" })}`;
|
|
72
|
+
device.append(h, frame);
|
|
73
|
+
devices.appendChild(device);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
$("person").addEventListener("change", () => { person = $("person").value; draw(); });
|
|
78
|
+
$("debug").addEventListener("click", () => { debug = !debug; remember("chinwag.debug", debug ? "1" : "0"); draw(); });
|
|
79
|
+
$("clear").addEventListener("click", async () => {
|
|
80
|
+
const who = people.find((p) => p.phone === person);
|
|
81
|
+
if (!who || !confirm(`Forget ${who.name}? Their history, schedules and everything the agent kept about them go, on every channel.`)) return;
|
|
82
|
+
await fetch(`${BASE}/api/clear?${new URLSearchParams({ person })}`, { method: "POST" });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
(async () => {
|
|
86
|
+
({ people = [], channels = [] } = await (await fetch(`${BASE}/api/platforms`)).json());
|
|
87
|
+
if (!channels.length) return note("This agent has no channels. Declare them in <code>createAgent({ channels: [...] })</code> to test it here.");
|
|
88
|
+
if (!people.length) return note("This agent has no test people. Declare who may be played here in <code>createAgent({ testPeople: [{ name, phone }] })</code>.");
|
|
89
|
+
for (const p of people) {
|
|
90
|
+
const o = document.createElement("option");
|
|
91
|
+
o.value = p.phone;
|
|
92
|
+
o.textContent = `${p.name} · ${p.phone}`;
|
|
93
|
+
$("person").appendChild(o);
|
|
94
|
+
}
|
|
95
|
+
if (!people.some((p) => p.phone === person)) person = people[0].phone;
|
|
96
|
+
draw();
|
|
97
|
+
})();
|
|
98
|
+
</script>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ChannelInfo } from "../../src/index.ts";
|
|
2
|
+
export interface Platform extends ChannelInfo {
|
|
3
|
+
id: "sms" | "whatsapp" | "imessage" | "telegram" | "web";
|
|
4
|
+
name: string;
|
|
5
|
+
/** Shows "typing…" while the agent works. Plain SMS has no typing indicator. */
|
|
6
|
+
typing: boolean;
|
|
7
|
+
/** SMS: texts are billed and split into segments (160 GSM-7 characters, 70 with Unicode). */
|
|
8
|
+
segments?: boolean;
|
|
9
|
+
/** SMS: the carrier answers STOP, START and HELP itself, and blocks texts after STOP. */
|
|
10
|
+
carrierKeywords?: boolean;
|
|
11
|
+
/** SMS: an iPhone's tapback reaches the agent as a text, like `Liked "…"`. */
|
|
12
|
+
tapbacksAsText?: boolean;
|
|
13
|
+
/** The person can react to the agent's messages (WhatsApp). */
|
|
14
|
+
personReactions?: string[];
|
|
15
|
+
/** The simulator models every feature and limit of the real platform. */
|
|
16
|
+
complete: boolean;
|
|
17
|
+
/** Simple Icons slug for the logo (cdn.simpleicons.org). */
|
|
18
|
+
icon: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const PLATFORMS: Platform[];
|
|
21
|
+
export declare function platform(id: string | null): Platform;
|
|
22
|
+
/** The parts of a platform the agent's runtime needs. */
|
|
23
|
+
export declare function channelOf(p: Platform): ChannelInfo;
|
|
24
|
+
/** How an SMS is split: its encoding, segment count, and characters left in the last segment. */
|
|
25
|
+
export declare function smsSegments(text: string): {
|
|
26
|
+
encoding: "GSM-7" | "Unicode";
|
|
27
|
+
segments: number;
|
|
28
|
+
remaining: number;
|
|
29
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// What each chat platform can do, used by the simulators and the test harness, so an
|
|
2
|
+
// agent meets the same rules it would on the real platform. Nothing here sends real messages.
|
|
3
|
+
// SMS and WhatsApp are modelled in full; the others are being brought up to the same standard.
|
|
4
|
+
export const PLATFORMS = [
|
|
5
|
+
{
|
|
6
|
+
id: "sms",
|
|
7
|
+
name: "SMS",
|
|
8
|
+
reactions: false,
|
|
9
|
+
status: false,
|
|
10
|
+
typing: false,
|
|
11
|
+
formatting: "plain",
|
|
12
|
+
// Providers join up to 10 segments into one text: 1,530 GSM-7 characters.
|
|
13
|
+
maxLength: 1530,
|
|
14
|
+
segments: true,
|
|
15
|
+
carrierKeywords: true,
|
|
16
|
+
tapbacksAsText: true,
|
|
17
|
+
complete: true,
|
|
18
|
+
icon: "googlemessages",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "whatsapp",
|
|
22
|
+
name: "WhatsApp",
|
|
23
|
+
reactions: true,
|
|
24
|
+
status: false,
|
|
25
|
+
typing: true,
|
|
26
|
+
formatting: "whatsapp",
|
|
27
|
+
maxLength: 4096,
|
|
28
|
+
replies: true,
|
|
29
|
+
buttons: { max: 3, labelMax: 20 },
|
|
30
|
+
lists: { maxRows: 10, titleMax: 24, buttonMax: 20 },
|
|
31
|
+
linkButton: { labelMax: 20 },
|
|
32
|
+
images: { captionMax: 1024 },
|
|
33
|
+
readReceipts: true,
|
|
34
|
+
personReactions: ["👍", "❤️", "😂", "😮", "😢", "🙏"],
|
|
35
|
+
complete: true,
|
|
36
|
+
icon: "whatsapp",
|
|
37
|
+
},
|
|
38
|
+
{ id: "imessage", name: "iMessage", reactions: true, status: false, typing: true, formatting: "plain", images: { captionMax: 4000 }, complete: false, icon: "imessage" },
|
|
39
|
+
{ id: "telegram", name: "Telegram", reactions: true, status: false, typing: true, formatting: "markdown", images: { captionMax: 1024 }, complete: false, icon: "telegram" },
|
|
40
|
+
{ id: "web", name: "Web", reactions: true, status: true, typing: true, formatting: "markdown", linkButton: { labelMax: 20 }, images: { captionMax: 4000 }, complete: false, icon: "googlechrome" },
|
|
41
|
+
];
|
|
42
|
+
export function platform(id) {
|
|
43
|
+
return PLATFORMS.find((p) => p.id === id) ?? PLATFORMS[0];
|
|
44
|
+
}
|
|
45
|
+
/** The parts of a platform the agent's runtime needs. */
|
|
46
|
+
export function channelOf(p) {
|
|
47
|
+
return {
|
|
48
|
+
reactions: p.reactions,
|
|
49
|
+
status: p.status,
|
|
50
|
+
formatting: p.formatting,
|
|
51
|
+
maxLength: p.maxLength,
|
|
52
|
+
replies: p.replies,
|
|
53
|
+
buttons: p.buttons,
|
|
54
|
+
lists: p.lists,
|
|
55
|
+
linkButton: p.linkButton,
|
|
56
|
+
images: p.images,
|
|
57
|
+
readReceipts: p.readReceipts,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const GSM7 = /^[@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !"#¤%&'()*+,\-./0-9:;<=>?¡A-ZÄÖÑܧ¿a-zäöñüà^{}\\[~\]|€]*$/;
|
|
61
|
+
/** How an SMS is split: its encoding, segment count, and characters left in the last segment. */
|
|
62
|
+
export function smsSegments(text) {
|
|
63
|
+
const gsm = GSM7.test(text);
|
|
64
|
+
// GSM-7 extension characters take two septets.
|
|
65
|
+
const length = gsm ? [...text].reduce((n, c) => n + ("^{}\\[~]|€".includes(c) ? 2 : 1), 0) : [...text].length;
|
|
66
|
+
const single = gsm ? 160 : 70;
|
|
67
|
+
const multi = gsm ? 153 : 67;
|
|
68
|
+
if (length <= single)
|
|
69
|
+
return { encoding: gsm ? "GSM-7" : "Unicode", segments: 1, remaining: single - length };
|
|
70
|
+
const segments = Math.ceil(length / multi);
|
|
71
|
+
return { encoding: gsm ? "GSM-7" : "Unicode", segments, remaining: segments * multi - length };
|
|
72
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
|
+
import { type Attachment, type ChannelInfo, type ChannelSummary, type Deliver, type EventSink, type FilledTemplate, type Incoming, type ListRow, type PlaygroundSink, type TemplateDefinition, type TestPerson, type TranscriptEntry } from "../../src/index.ts";
|
|
3
|
+
import type { Agent } from "../../src/agent.ts";
|
|
4
|
+
/** What the test cloud's simulators need from an agent. */
|
|
5
|
+
export interface AgentConnection {
|
|
6
|
+
/** A person's message; returns its id, or null when the agent answered it without a turn. */
|
|
7
|
+
receive(conversationId: string, incoming: Incoming, deliver: Deliver, options: {
|
|
8
|
+
channel: ChannelInfo;
|
|
9
|
+
channelName?: string;
|
|
10
|
+
}): string | null;
|
|
11
|
+
/**
|
|
12
|
+
* The agent's own channels, if it has any: a simulator plays the one of its kind (SMS, WhatsApp,
|
|
13
|
+
* and the web simulator for a custom UI) with its definition.
|
|
14
|
+
*/
|
|
15
|
+
channels?(conversationId: string): ChannelSummary[];
|
|
16
|
+
/** The person reacted to one of the agent's messages. */
|
|
17
|
+
reacted(conversationId: string, emoji: string, toText: string): void;
|
|
18
|
+
status(): {
|
|
19
|
+
busy: boolean;
|
|
20
|
+
idleForMs: number;
|
|
21
|
+
backgroundTasks: number;
|
|
22
|
+
conversations: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** What the playground needs from an agent. Flow Framework's Agent is one as it is. */
|
|
26
|
+
export interface PlaygroundAgent {
|
|
27
|
+
testPeople(): TestPerson[];
|
|
28
|
+
channels(): ChannelSummary[];
|
|
29
|
+
simulate(input: {
|
|
30
|
+
person: string;
|
|
31
|
+
channel: string;
|
|
32
|
+
message: Incoming;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
conversationId: string;
|
|
35
|
+
messageId: string;
|
|
36
|
+
} | null>;
|
|
37
|
+
testConversation(phone: string): Promise<string | null>;
|
|
38
|
+
transcript(conversationId: string): Promise<TranscriptEntry[]>;
|
|
39
|
+
reacted(conversationId: string, emoji: string, toText: string): void;
|
|
40
|
+
forgetPerson(conversationId: string): Promise<void>;
|
|
41
|
+
attachPlayground(sink: PlaygroundSink | null): void;
|
|
42
|
+
status(): {
|
|
43
|
+
busy: boolean;
|
|
44
|
+
idleForMs: number;
|
|
45
|
+
backgroundTasks: number;
|
|
46
|
+
conversations: number;
|
|
47
|
+
};
|
|
48
|
+
/** The WhatsApp templates the agent declared, shown as WhatsApp would show them. */
|
|
49
|
+
templates?(): TemplateDefinition[];
|
|
50
|
+
/** The Person panel (Flow Framework's Agent has them all): what is set and waiting for a person, nudges, the clock's due work. */
|
|
51
|
+
contact?: Agent["contact"];
|
|
52
|
+
outbox?: Agent["outbox"];
|
|
53
|
+
recordNudge?: Agent["recordNudge"];
|
|
54
|
+
setTimeZone?: Agent["setTimeZone"];
|
|
55
|
+
setQuietHours?: Agent["setQuietHours"];
|
|
56
|
+
runDue?: Agent["runDue"];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The playground's clock: the agent's `now` (createAgent({ now: clock.now })), which the Person
|
|
60
|
+
* panel moves forward to bring scheduled and held messages due. Real time plus an offset.
|
|
61
|
+
*/
|
|
62
|
+
export interface PlaygroundClock {
|
|
63
|
+
now(): number;
|
|
64
|
+
/** Moves the clock to `at` (ms since the epoch); never backwards. */
|
|
65
|
+
set(at: number): void;
|
|
66
|
+
}
|
|
67
|
+
export declare function playgroundClock(): PlaygroundClock;
|
|
68
|
+
export interface SimulatorsOptions {
|
|
69
|
+
/** The agent's name, shown in each simulator's header. */
|
|
70
|
+
title?: string;
|
|
71
|
+
/** The SMS provider's own replies; HELP is usually set to say who the agent is. */
|
|
72
|
+
sms?: {
|
|
73
|
+
helpReply?: string;
|
|
74
|
+
};
|
|
75
|
+
/** How a custom UI shows the links in its params: the first card whose `match` is in the URL. */
|
|
76
|
+
cards?: LinkCard[];
|
|
77
|
+
/** The pages' HTML, for a host with no file system (Cloudflare); else read from this folder. */
|
|
78
|
+
pages?: {
|
|
79
|
+
page: string;
|
|
80
|
+
person: string;
|
|
81
|
+
};
|
|
82
|
+
/** The agent's clock (playgroundClock(), passed to createAgent as `now`): the Person panel can move it. */
|
|
83
|
+
clock?: PlaygroundClock;
|
|
84
|
+
}
|
|
85
|
+
export interface LinkCard {
|
|
86
|
+
/** Text the link's URL contains, such as "/video/". */
|
|
87
|
+
match: string;
|
|
88
|
+
/** The card's button, such as "Start video call". */
|
|
89
|
+
label: string;
|
|
90
|
+
/** What opening it shows: a call screen, or a page of the app (the default). */
|
|
91
|
+
kind?: "call" | "page";
|
|
92
|
+
}
|
|
93
|
+
type Handler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
94
|
+
type Listening = {
|
|
95
|
+
close(): Promise<void>;
|
|
96
|
+
url: string;
|
|
97
|
+
};
|
|
98
|
+
export interface Playground {
|
|
99
|
+
/** Pass to the agent's `events`: the trace and tool calls of test people's conversations. */
|
|
100
|
+
events: EventSink;
|
|
101
|
+
/** Serves the playground on 127.0.0.1 only; a token is optional there. */
|
|
102
|
+
listen(agent: PlaygroundAgent, port: number, options?: {
|
|
103
|
+
token?: string;
|
|
104
|
+
}): Promise<Listening>;
|
|
105
|
+
/** The playground as a request handler under `base`, for a deployed agent's own server: a token is required. */
|
|
106
|
+
handler(agent: PlaygroundAgent, options: {
|
|
107
|
+
token: string;
|
|
108
|
+
base?: string;
|
|
109
|
+
}): Handler;
|
|
110
|
+
}
|
|
111
|
+
export interface TestCloudSimulators {
|
|
112
|
+
events: EventSink;
|
|
113
|
+
listen(agent: AgentConnection, port: number): Promise<Listening>;
|
|
114
|
+
handler(agent: AgentConnection, base?: string): Handler;
|
|
115
|
+
}
|
|
116
|
+
export type Line = {
|
|
117
|
+
kind: "in";
|
|
118
|
+
id: string | null;
|
|
119
|
+
text: string;
|
|
120
|
+
at: number;
|
|
121
|
+
replyTo?: string;
|
|
122
|
+
choice?: boolean;
|
|
123
|
+
attachments?: Array<{
|
|
124
|
+
kind: Attachment["kind"];
|
|
125
|
+
name?: string;
|
|
126
|
+
}>;
|
|
127
|
+
} | {
|
|
128
|
+
kind: "out";
|
|
129
|
+
id: string;
|
|
130
|
+
text: string;
|
|
131
|
+
at: number;
|
|
132
|
+
replyTo?: string;
|
|
133
|
+
buttons?: string[];
|
|
134
|
+
list?: {
|
|
135
|
+
button: string;
|
|
136
|
+
rows: ListRow[];
|
|
137
|
+
};
|
|
138
|
+
/** A button under the message that opens this page. */
|
|
139
|
+
link?: {
|
|
140
|
+
label: string;
|
|
141
|
+
url: string;
|
|
142
|
+
};
|
|
143
|
+
/** A photo above the text, which is its caption. */
|
|
144
|
+
image?: {
|
|
145
|
+
url: string;
|
|
146
|
+
};
|
|
147
|
+
/** The channel's own params the agent filled in (an app_link): shown under the message. */
|
|
148
|
+
fields?: Record<string, unknown>;
|
|
149
|
+
/**
|
|
150
|
+
* A WhatsApp template, by name; declared, with its header, footer and buttons, the text its
|
|
151
|
+
* filled body. Not declared, the text lists the values it was sent with.
|
|
152
|
+
*/
|
|
153
|
+
template?: {
|
|
154
|
+
name: string;
|
|
155
|
+
header?: FilledTemplate["header"];
|
|
156
|
+
footer?: string;
|
|
157
|
+
buttons?: FilledTemplate["buttons"];
|
|
158
|
+
};
|
|
159
|
+
sms?: {
|
|
160
|
+
segments: number;
|
|
161
|
+
encoding: string;
|
|
162
|
+
};
|
|
163
|
+
/** Sent by the SMS carrier itself (STOP, START, HELP replies), not the agent. */
|
|
164
|
+
carrier?: boolean;
|
|
165
|
+
} | {
|
|
166
|
+
kind: "reaction";
|
|
167
|
+
messageId: string;
|
|
168
|
+
emoji: string;
|
|
169
|
+
at: number;
|
|
170
|
+
by: "agent" | "person";
|
|
171
|
+
} | {
|
|
172
|
+
kind: "read";
|
|
173
|
+
messageIds: string[];
|
|
174
|
+
at: number;
|
|
175
|
+
}
|
|
176
|
+
/** A tool call by the agent: shown only in the simulator's debug mode, never to the person. */
|
|
177
|
+
| {
|
|
178
|
+
kind: "tool";
|
|
179
|
+
callId: string;
|
|
180
|
+
tool: string;
|
|
181
|
+
input: unknown;
|
|
182
|
+
at: number;
|
|
183
|
+
status: "running" | "background" | "done" | "failed";
|
|
184
|
+
ms?: number;
|
|
185
|
+
output?: string;
|
|
186
|
+
/** Cloud tests: which kind of response the tool simulator played, and what it changed. */
|
|
187
|
+
played?: string;
|
|
188
|
+
changes?: string[];
|
|
189
|
+
} | {
|
|
190
|
+
kind: "undelivered";
|
|
191
|
+
text: string;
|
|
192
|
+
reason: string;
|
|
193
|
+
at: number;
|
|
194
|
+
};
|
|
195
|
+
export declare function simulators(options: SimulatorsOptions & {
|
|
196
|
+
testCloud: true;
|
|
197
|
+
}): TestCloudSimulators;
|
|
198
|
+
export declare function simulators(options?: SimulatorsOptions & {
|
|
199
|
+
testCloud?: false;
|
|
200
|
+
}): Playground;
|
|
201
|
+
export {};
|