@pinecall/dot 0.11.23 → 0.11.25
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/README.md +145 -63
- package/dist/agent/index.d.ts +3 -2
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +2 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/tutor.d.ts +12 -10
- package/dist/agent/tutor.d.ts.map +1 -1
- package/dist/agent/tutor.js +81 -68
- package/dist/agent/tutor.js.map +1 -1
- package/dist/agent/walks.d.ts +19 -0
- package/dist/agent/walks.d.ts.map +1 -0
- package/dist/agent/walks.js +89 -0
- package/dist/agent/walks.js.map +1 -0
- package/dist/page/page.d.ts +2 -2
- package/dist/page/page.d.ts.map +1 -1
- package/dist/page/page.js +5 -5
- package/dist/page/page.js.map +1 -1
- package/dist/page/roles.d.ts +2 -0
- package/dist/page/roles.d.ts.map +1 -1
- package/dist/page/roles.js +21 -2
- package/dist/page/roles.js.map +1 -1
- package/dist/pointer/asking.d.ts +25 -0
- package/dist/pointer/asking.d.ts.map +1 -0
- package/dist/pointer/asking.js +82 -0
- package/dist/pointer/asking.js.map +1 -0
- package/dist/pointer/flight.d.ts +40 -4
- package/dist/pointer/flight.d.ts.map +1 -1
- package/dist/pointer/flight.js +139 -17
- package/dist/pointer/flight.js.map +1 -1
- package/dist/pointer/frames.d.ts +28 -0
- package/dist/pointer/frames.d.ts.map +1 -0
- package/dist/pointer/frames.js +66 -0
- package/dist/pointer/frames.js.map +1 -0
- package/dist/pointer/icons.d.ts +8 -0
- package/dist/pointer/icons.d.ts.map +1 -0
- package/dist/pointer/icons.js +15 -0
- package/dist/pointer/icons.js.map +1 -0
- package/dist/pointer/menu.d.ts +5 -4
- package/dist/pointer/menu.d.ts.map +1 -1
- package/dist/pointer/menu.js +56 -47
- package/dist/pointer/menu.js.map +1 -1
- package/dist/pointer/person.d.ts +13 -2
- package/dist/pointer/person.d.ts.map +1 -1
- package/dist/pointer/person.js +47 -41
- package/dist/pointer/person.js.map +1 -1
- package/dist/pointer/pressing.d.ts +6 -0
- package/dist/pointer/pressing.d.ts.map +1 -0
- package/dist/pointer/pressing.js +32 -0
- package/dist/pointer/pressing.js.map +1 -0
- package/dist/pointer/sections.d.ts +4 -7
- package/dist/pointer/sections.d.ts.map +1 -1
- package/dist/pointer/sections.js +11 -17
- package/dist/pointer/sections.js.map +1 -1
- package/dist/pointer/typing.d.ts +5 -3
- package/dist/pointer/typing.d.ts.map +1 -1
- package/dist/pointer/typing.js +46 -15
- package/dist/pointer/typing.js.map +1 -1
- package/dist/server/index.d.ts +34 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +108 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/questions.d.ts +30 -0
- package/dist/server/questions.d.ts.map +1 -0
- package/dist/server/questions.js +136 -0
- package/dist/server/questions.js.map +1 -0
- package/dist/server/saying.d.ts +31 -0
- package/dist/server/saying.d.ts.map +1 -0
- package/dist/server/saying.js +124 -0
- package/dist/server/saying.js.map +1 -0
- package/dist/tutor/acting.d.ts +42 -0
- package/dist/tutor/acting.d.ts.map +1 -0
- package/dist/tutor/acting.js +180 -0
- package/dist/tutor/acting.js.map +1 -0
- package/dist/tutor/aloud.d.ts +50 -0
- package/dist/tutor/aloud.d.ts.map +1 -0
- package/dist/tutor/aloud.js +288 -0
- package/dist/tutor/aloud.js.map +1 -0
- package/dist/tutor/ask.d.ts +49 -0
- package/dist/tutor/ask.d.ts.map +1 -0
- package/dist/tutor/ask.js +47 -0
- package/dist/tutor/ask.js.map +1 -0
- package/dist/tutor/listening.d.ts +17 -0
- package/dist/tutor/listening.d.ts.map +1 -0
- package/dist/tutor/listening.js +66 -0
- package/dist/tutor/listening.js.map +1 -0
- package/dist/tutor/options.d.ts +37 -1
- package/dist/tutor/options.d.ts.map +1 -1
- package/dist/tutor/options.js +1 -1
- package/dist/tutor/options.js.map +1 -1
- package/dist/tutor/reading.d.ts +58 -0
- package/dist/tutor/reading.d.ts.map +1 -0
- package/dist/tutor/reading.js +193 -0
- package/dist/tutor/reading.js.map +1 -0
- package/dist/tutor/text.d.ts +16 -0
- package/dist/tutor/text.d.ts.map +1 -0
- package/dist/tutor/text.js +119 -0
- package/dist/tutor/text.js.map +1 -0
- package/dist/tutor/tutor.d.ts +2 -9
- package/dist/tutor/tutor.d.ts.map +1 -1
- package/dist/tutor/tutor.js +110 -87
- package/dist/tutor/tutor.js.map +1 -1
- package/dist/tutor/walking.d.ts +26 -8
- package/dist/tutor/walking.d.ts.map +1 -1
- package/dist/tutor/walking.js +145 -30
- package/dist/tutor/walking.js.map +1 -1
- package/dist/tutor/walks.d.ts +33 -0
- package/dist/tutor/walks.d.ts.map +1 -1
- package/dist/tutor/walks.js +121 -13
- package/dist/tutor/walks.js.map +1 -1
- package/dist/wire/acts.d.ts +60 -0
- package/dist/wire/acts.d.ts.map +1 -0
- package/dist/wire/acts.js +120 -0
- package/dist/wire/acts.js.map +1 -0
- package/dist/wire/ask.d.ts +132 -0
- package/dist/wire/ask.d.ts.map +1 -0
- package/dist/wire/ask.js +102 -0
- package/dist/wire/ask.js.map +1 -0
- package/dist/wire/index.d.ts +38 -8
- package/dist/wire/index.d.ts.map +1 -1
- package/dist/wire/index.js +86 -12
- package/dist/wire/index.js.map +1 -1
- package/docs/macros.md +216 -0
- package/docs/write-a-tutor.md +247 -0
- package/package.json +11 -6
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Write a tutor agent
|
|
2
|
+
|
|
3
|
+
> **Experimental.** `@pinecall/dot` may change in any release. This page is true of the version it ships with.
|
|
4
|
+
|
|
5
|
+
A tutor is an agent that is also a person on your site: a dot in the corner that talks, and a
|
|
6
|
+
pointer of its own that shows the visitor where things are. It never clicks or sends anything for
|
|
7
|
+
them. This page takes you from an empty agent to one that answers a call, gives guided walks, and
|
|
8
|
+
draws your own tools' results on the screen. Each step runs on its own, so stop where you have what
|
|
9
|
+
you need.
|
|
10
|
+
|
|
11
|
+
You need a Pinecall agent project (`pinecall new`, the [tutorial](https://docs.pinecall.io/start/tutorial/))
|
|
12
|
+
and a site you may edit.
|
|
13
|
+
|
|
14
|
+
## 1. The agent extends `Tutor`
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @pinecall/dot @pinecall/agents
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
// agents/pip/agent.tsx
|
|
22
|
+
import { macros, Tutor } from "@pinecall/dot/agent";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* You are Pip, Brightside Dental's front desk, on the visitor's screen. Short sentences: everything you
|
|
26
|
+
* say is read aloud. Show them where things are on the page; never say you clicked something for them.
|
|
27
|
+
*/
|
|
28
|
+
export default class Pip extends Tutor {
|
|
29
|
+
static override macros = macros({});
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
That is a tutor. `Tutor` adds the pointer's tools (`point`, `highlight`, `scrollTo`, `fill`, `home`
|
|
34
|
+
and the rest), the page's events, and a view that prints the screen the visitor sees. If your class
|
|
35
|
+
has a `render()` of its own, end it with `{this.seeing()}`, or the model never reads the page:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
override render() {
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
<p>Ask for the visitor's name before anything else.</p>
|
|
42
|
+
{this.seeing()}
|
|
43
|
+
</>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
A call that no tutor's page joined (the phone, a chat, a site with no dot) is the agent's alone:
|
|
49
|
+
the pointer's tools stay hidden and the view says nothing of a screen. One class serves both.
|
|
50
|
+
|
|
51
|
+
Hold it in the sandbox with `pinecall start`, as any agent.
|
|
52
|
+
|
|
53
|
+
## 2. The site draws the dot
|
|
54
|
+
|
|
55
|
+
The quickest way is the widget:
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
<script type="module" src="https://cloud.pinecall.io/widget/pinecall-widget.js"></script>
|
|
59
|
+
<pinecall-widget agent="pip" look="dot" tutor token-url="/pinecall/token"></pinecall-widget>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`token-url` is a door of yours that mints a seat for the visitor with your org's key (never put the
|
|
63
|
+
key on the page; [tokens](https://docs.pinecall.io/api/tokens/)). Add `walks="sandbox"` while you
|
|
64
|
+
build, to read the sandbox's walks.
|
|
65
|
+
|
|
66
|
+
To own the code, build it from the two packages the widget uses:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { room } from "@pinecall/room";
|
|
70
|
+
import { tutor } from "@pinecall/dot";
|
|
71
|
+
|
|
72
|
+
let pip: ReturnType<typeof tutor> | null = null;
|
|
73
|
+
const call = room({
|
|
74
|
+
tokens: (scope, resume) => ticket(scope, resume), // your door's answer
|
|
75
|
+
gateway: "https://cloud.pinecall.io",
|
|
76
|
+
onUi: (data) => void pip?.take(data),
|
|
77
|
+
});
|
|
78
|
+
pip = tutor(call, {
|
|
79
|
+
label: "Talk to Pip",
|
|
80
|
+
onClick: () => { pip?.wake(); void call.start("talk"); }, // inside the click: audio starts only there
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Open the page, click the dot and ask "what's on this page?". The agent reads the page as roles and
|
|
85
|
+
names, the way Playwright's aria snapshot does, so **name the parts you want it to find**: a
|
|
86
|
+
`<section>` needs an `aria-label` or `aria-labelledby` to be a region; a `<div>` you want to point
|
|
87
|
+
at needs `role="group"` and an `aria-label`. [The README](../README.md#the-page-as-the-agent-reads-it)
|
|
88
|
+
shows what the agent sees.
|
|
89
|
+
|
|
90
|
+
## 3. Write a macro
|
|
91
|
+
|
|
92
|
+
A macro is a guided walk: sentences the agent says as written, with moves hung on their words. Save
|
|
93
|
+
it as `agents/pip/macros/tour.json`:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"name": "tour",
|
|
98
|
+
"title": "Site tour",
|
|
99
|
+
"when": "the visitor asks to be shown around",
|
|
100
|
+
"steps": [
|
|
101
|
+
{
|
|
102
|
+
"say": "Up here are our services. Whitening is the one most people ask about.",
|
|
103
|
+
"beats": [
|
|
104
|
+
{ "word": "Up", "do": "scroll_to", "find": ["region", "Services"] },
|
|
105
|
+
{ "word": "Whitening", "do": "highlight", "find": ["article", "Whitening"] }
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
and give it to the class:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import tour from "./macros/tour.json" with { type: "json" };
|
|
116
|
+
|
|
117
|
+
static override macros = macros({ tour });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Now "show me around" in a call runs it, and `pinecall start` records it as a walk (step 4). The
|
|
121
|
+
[macro reference](macros.md) has every field, the moves, and how to write one that holds up.
|
|
122
|
+
|
|
123
|
+
## 4. Walks a visitor plays with no call
|
|
124
|
+
|
|
125
|
+
As the agent starts, `Tutor` records each sentence of each macro once, in the agent's own voice,
|
|
126
|
+
and publishes them. The terminal says so:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
pip walk tour · 5 sentences · 0:38 · cartesia sonic-3 …
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
A visitor then plays them from the dot's menu with no call and nothing spent. Turn it on by naming
|
|
133
|
+
where the walks are published:
|
|
134
|
+
|
|
135
|
+
```html
|
|
136
|
+
<pinecall-widget … walks></pinecall-widget> <!-- production's; walks="sandbox" for the sandbox's -->
|
|
137
|
+
```
|
|
138
|
+
```ts
|
|
139
|
+
tutor(call, { walks: "https://cloud.pinecall.io/v1/agents/pip/public/walks" }); // ?env=sandbox for the sandbox's
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`pinecall walks list` shows what an agent published and `pinecall walks play tour` plays it in your
|
|
143
|
+
terminal. A sentence you did not edit costs nothing the next time; one you edit is recorded again.
|
|
144
|
+
|
|
145
|
+
**Show some walks on some pages.** Name the pages in the macro (`"pages": ["/security"]`) and the
|
|
146
|
+
menu, and the agent in a call, offer it only there; with no `pages` a walk is for every page
|
|
147
|
+
([Macros](macros.md#walks-for-some-pages)).
|
|
148
|
+
|
|
149
|
+
**Tie a walk to the page.** A section marked `data-pinecall-walk="tour"` makes the menu mark that
|
|
150
|
+
walk while the visitor is on that section. A button of yours marked `data-pinecall-play="tour"` and
|
|
151
|
+
`hidden` is shown while that walk can play, and a click plays it:
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<section id="services" data-pinecall-walk="tour" aria-labelledby="services-h">…</section>
|
|
155
|
+
<button data-pinecall-play="tour" hidden>Show me around</button>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## 5. Say one thing without a macro
|
|
159
|
+
|
|
160
|
+
Mark any element with the line it should say:
|
|
161
|
+
|
|
162
|
+
```html
|
|
163
|
+
<article data-pinecall-say="Family Care covers up to four people for thirty-nine dollars a month.">
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The agent says it, with the pointer on the element, when the visitor asks about it or presses the ▶
|
|
167
|
+
that shows on it during a call. See [Lines on the page](../README.md#lines-on-the-page).
|
|
168
|
+
|
|
169
|
+
## 6. Draw your own tools' results
|
|
170
|
+
|
|
171
|
+
An agent has tools of its own: free times, a booking, a piece of code. Their results are on the
|
|
172
|
+
call's log, so the page says how to draw each by the tool's name, on a card above the dot:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
tutor(call, {
|
|
176
|
+
views: {
|
|
177
|
+
freeSlots: (host, result, tools) => {
|
|
178
|
+
const { times } = result as { times: string[] };
|
|
179
|
+
host.innerHTML = times.map((time) => `<button>${time}</button>`).join("");
|
|
180
|
+
host.onclick = (event) => tools.send((event.target as HTMLElement).textContent ?? "");
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
With the widget, set `widget.views = { … }` on the element. A view may draw anything (React included:
|
|
187
|
+
`createRoot(host)`, and return `() => root.unmount()` to take it down). `tools.send(text)` says
|
|
188
|
+
something to the agent in writing, as the visitor; `tools.hide()` puts the card away.
|
|
189
|
+
|
|
190
|
+
**Two things to get right:**
|
|
191
|
+
|
|
192
|
+
- **The log must carry the tools.** A visitor's seat reads the call's log through the *public*
|
|
193
|
+
projection, which holds no tool runs. Your token door has to ask for the tenant's: `log: "tenant"`
|
|
194
|
+
when it mints (`mint(key, { …, log: "tenant" })` in `@pinecall/room/server`). Without it no card
|
|
195
|
+
ever appears and nothing says why.
|
|
196
|
+
- **The log masks what the agent declared private.** A tool argument listed under `pii` reads
|
|
197
|
+
as `***` there. To fill a form with what the visitor said, have the tool tell the page, to this
|
|
198
|
+
visitor alone:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
if (this.onScreen()) this.call.send("pinecall.ui", { type: "form.fill", name, email });
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
and handle it in `onUi` (`tutor.take(data)` is `false` for a message that is not its own):
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
onUi: (data) => { if (pip?.take(data)) return; if (isFormFill(data)) fillMyForm(data); }
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## 7. Let the visitor type what to show
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
tutor(call, {
|
|
214
|
+
ask: (request) => fetch("/api/ask", { method: "POST", body: JSON.stringify(request) }).then((r) => r.json()),
|
|
215
|
+
onTalk: (message) => startACallAndSay(message),
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
and on your server, with an OpenRouter key, and your Pinecall key for the sentences with slots:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { answer } from "@pinecall/dot/server";
|
|
223
|
+
const voicing = { key: process.env.PINECALL_KEY, agent: "pip" };
|
|
224
|
+
export const POST = async (request) => Response.json(await answer(await request.json(), { key: process.env.OPENROUTER_API_KEY, voicing }));
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The walks' menu gets a field, "Ask me to show you…". The dot plays a walk, with the params picked out
|
|
228
|
+
of what was typed, or points at a thing; a question it cannot show goes to the agent through `onTalk`.
|
|
229
|
+
A walk that takes params is a function the visitor calls by asking: "book it for Jordan Lee,
|
|
230
|
+
jordan@lee.dev" fills the form, "a cleaning on Thursday" searches your times and reads the row that
|
|
231
|
+
fits ([macros](macros.md#a-macro-as-a-function)). Pace the door per visitor: it spends your keys.
|
|
232
|
+
|
|
233
|
+
## 8. Try it, then ship it
|
|
234
|
+
|
|
235
|
+
1. `pinecall start` holds the agent in the sandbox and records its walks there.
|
|
236
|
+
2. Serve your site, with the dot's walks pointing at the sandbox (`walks="sandbox"`), and talk to it.
|
|
237
|
+
Everything a laptop does stays in the sandbox.
|
|
238
|
+
3. `pinecall deploy` runs it in production, where it records its walks as it starts; the dot's
|
|
239
|
+
`walks` then points at production's.
|
|
240
|
+
|
|
241
|
+
## What the tutor will not do
|
|
242
|
+
|
|
243
|
+
It never submits a form or opens another page for the visitor: the one button it presses is one your
|
|
244
|
+
macro names, outside any form or in a search form. It writes into a field only
|
|
245
|
+
what the visitor told it, never into a password, a card, a file or a hidden field. The page's words
|
|
246
|
+
reach the model fenced as data, never as instructions: put what other people wrote (reviews,
|
|
247
|
+
comments) under `data-pinecall-skip` so the agent does not read it at all.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pinecall/dot",
|
|
3
|
-
"version": "0.11.
|
|
4
|
-
"description": "The talking dot: a face that speaks with the agent's voice, and a tutor that walks the page with a pointer of its own",
|
|
3
|
+
"version": "0.11.25",
|
|
4
|
+
"description": "EXPERIMENTAL. The talking dot: a face that speaks with the agent's voice, and a tutor that walks the page with a pointer of its own",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Pinecall <hello@pinecall.io>",
|
|
7
7
|
"repository": {
|
|
@@ -21,16 +21,21 @@
|
|
|
21
21
|
"./agent": {
|
|
22
22
|
"types": "./dist/agent/index.d.ts",
|
|
23
23
|
"import": "./dist/agent/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./server": {
|
|
26
|
+
"types": "./dist/server/index.d.ts",
|
|
27
|
+
"import": "./dist/server/index.js"
|
|
24
28
|
}
|
|
25
29
|
},
|
|
26
30
|
"files": [
|
|
27
|
-
"dist"
|
|
31
|
+
"dist",
|
|
32
|
+
"docs"
|
|
28
33
|
],
|
|
29
34
|
"dependencies": {
|
|
30
|
-
"@pinecall/room": "^0.11.
|
|
35
|
+
"@pinecall/room": "^0.11.25"
|
|
31
36
|
},
|
|
32
37
|
"peerDependencies": {
|
|
33
|
-
"@pinecall/agents": "^0.11.
|
|
38
|
+
"@pinecall/agents": "^0.11.25"
|
|
34
39
|
},
|
|
35
40
|
"peerDependenciesMeta": {
|
|
36
41
|
"@pinecall/agents": {
|
|
@@ -43,7 +48,7 @@
|
|
|
43
48
|
"livekit-client": "^2.22.3",
|
|
44
49
|
"typescript": "7.0.2",
|
|
45
50
|
"vitest": "^5.0.0",
|
|
46
|
-
"@pinecall/agents": "^0.11.
|
|
51
|
+
"@pinecall/agents": "^0.11.25"
|
|
47
52
|
},
|
|
48
53
|
"engines": {
|
|
49
54
|
"node": ">=24"
|