@agent-native/core 0.172.3 → 0.172.6

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 (55) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/calendar/actions/get-settings.ts +2 -5
  3. package/corpus/templates/calendar/actions/list-events.ts +21 -80
  4. package/corpus/templates/calendar/actions/update-settings.ts +11 -16
  5. package/corpus/templates/calendar/actions/view-screen.ts +25 -10
  6. package/corpus/templates/calendar/app/components/calendar/CommandPalette.tsx +7 -1
  7. package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +5 -2
  8. package/corpus/templates/calendar/app/lib/calendar-timezone.ts +3 -18
  9. package/corpus/templates/calendar/app/lib/event-form-utils.ts +3 -62
  10. package/corpus/templates/calendar/changelog/2026-07-22-calendar-grid-local-time.md +6 -0
  11. package/corpus/templates/calendar/changelog/2026-08-19-the-calendar-loads-again-for-accounts-whose-saved-timezone-w.md +6 -0
  12. package/corpus/templates/calendar/server/handlers/settings.ts +8 -24
  13. package/corpus/templates/calendar/server/lib/calendar-settings.ts +58 -0
  14. package/corpus/templates/calendar/server/lib/find-time.ts +15 -93
  15. package/corpus/templates/calendar/shared/settings.ts +15 -9
  16. package/corpus/templates/calendar/shared/timezone.ts +127 -0
  17. package/corpus/templates/factory/AGENTS.md +5 -3
  18. package/corpus/templates/factory/actions/babysit-pull-request.ts +6 -0
  19. package/corpus/templates/factory/actions/list-factory-automations.ts +2 -2
  20. package/corpus/templates/factory/actions/save-factory-automation.ts +0 -5
  21. package/corpus/templates/factory/changelog/2026-08-24-slack-checking-works-for-teammate-created-factories.md +6 -0
  22. package/corpus/templates/factory/changelog/2026-08-24-teammates-can-edit-and-run-factory-jobs.md +6 -0
  23. package/corpus/templates/factory/server/lib/factory-scope.ts +8 -0
  24. package/corpus/templates/factory/server/lib/require-factory-automation.ts +1 -9
  25. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +2 -1
  26. package/corpus/templates/slides/actions/list-decks.ts +79 -5
  27. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +21 -1
  28. package/corpus/templates/slides/app/components/editor/ShareDialog.tsx +49 -21
  29. package/corpus/templates/slides/app/context/DeckContext.tsx +49 -21
  30. package/corpus/templates/slides/app/pages/DeckEditor.tsx +2 -2
  31. package/corpus/templates/slides/app/pages/Index.tsx +11 -13
  32. package/corpus/templates/slides/changelog/2026-08-24-deck-previews-and-sharing-live-in-the-overflow-menu.md +5 -0
  33. package/dist/agent/production-agent.js +5 -1
  34. package/dist/agent/types.d.ts +5 -0
  35. package/dist/automations/service.d.ts +6 -0
  36. package/dist/automations/service.js +20 -0
  37. package/dist/client/settings/SettingsPanel.js +80 -33
  38. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  39. package/dist/jobs/run-now.js +2 -2
  40. package/dist/observability/posthog-ai.d.ts +36 -11
  41. package/dist/observability/posthog-ai.js +99 -30
  42. package/dist/observability/routes.js +9 -2
  43. package/dist/observability/traces.js +330 -142
  44. package/dist/tracking/providers.js +9 -3
  45. package/dist/vite/client.js +5 -2
  46. package/docs/content/docs-components.mdx +20 -1
  47. package/docs/content/observability.mdx +27 -10
  48. package/docs/content/template-calendar-agent.mdx +63 -26
  49. package/docs/content/template-calendar-developers.mdx +165 -40
  50. package/docs/content/template-calendar-features.mdx +276 -0
  51. package/docs/content/template-calendar-integrations.mdx +78 -0
  52. package/docs/content/template-calendar.mdx +56 -123
  53. package/package.json +1 -1
  54. package/docs/content/template-calendar-booking-links.mdx +0 -73
  55. package/docs/content/template-calendar-scheduling.mdx +0 -128
@@ -140,11 +140,16 @@ function createPostHogProvider(apiKey, host, errorTracking) {
140
140
  enqueue(`${host}/i/v0/e/`, JSON.stringify({
141
141
  api_key: apiKey,
142
142
  event: event.name,
143
+ // Top level, NOT inside `properties`: PostHog reads the event time from
144
+ // the payload root and treats a `properties.timestamp` as an ordinary
145
+ // custom property, stamping the event with its ingestion time instead.
146
+ // An agent run emits its whole tree in one burst at the end, so that
147
+ // collapsed a five-minute waterfall into the 100ms it took to flush.
148
+ timestamp: event.timestamp,
143
149
  properties: {
144
150
  distinct_id: distinctId,
145
151
  ...properties,
146
152
  ...(event.sessionId ? { $session_id: event.sessionId } : {}),
147
- timestamp: event.timestamp,
148
153
  },
149
154
  }));
150
155
  };
@@ -176,10 +181,10 @@ function createPostHogProvider(apiKey, host, errorTracking) {
176
181
  api_key: apiKey,
177
182
  event: event.name,
178
183
  distinct_id: distinctId,
184
+ timestamp: event.timestamp,
179
185
  properties: {
180
186
  ...event.properties,
181
187
  ...(event.sessionId ? { $session_id: event.sessionId } : {}),
182
- timestamp: event.timestamp,
183
188
  },
184
189
  }));
185
190
  },
@@ -218,7 +223,8 @@ export function sendPostHogEvent(name, properties, distinctId) {
218
223
  api_key: apiKey,
219
224
  event: name,
220
225
  distinct_id: distinctId,
221
- properties: { ...properties, timestamp: new Date().toISOString() },
226
+ timestamp: new Date().toISOString(),
227
+ properties,
222
228
  }));
223
229
  return true;
224
230
  }
@@ -1680,6 +1680,10 @@ function virtualModuleIdFromRuntimeUrl(runtimeUrl) {
1680
1680
  }
1681
1681
  async function loadMountedEmbedRuntimeModule(server, runtimeUrl) {
1682
1682
  const virtualId = virtualModuleIdFromRuntimeUrl(runtimeUrl);
1683
+ // transform import to encode virtual modules in vite as these imports don't work in the browser
1684
+ const result = await server.transformRequest(virtualId ?? runtimeUrl);
1685
+ if (typeof result?.code === "string")
1686
+ return result.code;
1683
1687
  if (virtualId) {
1684
1688
  const loaded = await server.pluginContainer?.load?.(virtualId);
1685
1689
  if (typeof loaded === "string")
@@ -1687,8 +1691,7 @@ async function loadMountedEmbedRuntimeModule(server, runtimeUrl) {
1687
1691
  if (loaded && typeof loaded.code === "string")
1688
1692
  return loaded.code;
1689
1693
  }
1690
- const result = await server.transformRequest(runtimeUrl);
1691
- return result?.code ?? null;
1694
+ return null;
1692
1695
  }
1693
1696
  function serveMountedEmbedRuntimeModule(server, req, res, base) {
1694
1697
  if (req.method !== "GET" && req.method !== "HEAD")
@@ -197,6 +197,7 @@ Each card is a `###` heading (optionally linked) followed by the description. Ca
197
197
  | `plug` | integrations, connected tools |
198
198
  | `network` | cross-app or distributed communication |
199
199
  | `terminal` | CLI, scripts, terminal-driven workflows |
200
+ | `calendar` | scheduling, dates, time-based features |
200
201
 
201
202
  The grid defaults to 3 columns. Set `columns` (1-4) on the opening tag to override it, e.g. `<Cards columns={2}>`. Below 640px the grid always collapses to a single column regardless of `columns`.
202
203
 
@@ -337,7 +338,25 @@ Define one action. The agent calls it as a tool, the UI calls it through a React
337
338
  </Comparison>
338
339
  ```
339
340
 
340
- Add more `###` sections to get 3 or 4 columns (max 4). The red/green label accent only applies to headings that exactly match `Before`, `Old`, `Without` (red) or `After`, `New`, `With` (green).
341
+ Add more `###` sections to get 3 or 4 columns (max 4). The red/green label accent applies automatically to headings that exactly match `Before`, `Old`, `Without` (red) or `After`, `New`, `With` (green).
342
+
343
+ For any other pairing, an explicit `:color:` prefix sets a column's accent directly, the same shortcode idiom `Cards` uses for `:icon:`:
344
+
345
+ ```mdx
346
+ <Comparison>
347
+
348
+ ### :blue: You ask
349
+
350
+ "Am I free Thursday afternoon?"
351
+
352
+ ### :green: What happens
353
+
354
+ The agent answers in chat. Nothing on screen changes.
355
+
356
+ </Comparison>
357
+ ```
358
+
359
+ `color` accepts `blue`, `green`, `red`, or `yellow`, matching `Badge`'s color naming. Each routes through a real theme token (the site's brand blue, or the same semantic red/yellow/green used elsewhere) rather than a raw color, so it stays correct in both themes. An explicit `color` always wins over the Before/After heuristic.
341
360
 
342
361
  ---
343
362
 
@@ -297,13 +297,24 @@ All endpoints support `?since=N` (ms timestamp) and `?limit=N` query params.
297
297
  When `POSTHOG_API_KEY` is set, every instrumented run is mirrored to PostHog's
298
298
  LLM analytics as a full trace tree:
299
299
 
300
- | Event | Emitted per | Carries |
301
- | ---------------- | ----------- | ------------------------------------------------------- |
302
- | `$ai_trace` | agent run | Latency, error state, token totals, cost |
303
- | `$ai_generation` | model call | Model, provider, tokens, cost, tool definitions offered |
304
- | `$ai_span` | tool call | Tool name, duration, error state |
305
-
306
- Nodes share the run id as `$ai_trace_id`, so PostHog renders one tree per run.
300
+ | Event | Emitted per | Carries |
301
+ | ---------------- | ---------------- | --------------------------------------------------------- |
302
+ | `$ai_trace` | agent run | Run name, totals, terminal state, run-level failure |
303
+ | `$ai_generation` | model round-trip | Prompt, answer, model, tokens, cost, latency, stop reason |
304
+ | `$ai_span` | tool call | Tool name, duration, failure class |
305
+
306
+ Nodes share the run id as `$ai_trace_id`, so PostHog renders one tree per run,
307
+ with each tool span under the generation that requested it. A run's tool
308
+ definitions are never sent: they are the same catalogue on every call, and a
309
+ call is identified by its name in `$ai_output_choices` and by its own span.
310
+
311
+ `$ai_is_error` means a different thing at each level, and never travels without
312
+ `$ai_error` and `$ai_error_type`. On a generation it is the model call itself
313
+ failing — a provider error, a dropped stream. On a span it is a tool that
314
+ crashed or returned an error. On the trace it is the run: a step budget, a
315
+ timeout, a no-progress cut-off, or a tool that stopped the run. A run that
316
+ failed after the model answered leaves its generations green, because they did
317
+ not fail.
307
318
  `$ai_session_id` is the conversation thread; the browser session is sent
308
319
  separately as `$session_id` so a trace links to its session replay.
309
320
 
@@ -337,9 +348,15 @@ to each other.
337
348
 
338
349
  Thumbs, category, and free-text feedback all emit `$ai_feedback`. PostHog's LLM
339
350
  analytics feedback view reads a `survey sent` event instead, so set
340
- `POSTHOG_AI_FEEDBACK_SURVEY_ID` (and `POSTHOG_AI_FEEDBACK_SURVEY_QUESTION_ID`
341
- for multi-question surveys) to populate it. Without a survey id no survey event
342
- is sent.
351
+ `POSTHOG_AI_FEEDBACK_SURVEY_ID` to the survey PostHog creates from a trace's
352
+ **Feedback** tab. That one id is the whole configuration; without it no survey
353
+ event is sent.
354
+
355
+ A thumbs vote answers the survey's first question as PostHog's choice index
356
+ (`1` up, `2` down) and the free text after a thumbs-down answers the follow-up
357
+ question, so the text never lands where the rating belongs. Both carry one
358
+ submission id per rated message, which is how PostHog joins them into a single
359
+ response, and a thumbs-down stays marked incomplete until its follow-up arrives.
343
360
 
344
361
  ## Export to external platforms {#export}
345
362
 
@@ -1,52 +1,89 @@
1
1
  ---
2
- title: "Calendar: Talking to the Agent"
2
+ title: "Calendar: Talk to the Agent"
3
3
  description: "What the agent sees when you're in Calendar, prompts organized by task, and when it reaches for the raw Google Calendar API instead of a canned action."
4
4
  ---
5
5
 
6
- # Talking to the Agent
6
+ # Talk to the Agent
7
7
 
8
8
  Everything in Calendar is available to the agent through the same actions the UI uses, so anything you can click, you can also ask for in plain English. This page covers how the agent sees your screen, useful prompts by task, and where it escalates beyond the built-in actions. See [Calendar](/docs/template-calendar) for the overview.
9
9
 
10
10
  ## How the agent sees your screen
11
11
 
12
- The agent always knows the current calendar view, the date range you're looking at, and the event you have selected. That's why "reschedule this" or "who's on this call" resolves correctly without you naming the event — the agent already knows which one "this" refers to.
12
+ The agent always knows the current calendar view, the date range you're looking at, and the event you have selected. That's why "reschedule this" or "who's on this call" resolves correctly without you naming the event. The agent already knows which one "this" refers to.
13
13
 
14
14
  ## Useful prompts by task
15
15
 
16
- **Schedule**
16
+ Every prompt below becomes a real action against your actual calendar, the
17
+ same one the UI reads and writes. Here's one worked example:
17
18
 
18
- - "Schedule a 30-minute call with Alex tomorrow at 2pm."
19
- - "Create a Zoom meeting with the design team Thursday at 10."
20
- - "Set me as out of office Friday."
19
+ <Comparison>
21
20
 
22
- **Find time**
21
+ ### :blue: You ask
23
22
 
24
- - "Find a 1-hour slot next week with Sam and Priya and book 'Planning' on it."
25
- - "Am I free Thursday afternoon?"
23
+ "Am I free Thursday afternoon?"
26
24
 
27
- **Manage events**
25
+ ### :green: What happens
28
26
 
29
- - "Move this event to Friday at 2pm." (with an event selected)
30
- - "Add Jordan to this meeting as an optional guest."
31
- - "Accept this invite." / "Decline with a note that I'm out that day."
32
- - "Delete this meeting, just for me — I'm not the organizer."
27
+ This is a read-only question. The agent checks your real calendar and
28
+ availability rules, then answers directly in chat, for example "You're open
29
+ from 1 to 4pm." Nothing on your screen changes, since nothing was created or
30
+ moved.
33
31
 
34
- **Booking links**
32
+ </Comparison>
35
33
 
36
- - "Create a 15-minute intro booking link with a name field."
37
- - "Add Brent as a required co-host on my 'Steve + Brent' link."
38
- - "Pause my '30 min demo' link."
39
- - "Show me this week's bookings."
34
+ <Cards columns={2}>
40
35
 
41
- **People and view**
36
+ ### Schedule
37
+
38
+ - **"Schedule a 30-minute call with Alex tomorrow at 2pm."** The event
39
+ appears on your calendar the moment it's created. No dialog needed.
40
+ - **"Create a Zoom meeting with the design team Thursday at 10."** Same as
41
+ above, and a Zoom link is attached automatically.
42
+ - **"Set me as out of office Friday."** An all-day out-of-office event shows
43
+ up on your calendar right away.
44
+
45
+ ### Find time
46
+
47
+ - **"Find a 1-hour slot next week with Sam and Priya and book 'Planning' on
48
+ it."** The agent checks everyone's real availability, then the new event
49
+ appears on your calendar live. No dialog needed.
50
+
51
+ ### Manage events
52
+
53
+ _With an event selected, "this" and "this meeting" resolve on their own._
54
+
55
+ - **"Move this event to Friday at 2pm."** The event moves on your calendar
56
+ grid live.
57
+ - **"Add Jordan to this meeting as an optional guest."** Jordan shows up in
58
+ the attendee list immediately.
59
+ - **"Accept this invite." / "Decline with a note that I'm out that day."**
60
+ Your RSVP updates on the event right away.
61
+ - **"Delete this meeting, just for me. I'm not the organizer."** The meeting
62
+ disappears from your calendar. Other attendees still see it on theirs.
63
+
64
+ ### Booking links
65
+
66
+ - **"Create a 15-minute intro booking link with a name field."** The new
67
+ meeting type appears in your Booking Links list live.
68
+ - **"Add Brent as a required co-host on my 'Steve + Brent' link."** Brent
69
+ shows up as a co-host immediately, and future slots only open when you're
70
+ both free.
71
+ - **"Pause my '30 min demo' link."** The link shows as paused right away and
72
+ stops accepting new bookings.
73
+ - **"Show me this week's bookings."** A read-only question. The agent
74
+ answers in chat with the list.
75
+
76
+ ### People and view
42
77
 
43
78
  - "Who is jane at acme.com?"
44
79
  - "Add Priya's calendar to my view."
45
80
  - "Switch to day view and jump to next Monday."
46
81
 
82
+ </Cards>
83
+
47
84
  ## Group and co-host booking links
48
85
 
49
- When a booking link has required co-hosts, the agent (and the UI) only offers a slot when the owner _and_ every co-host are free — it checks all of their calendars, not just the owner's. Once someone books, the agent invites every co-host to the created Google Calendar event alongside the visitor.
86
+ When a booking link has required co-hosts, the agent (and the UI) only offers a slot when the owner _and_ every co-host are free. It checks all of their calendars, not just the owner's. Once someone books, the agent invites every co-host to the created Google Calendar event alongside the visitor.
50
87
 
51
88
  <Diagram id="doc-block-calendar5" title="How a scheduling request flows" summary="Whether you click in the calendar or ask the agent, the same actions read live from Google Calendar and write back to the same view. Zoom and people search plug into the same flow.">
52
89
 
@@ -118,16 +155,16 @@ When a booking link has required co-hosts, the agent (and the UI) only offers a
118
155
 
119
156
  ## When the agent goes straight to the Google Calendar API
120
157
 
121
- The built-in actions (`list-events`, `search-events`, `find-a-time`, and so on) cover everyday scheduling. For requests that need an exact Google Calendar endpoint, filter, or pagination mode the actions don't expose — for example scanning every event across six months for a report, or an unusual calendar-list query — the agent can call the real Google Calendar API directly instead of forcing the answer through a narrower action. For large scans, it stages the results into a scratch dataset and works from a compact summary rather than dumping thousands of events into the conversation. You'll typically notice this as "let me pull that directly from Google Calendar" on requests that go beyond a normal schedule question.
158
+ The built-in actions (`list-events`, `search-events`, `find-a-time`, and so on) cover everyday scheduling. Some requests need an exact Google Calendar endpoint, filter, or pagination mode that the actions don't expose, like scanning every event across six months for a report, or an unusual calendar-list query. For those, the agent can call the real Google Calendar API directly instead of forcing the answer through a narrower action. For large scans, it stages the results into a scratch dataset and works from a compact summary rather than dumping thousands of events into the conversation. You'll typically notice this as "let me pull that directly from Google Calendar" on requests that go beyond a normal schedule question.
122
159
 
123
160
  ## Extensions
124
161
 
125
- Calendar supports Extensions — small sandboxed widgets you or the agent can build and drop into the app. The event detail panel exposes a slot at the bottom where an extension can render, with the event's id, title, times, timezones, location, attendees, and account already available to it. See [Extensions](/docs/extensions) for how to build one.
162
+ Calendar supports Extensions: small sandboxed widgets you or the agent can build and drop into the app. The event detail panel exposes a slot at the bottom where an extension can render, with the event's id, title, times, timezones, location, attendees, and account already available to it. See [Extensions](/docs/extensions) for how to build one.
126
163
 
127
164
  ## What's next
128
165
 
129
166
  - [**Calendar**](/docs/template-calendar) — the overview and getting-started steps
130
- - [**Events, Availability & Finding Time**](/docs/template-calendar-scheduling) — views, availability, and finding time
131
- - [**Booking Links**](/docs/template-calendar-booking-links) — the public scheduling pages these prompts manage
167
+ - [**Features**](/docs/template-calendar-features) — views, availability, finding time, and booking links, with setup steps for each
168
+ - [**Calendar in a Multi-App Workspace**](/docs/template-calendar-integrations) — integration strategies and connecting to Dispatch
132
169
  - [**Developer Guide**](/docs/template-calendar-developers) — every action, the data model, and how to extend the app
133
170
  - [**Extensions**](/docs/extensions) — building a widget for the event-detail slot
@@ -9,15 +9,91 @@ This page is for anyone customizing the Calendar template or extending it: the q
9
9
 
10
10
  ## Quick start
11
11
 
12
+ This walks through running Calendar from nothing installed to a connected
13
+ Google Calendar. The commands are identical whether you're in Terminal on
14
+ macOS or PowerShell/Command Prompt on Windows, they're plain Node.js tooling.
15
+
16
+ <Steps>
17
+
18
+ ### Create the app
19
+
12
20
  ```bash
13
21
  npx @agent-native/core@latest create my-calendar --standalone --template calendar
22
+ cd my-calendar
23
+ pnpm install
24
+ ```
25
+
26
+ This needs [Node.js](https://nodejs.org) 22 or later and
27
+ [pnpm](https://pnpm.io) installed first. See
28
+ [Getting started](/docs/getting-started) if you haven't installed either
29
+ yet, or don't have an LLM connection (Builder.io, an Anthropic/OpenAI key,
30
+ or local Ollama) set up.
31
+
32
+ ### Start the dev server
33
+
34
+ ```bash
35
+ pnpm dev
36
+ ```
37
+
38
+ The app opens at `http://localhost:8082`. If it doesn't open automatically,
39
+ visit that URL in your browser. Calendar works fully with Google
40
+ disconnected. You'll only hit an error if you try an action that needs it
41
+ before connecting.
42
+
43
+ ### Connect Google Calendar
44
+
45
+ In the app, open **Settings**, and on the **General** tab find the **Google
46
+ Calendar** card, then click **Connect Google Calendar**. This opens a setup
47
+ wizard:
48
+
49
+ 1. **Create a Google Cloud project**, if you don't already have one to use:
50
+ in [Google Cloud Console](https://console.cloud.google.com/), click the
51
+ project dropdown near the top of the page, then **New Project**. Give it
52
+ any name, click **Create**, and wait for the notification that it's
53
+ ready. Make sure it's selected in that same dropdown before continuing.
54
+ 2. **Enable the API**: with your project selected, go to **APIs & Services
55
+ → Library**, search for "Google Calendar API," open it, and click
56
+ **Enable**.
57
+ 3. **Configure the consent screen**: under **APIs & Services → OAuth
58
+ consent screen**, set one up ("External" is fine for development).
59
+ 4. **Create credentials**: under **APIs & Services → Credentials**, create
60
+ an OAuth client ID of type "Web application," and add
61
+ `http://localhost:8082/_agent-native/google/callback` as an authorized
62
+ redirect URI (8082 is Calendar's default dev port).
63
+ 5. **Add the credentials**: download the credentials JSON and upload it in
64
+ the wizard, or toggle "Paste manually" and enter the Client ID and Client
65
+ Secret directly.
66
+
67
+ Calendar saves these to its own secret store, not a `.env` file, so this
68
+ step is identical on Windows and macOS. Tokens refresh automatically once
69
+ connected.
70
+
71
+ ### Connect Zoom (optional)
72
+
73
+ Unlike Google, Zoom has no Settings form. Create a `.env` file in the
74
+ project root (or add to the existing one) with:
75
+
76
+ ```text
77
+ ZOOM_CLIENT_ID=your-client-id
78
+ ZOOM_CLIENT_SECRET=your-client-secret
14
79
  ```
15
80
 
16
- See [Getting started](/docs/getting-started) for installing dependencies and running the dev server.
81
+ Get a Client ID and Secret from a [Zoom OAuth app](https://marketplace.zoom.us/)
82
+ (Zoom Marketplace → Develop → Build App → OAuth), with
83
+ `/_agent-native/zoom/callback` as the redirect URI, then restart `pnpm dev`
84
+ so it picks up the new values. Without Zoom connected, booking links can
85
+ still offer Google Meet, just not Zoom.
86
+
87
+ </Steps>
17
88
 
18
- To connect Google Calendar in dev, open the Settings view, paste a `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` from [Google Cloud Console](https://console.cloud.google.com/), and click "Connect Google Calendar." The OAuth redirect URI is `http://localhost:8082/_agent-native/google/callback` in dev (8082 is the default Calendar dev port). Tokens are stored in the framework `oauth_tokens` table and refresh automatically.
89
+ <Callout tone="warning">
19
90
 
20
- To enable Zoom conferencing on booking links, set `ZOOM_CLIENT_ID` and `ZOOM_CLIENT_SECRET` from a [Zoom OAuth app](https://marketplace.zoom.us/). Its redirect URI is `/_agent-native/zoom/callback`. Without these set, booking links can still offer Google Meet — Zoom just won't appear as a conferencing option.
91
+ On Windows, some editors add a hidden `.txt` extension when you save a file
92
+ named `.env`, leaving a `.env.txt` that Calendar never reads. In File
93
+ Explorer, turn on "File name extensions" (View tab) and confirm the saved
94
+ file is named exactly `.env`, with nothing after it.
95
+
96
+ </Callout>
21
97
 
22
98
  <Callout tone="info">
23
99
 
@@ -29,57 +105,101 @@ To enable Zoom conferencing on booking links, set `ZOOM_CLIENT_ID` and `ZOOM_CLI
29
105
 
30
106
  Every operation is a TypeScript file in `templates/calendar/actions/`, auto-mounted at `POST /_agent-native/actions/:name`.
31
107
 
32
- **Events & guests** — event data lives in Google Calendar, never SQL:
108
+ ### Events & guests
109
+
110
+ Event data lives in Google Calendar, never SQL.
33
111
 
34
- - `list-events`, `search-events`, `get-event` — read events in a range or by id
35
- - `create-event`, `update-event`, `delete-event` — write events, including recurrence (RRULE), working locations, and status types (`outOfOffice`, `focusTime`)
36
- - `manage-event-draft` — stage an unsent invite for user review before anything is written or guests are notified
37
- - `rsvp-event` — accept/decline/tentative on an invitation
38
- - `get-attendee-timezones`, `set-attendee-timezone` — per-guest timezone overrides for showing local times
112
+ | Action | What it does |
113
+ | --- | --- |
114
+ | `list-events` | List events in a date range |
115
+ | `search-events` | Search events by query |
116
+ | `get-event` | Read one event by id |
117
+ | `create-event` | Create an event, including recurrence (RRULE), working locations, and status types (`outOfOffice`, `focusTime`) |
118
+ | `update-event` | Update an event, including recurrence, working locations, and status types |
119
+ | `delete-event` | Delete an event |
120
+ | `manage-event-draft` | Stage an unsent invite for user review before anything is written or guests are notified |
121
+ | `rsvp-event` | Accept, decline, or mark tentative on an invitation |
122
+ | `get-attendee-timezones` | Read per-guest timezone overrides |
123
+ | `set-attendee-timezone` | Set a per-guest timezone override so their local time keeps showing correctly |
39
124
 
40
- **Availability & finding time:**
125
+ ### Availability & finding time
41
126
 
42
- - `get-availability`, `update-availability` — the weekly schedule, timezone, buffer, notice, and advance-window settings
43
- - `check-availability` — open slots for one date
44
- - `find-a-time` — shared free-slot search across an organizer and named attendees, combining Google free/busy with local conflicts
127
+ | Action | What it does |
128
+ | --- | --- |
129
+ | `get-availability` | Read the weekly schedule, timezone, buffer, notice, and advance-window settings |
130
+ | `update-availability` | Update the weekly schedule, timezone, buffer, notice, and advance-window settings |
131
+ | `check-availability` | Find open slots for one date |
132
+ | `find-a-time` | Shared free-slot search across an organizer and named attendees, combining Google free/busy with local conflicts |
45
133
 
46
- **Booking links & bookings:**
134
+ ### Booking links & bookings
47
135
 
48
- - `create-booking-link`, `update-booking-link`, `duplicate-booking-link` — link lifecycle, including co-hosts (`hosts`)
49
- - `list-booking-links`, `list-bookings` — accessible links and their confirmed appointments
136
+ | Action | What it does |
137
+ | --- | --- |
138
+ | `create-booking-link` | Create a booking link |
139
+ | `update-booking-link` | Update a booking link |
140
+ | `duplicate-booking-link` | Duplicate a booking link, including co-hosts (`hosts`) |
141
+ | `list-booking-links` | List accessible booking links |
142
+ | `list-bookings` | List confirmed appointments |
50
143
 
51
- **External calendars (read-only ICS feeds):**
144
+ ### External calendars
52
145
 
53
- - `add-external-calendar`, `list-external-calendars`, `remove-external-calendar`, `update-external-calendars`
146
+ Read-only ICS feeds.
54
147
 
55
- **People & overlays:**
148
+ | Action | What it does |
149
+ | --- | --- |
150
+ | `add-external-calendar` | Subscribe to a read-only ICS/webcal feed |
151
+ | `list-external-calendars` | List subscribed feeds |
152
+ | `remove-external-calendar` | Remove a subscribed feed |
153
+ | `update-external-calendars` | Update subscribed feeds |
56
154
 
57
- - `search-people` — Google Contacts + Workspace directory search
58
- - `get-overlay-people`, `update-overlay-people` — other people's calendars pinned into your view
155
+ ### People & overlays
59
156
 
60
- **Zoom:**
157
+ | Action | What it does |
158
+ | --- | --- |
159
+ | `search-people` | Search Google Contacts and the Workspace directory |
160
+ | `get-overlay-people` | Read the other people whose calendars are pinned into your view |
161
+ | `update-overlay-people` | Update the other people whose calendars are pinned into your view |
61
162
 
62
- - `get-zoom-status` — connection status for the current user; connect/disconnect happen through `/_agent-native/zoom/*` routes, not an action, since OAuth needs the signed-in browser session
163
+ ### Zoom
63
164
 
64
- **Google Calendar connection & sync:**
165
+ | Action | What it does |
166
+ | --- | --- |
167
+ | `get-zoom-status` | Read the current user's Zoom connection status. Connect and disconnect happen through `/_agent-native/zoom/*` routes, not an action, since OAuth needs the signed-in browser session |
65
168
 
66
- - `connect-google-calendar` — returns the OAuth connect URL for the signed-in browser session
67
- - `sync-google-calendar` — forces a resync
169
+ ### Google Calendar connection & sync
68
170
 
69
- **Settings & preferences:**
171
+ | Action | What it does |
172
+ | --- | --- |
173
+ | `connect-google-calendar` | Return the OAuth connect URL for the signed-in browser session |
174
+ | `sync-google-calendar` | Force a resync |
70
175
 
71
- - `get-settings`, `update-settings` — user-level settings
72
- - `update-calendar-visual-preferences` — local-only display prefs (event colors, hidden weekends); never touches Google Calendar
176
+ ### Settings & preferences
73
177
 
74
- **Navigation & UI state:**
178
+ | Action | What it does |
179
+ | --- | --- |
180
+ | `get-settings` | Read user-level settings |
181
+ | `update-settings` | Update user-level settings |
182
+ | `update-calendar-visual-preferences` | Update local-only display preferences (event colors, hidden weekends). Never touches Google Calendar |
75
183
 
76
- - `navigate` — moves the UI to a view, date, or event
77
- - `view-screen` — reads the current view back for the agent
184
+ ### Navigation & UI state
78
185
 
79
- **Provider API escalation** — the raw Google Calendar (and connected CRM) API, for when the actions above are too narrow:
186
+ | Action | What it does |
187
+ | --- | --- |
188
+ | `navigate` | Move the UI to a view, date, or event |
189
+ | `view-screen` | Read the current view back for the agent |
80
190
 
81
- - `provider-api-catalog`, `provider-api-docs`, `provider-api-request`
82
- - `list-staged-datasets`, `query-staged-dataset`, `delete-staged-dataset` — for staging and querying large scans instead of returning raw results
191
+ ### Provider API escalation
192
+
193
+ The raw Google Calendar (and connected CRM) API, for when the actions above are too narrow.
194
+
195
+ | Action | What it does |
196
+ | --- | --- |
197
+ | `provider-api-catalog` | List the raw provider APIs available to call directly |
198
+ | `provider-api-docs` | Read documentation for a raw provider API endpoint |
199
+ | `provider-api-request` | Call the raw Google Calendar (or connected CRM) API directly |
200
+ | `list-staged-datasets` | List large scans staged for querying instead of returning raw results |
201
+ | `query-staged-dataset` | Query a staged dataset |
202
+ | `delete-staged-dataset` | Delete a staged dataset |
83
203
 
84
204
  ## Data model
85
205
 
@@ -246,7 +366,7 @@ Availability rules live in per-user settings under the `calendar-availability` k
246
366
 
247
367
  ## An action walked through
248
368
 
249
- `create-booking-link` shows the two guardrails every booking-link write needs: a validated slug and a check against the other table that can claim one.
369
+ `create-booking-link` shows patterns worth reusing in any action: validating input before it touches the database, enforcing a rule across two tables, safely resolving the signed-in user, and normalizing a flexible input shape once instead of in every reader.
250
370
 
251
371
  <AnnotatedCode
252
372
  id="doc-block-calendar8"
@@ -258,7 +378,7 @@ Availability rules live in per-user settings under the `calendar-availability` k
258
378
  }
259
379
  annotations={[
260
380
  {
261
- lines: "38-42",
381
+ lines: "37-41",
262
382
  label: "Slug format is validated up front",
263
383
  note: "The regex enforces lowercase, hyphen-separated URL slugs before anything touches the database, so a malformed slug never reaches the uniqueness check below.",
264
384
  },
@@ -272,6 +392,11 @@ Availability rules live in per-user settings under the `calendar-availability` k
272
392
  label: "A slug must be free in two tables",
273
393
  note: "A new slug has to be unused in both `booking_links` and `booking_slug_redirects` — otherwise a fresh link could silently claim a URL an older, renamed link still forwards visitors from.",
274
394
  },
395
+ {
396
+ lines: "94-98",
397
+ label: "The signed-in user is resolved once, and narrowed",
398
+ note: "`getRequestUserEmail()` returns `string | undefined`, since there's technically no signed-in user for an unauthenticated call. Wrapping it in an immediately-invoked function lets the action throw right there if that ever happens, so `ownerEmail` is a plain `string` for the rest of the function instead of something every later line would have to re-check.",
399
+ },
275
400
  {
276
401
  lines: "110",
277
402
  label: "Co-host input is normalized once",
@@ -359,8 +484,8 @@ If you add a feature, remember to update all four areas: UI, action, skill or AG
359
484
  ## What's next
360
485
 
361
486
  - [**Calendar**](/docs/template-calendar) — the overview and getting-started steps
362
- - [**Events, Availability & Finding Time**](/docs/template-calendar-scheduling) — the user-facing behavior these actions power
363
- - [**Booking Links**](/docs/template-calendar-booking-links) — the user-facing behavior this data model powers
364
- - [**Talking to the Agent**](/docs/template-calendar-agent) — how the agent chooses between these actions and the raw Google Calendar API
487
+ - [**Features**](/docs/template-calendar-features) — the user-facing behavior these actions and this data model power
488
+ - [**Talk to the Agent**](/docs/template-calendar-agent) — how the agent chooses between these actions and the raw Google Calendar API
489
+ - [**Calendar in a Multi-App Workspace**](/docs/template-calendar-integrations) — how these actions get exposed to other apps and Dispatch
365
490
  - [**Actions**](/docs/actions-overview) — the action system this template is built on
366
491
  - [**Sharing**](/docs/sharing) — the share-grant model `booking_link_shares` uses