@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.
Files changed (124) hide show
  1. package/README.md +145 -63
  2. package/dist/agent/index.d.ts +3 -2
  3. package/dist/agent/index.d.ts.map +1 -1
  4. package/dist/agent/index.js +2 -1
  5. package/dist/agent/index.js.map +1 -1
  6. package/dist/agent/tutor.d.ts +12 -10
  7. package/dist/agent/tutor.d.ts.map +1 -1
  8. package/dist/agent/tutor.js +81 -68
  9. package/dist/agent/tutor.js.map +1 -1
  10. package/dist/agent/walks.d.ts +19 -0
  11. package/dist/agent/walks.d.ts.map +1 -0
  12. package/dist/agent/walks.js +89 -0
  13. package/dist/agent/walks.js.map +1 -0
  14. package/dist/page/page.d.ts +2 -2
  15. package/dist/page/page.d.ts.map +1 -1
  16. package/dist/page/page.js +5 -5
  17. package/dist/page/page.js.map +1 -1
  18. package/dist/page/roles.d.ts +2 -0
  19. package/dist/page/roles.d.ts.map +1 -1
  20. package/dist/page/roles.js +21 -2
  21. package/dist/page/roles.js.map +1 -1
  22. package/dist/pointer/asking.d.ts +25 -0
  23. package/dist/pointer/asking.d.ts.map +1 -0
  24. package/dist/pointer/asking.js +82 -0
  25. package/dist/pointer/asking.js.map +1 -0
  26. package/dist/pointer/flight.d.ts +40 -4
  27. package/dist/pointer/flight.d.ts.map +1 -1
  28. package/dist/pointer/flight.js +139 -17
  29. package/dist/pointer/flight.js.map +1 -1
  30. package/dist/pointer/frames.d.ts +28 -0
  31. package/dist/pointer/frames.d.ts.map +1 -0
  32. package/dist/pointer/frames.js +66 -0
  33. package/dist/pointer/frames.js.map +1 -0
  34. package/dist/pointer/icons.d.ts +8 -0
  35. package/dist/pointer/icons.d.ts.map +1 -0
  36. package/dist/pointer/icons.js +15 -0
  37. package/dist/pointer/icons.js.map +1 -0
  38. package/dist/pointer/menu.d.ts +5 -4
  39. package/dist/pointer/menu.d.ts.map +1 -1
  40. package/dist/pointer/menu.js +56 -47
  41. package/dist/pointer/menu.js.map +1 -1
  42. package/dist/pointer/person.d.ts +13 -2
  43. package/dist/pointer/person.d.ts.map +1 -1
  44. package/dist/pointer/person.js +47 -41
  45. package/dist/pointer/person.js.map +1 -1
  46. package/dist/pointer/pressing.d.ts +6 -0
  47. package/dist/pointer/pressing.d.ts.map +1 -0
  48. package/dist/pointer/pressing.js +32 -0
  49. package/dist/pointer/pressing.js.map +1 -0
  50. package/dist/pointer/sections.d.ts +4 -7
  51. package/dist/pointer/sections.d.ts.map +1 -1
  52. package/dist/pointer/sections.js +11 -17
  53. package/dist/pointer/sections.js.map +1 -1
  54. package/dist/pointer/typing.d.ts +5 -3
  55. package/dist/pointer/typing.d.ts.map +1 -1
  56. package/dist/pointer/typing.js +46 -15
  57. package/dist/pointer/typing.js.map +1 -1
  58. package/dist/server/index.d.ts +34 -0
  59. package/dist/server/index.d.ts.map +1 -0
  60. package/dist/server/index.js +108 -0
  61. package/dist/server/index.js.map +1 -0
  62. package/dist/server/questions.d.ts +30 -0
  63. package/dist/server/questions.d.ts.map +1 -0
  64. package/dist/server/questions.js +136 -0
  65. package/dist/server/questions.js.map +1 -0
  66. package/dist/server/saying.d.ts +31 -0
  67. package/dist/server/saying.d.ts.map +1 -0
  68. package/dist/server/saying.js +124 -0
  69. package/dist/server/saying.js.map +1 -0
  70. package/dist/tutor/acting.d.ts +42 -0
  71. package/dist/tutor/acting.d.ts.map +1 -0
  72. package/dist/tutor/acting.js +180 -0
  73. package/dist/tutor/acting.js.map +1 -0
  74. package/dist/tutor/aloud.d.ts +50 -0
  75. package/dist/tutor/aloud.d.ts.map +1 -0
  76. package/dist/tutor/aloud.js +288 -0
  77. package/dist/tutor/aloud.js.map +1 -0
  78. package/dist/tutor/ask.d.ts +49 -0
  79. package/dist/tutor/ask.d.ts.map +1 -0
  80. package/dist/tutor/ask.js +47 -0
  81. package/dist/tutor/ask.js.map +1 -0
  82. package/dist/tutor/listening.d.ts +17 -0
  83. package/dist/tutor/listening.d.ts.map +1 -0
  84. package/dist/tutor/listening.js +66 -0
  85. package/dist/tutor/listening.js.map +1 -0
  86. package/dist/tutor/options.d.ts +37 -1
  87. package/dist/tutor/options.d.ts.map +1 -1
  88. package/dist/tutor/options.js +1 -1
  89. package/dist/tutor/options.js.map +1 -1
  90. package/dist/tutor/reading.d.ts +58 -0
  91. package/dist/tutor/reading.d.ts.map +1 -0
  92. package/dist/tutor/reading.js +193 -0
  93. package/dist/tutor/reading.js.map +1 -0
  94. package/dist/tutor/text.d.ts +16 -0
  95. package/dist/tutor/text.d.ts.map +1 -0
  96. package/dist/tutor/text.js +119 -0
  97. package/dist/tutor/text.js.map +1 -0
  98. package/dist/tutor/tutor.d.ts +2 -9
  99. package/dist/tutor/tutor.d.ts.map +1 -1
  100. package/dist/tutor/tutor.js +110 -87
  101. package/dist/tutor/tutor.js.map +1 -1
  102. package/dist/tutor/walking.d.ts +26 -8
  103. package/dist/tutor/walking.d.ts.map +1 -1
  104. package/dist/tutor/walking.js +145 -30
  105. package/dist/tutor/walking.js.map +1 -1
  106. package/dist/tutor/walks.d.ts +33 -0
  107. package/dist/tutor/walks.d.ts.map +1 -1
  108. package/dist/tutor/walks.js +121 -13
  109. package/dist/tutor/walks.js.map +1 -1
  110. package/dist/wire/acts.d.ts +60 -0
  111. package/dist/wire/acts.d.ts.map +1 -0
  112. package/dist/wire/acts.js +120 -0
  113. package/dist/wire/acts.js.map +1 -0
  114. package/dist/wire/ask.d.ts +132 -0
  115. package/dist/wire/ask.d.ts.map +1 -0
  116. package/dist/wire/ask.js +102 -0
  117. package/dist/wire/ask.js.map +1 -0
  118. package/dist/wire/index.d.ts +38 -8
  119. package/dist/wire/index.d.ts.map +1 -1
  120. package/dist/wire/index.js +86 -12
  121. package/dist/wire/index.js.map +1 -1
  122. package/docs/macros.md +216 -0
  123. package/docs/write-a-tutor.md +247 -0
  124. 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.23",
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.23"
35
+ "@pinecall/room": "^0.11.25"
31
36
  },
32
37
  "peerDependencies": {
33
- "@pinecall/agents": "^0.11.23"
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.23"
51
+ "@pinecall/agents": "^0.11.25"
47
52
  },
48
53
  "engines": {
49
54
  "node": ">=24"