@alexkroman1/aai-cli 6.11.0 → 8.0.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.
Files changed (234) hide show
  1. package/README.md +1 -0
  2. package/dist/{_agent-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
  3. package/dist/{_bundler-C2BDwU7r.mjs → _bundler-CDuYl5Gb.mjs} +2 -2
  4. package/dist/_dev-env.d.ts +22 -2
  5. package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-Me06wgeF.mjs} +38 -47
  6. package/dist/_dev-server.d.ts +0 -17
  7. package/dist/{_init-C57bWAQ8.mjs → _init-BhEfQ6Yi.mjs} +3 -2
  8. package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
  9. package/dist/_preflight.d.ts +1 -1
  10. package/dist/_resource-commands.d.ts +23 -0
  11. package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-z-USxPtL.mjs} +16 -12
  12. package/dist/_slug-api.d.ts +5 -2
  13. package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
  14. package/dist/{_templates-Dxb_P2Wz.mjs → _templates-4WcKOjS5.mjs} +5 -6
  15. package/dist/_templates.d.ts +3 -4
  16. package/dist/_workflow-scan.d.ts +108 -0
  17. package/dist/{build-Dfza2pRI.mjs → build-BBxf8vom.mjs} +6 -3
  18. package/dist/cli.mjs +346 -349
  19. package/dist/{client-bundler-BEIqgOtd.mjs → client-bundler-CWnG42cU.mjs} +2 -1
  20. package/dist/client-bundler.mjs +1 -1
  21. package/dist/{delete-vbpjGzqs.mjs → delete-Dl_LSfvI.mjs} +3 -3
  22. package/dist/{deploy-B8lubiRT.mjs → deploy-DRC8jWr8.mjs} +5 -5
  23. package/dist/{dev-Uq5ujP8a.mjs → dev-BvcctCn2.mjs} +1 -1
  24. package/dist/eval-DfQ8cm5p.mjs +56 -0
  25. package/dist/eval.d.ts +43 -0
  26. package/dist/{init-l6xfU4xX.mjs → init-CUo87gcI.mjs} +3 -3
  27. package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
  28. package/dist/{logs-BWVGSjLR.mjs → logs-CQ2T46dr.mjs} +2 -2
  29. package/dist/project-config.mjs +1 -1
  30. package/dist/scaffold/CLAUDE.md +442 -80
  31. package/dist/scaffold/package.json +6 -3
  32. package/dist/scaffold/server.mjs +11 -4
  33. package/dist/scaffold/tsconfig.json +0 -1
  34. package/dist/{secret-BuMuFR4B.mjs → secret-Ct34kdgU.mjs} +2 -2
  35. package/dist/{storage-DzRZ-eCw.mjs → storage-i_79ZwAR.mjs} +2 -2
  36. package/dist/{studio-BCNUpDgP.mjs → studio-CzTzPqe1.mjs} +4 -4
  37. package/dist/templates/briefing-desk/agent.test.ts +350 -0
  38. package/dist/templates/briefing-desk/agent.ts +30 -0
  39. package/dist/templates/briefing-desk/shared.ts +200 -0
  40. package/dist/templates/briefing-desk/system-prompt.md +39 -0
  41. package/dist/templates/briefing-desk/tools/briefing_so_far.ts +35 -0
  42. package/dist/templates/briefing-desk/tools/research_topic.ts +88 -0
  43. package/dist/templates/briefing-desk/tools/verify_claim.ts +77 -0
  44. package/dist/templates/call-audit/agent.eval.test.ts +474 -0
  45. package/dist/templates/call-audit/agent.test.ts +57 -204
  46. package/dist/templates/call-audit/agent.ts +32 -19
  47. package/dist/templates/call-audit/client.tsx +17 -54
  48. package/dist/templates/call-audit/workflows/audit.ts +11 -18
  49. package/dist/templates/call-audit/workflows/ingest.ts +106 -114
  50. package/dist/templates/call-audit/workflows/media.ts +2 -12
  51. package/dist/templates/call-audit/workflows/summarize.ts +47 -52
  52. package/dist/templates/call-audit/workflows/sync-api.ts +7 -7
  53. package/dist/templates/code-interpreter/agent.eval.test.ts +165 -0
  54. package/dist/templates/dispatch-center/agent.eval.test.ts +349 -0
  55. package/dist/templates/dispatch-center/agent.test.ts +149 -25
  56. package/dist/templates/dispatch-center/client.tsx +239 -129
  57. package/dist/templates/dispatch-center/shared.ts +99 -1
  58. package/dist/templates/dispatch-center/system-prompt.md +3 -1
  59. package/dist/templates/dispatch-center/tools/incident_add_note.ts +16 -13
  60. package/dist/templates/dispatch-center/tools/incident_create.ts +19 -1
  61. package/dist/templates/dispatch-center/tools/incident_escalate.ts +68 -59
  62. package/dist/templates/dispatch-center/tools/incident_triage.ts +57 -43
  63. package/dist/templates/dispatch-center/tools/incident_update_status.ts +49 -40
  64. package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +8 -2
  65. package/dist/templates/dispatch-center/tools/resources_dispatch.ts +69 -52
  66. package/dist/templates/dispatch-center/tools/resources_update_status.ts +45 -41
  67. package/dist/templates/embedded-assets/agent.eval.test.ts +138 -0
  68. package/dist/templates/embedded-assets/agent.test.ts +17 -14
  69. package/dist/templates/health-assistant/agent.eval.test.ts +167 -0
  70. package/dist/templates/health-assistant/agent.test.ts +22 -4
  71. package/dist/templates/infocom-adventure/agent.eval.test.ts +158 -0
  72. package/dist/templates/infocom-adventure/agent.test.ts +53 -24
  73. package/dist/templates/infocom-adventure/agent.ts +21 -0
  74. package/dist/templates/infocom-adventure/client.tsx +246 -164
  75. package/dist/templates/infocom-adventure/shared.ts +20 -9
  76. package/dist/templates/infocom-adventure/system-prompt.md +1 -1
  77. package/dist/templates/infocom-adventure/tools/game_state_move.ts +3 -2
  78. package/dist/templates/link-digest/agent.eval.test.ts +195 -0
  79. package/dist/templates/link-digest/agent.test.ts +24 -19
  80. package/dist/templates/link-digest/client.tsx +47 -61
  81. package/dist/templates/link-digest/workflows/digest.ts +19 -31
  82. package/dist/templates/math-buddy/agent.eval.test.ts +146 -0
  83. package/dist/templates/night-owl/agent.eval.test.ts +272 -0
  84. package/dist/templates/night-owl/agent.test.ts +70 -19
  85. package/dist/templates/night-owl/agent.ts +5 -0
  86. package/dist/templates/night-owl/client.tsx +56 -56
  87. package/dist/templates/night-owl/shared.ts +24 -0
  88. package/dist/templates/night-owl/tools/recommend.ts +18 -6
  89. package/dist/templates/personal-finance/agent.eval.test.ts +168 -0
  90. package/dist/templates/personal-finance/system-prompt.md +4 -0
  91. package/dist/templates/pipeline-simple/agent.eval.test.ts +78 -0
  92. package/dist/templates/pipeline-simple/agent.ts +2 -2
  93. package/dist/templates/pizza-ordering/agent.eval.test.ts +171 -0
  94. package/dist/templates/pizza-ordering/agent.test.ts +42 -27
  95. package/dist/templates/pizza-ordering/client.tsx +9 -26
  96. package/dist/templates/pizza-ordering/shared.ts +20 -1
  97. package/dist/templates/pizza-ordering/system-prompt.md +6 -0
  98. package/dist/templates/plan-and-execute/agent.eval.test.ts +209 -0
  99. package/dist/templates/plan-and-execute/agent.test.ts +99 -58
  100. package/dist/templates/plan-and-execute/agent.ts +1 -1
  101. package/dist/templates/plan-and-execute/client.tsx +12 -15
  102. package/dist/templates/plan-and-execute/shared.ts +71 -2
  103. package/dist/templates/plan-and-execute/tools/plan_status.ts +18 -6
  104. package/dist/templates/plan-and-execute/tools/revise_plan.ts +18 -10
  105. package/dist/templates/plan-and-execute/tools/start_plan.ts +13 -2
  106. package/dist/templates/plan-and-execute/tools/work_next_step.ts +70 -22
  107. package/dist/templates/podcast-digest/agent.eval.test.ts +411 -0
  108. package/dist/templates/podcast-digest/agent.test.ts +717 -0
  109. package/dist/templates/podcast-digest/agent.ts +139 -0
  110. package/dist/templates/podcast-digest/client.tsx +154 -0
  111. package/dist/templates/podcast-digest/workflows/digest.ts +444 -0
  112. package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
  113. package/dist/templates/podcast-digest/workflows/slack.ts +88 -0
  114. package/dist/templates/recap-workflow/agent.eval.test.ts +366 -0
  115. package/dist/templates/recap-workflow/agent.test.ts +97 -94
  116. package/dist/templates/recap-workflow/tools/recap_progress.ts +9 -12
  117. package/dist/templates/recap-workflow/tools/recap_status.ts +8 -9
  118. package/dist/templates/recap-workflow/tools/request_recap.ts +2 -1
  119. package/dist/templates/recap-workflow/workflows/recap.ts +36 -40
  120. package/dist/templates/redline/agent.eval.test.ts +209 -0
  121. package/dist/templates/redline/agent.test.ts +17 -15
  122. package/dist/templates/redline/client.tsx +12 -12
  123. package/dist/templates/redline/workflows/redline.ts +19 -31
  124. package/dist/templates/research-workflow/agent.eval.test.ts +329 -0
  125. package/dist/templates/research-workflow/agent.test.ts +60 -59
  126. package/dist/templates/research-workflow/tools/research_progress.ts +9 -12
  127. package/dist/templates/research-workflow/tools/research_status.ts +9 -11
  128. package/dist/templates/research-workflow/workflows/research.ts +44 -61
  129. package/dist/templates/retail/address.ts +87 -1
  130. package/dist/templates/retail/agent.eval.test.ts +314 -0
  131. package/dist/templates/retail/agent.test.ts +195 -112
  132. package/dist/templates/retail/agent.ts +35 -1
  133. package/dist/templates/retail/cancel.ts +116 -0
  134. package/dist/templates/retail/client.tsx +265 -117
  135. package/dist/templates/retail/payment.ts +107 -0
  136. package/dist/templates/retail/pending.test.ts +196 -0
  137. package/dist/templates/retail/pending.ts +137 -0
  138. package/dist/templates/retail/registry.test.ts +150 -7
  139. package/dist/templates/retail/returns.ts +102 -0
  140. package/dist/templates/retail/shared.test.ts +42 -0
  141. package/dist/templates/retail/shared.ts +35 -0
  142. package/dist/templates/retail/store.test.ts +84 -15
  143. package/dist/templates/retail/store.ts +231 -48
  144. package/dist/templates/retail/swap.test.ts +4 -4
  145. package/dist/templates/retail/swap.ts +248 -15
  146. package/dist/templates/retail/system-prompt.md +69 -28
  147. package/dist/templates/retail/tools/cancel_change.ts +33 -0
  148. package/dist/templates/retail/tools/cancel_pending_order.ts +16 -60
  149. package/dist/templates/retail/tools/confirm_change.ts +42 -0
  150. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -67
  151. package/dist/templates/retail/tools/find_user_id_by_email.ts +8 -5
  152. package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +4 -5
  153. package/dist/templates/retail/tools/get_item_details.ts +3 -4
  154. package/dist/templates/retail/tools/get_order_details.ts +3 -4
  155. package/dist/templates/retail/tools/get_product_details.ts +3 -4
  156. package/dist/templates/retail/tools/get_user_details.ts +2 -2
  157. package/dist/templates/retail/tools/list_all_product_types.ts +5 -5
  158. package/dist/templates/retail/tools/modify_pending_order_address.ts +13 -25
  159. package/dist/templates/retail/tools/modify_pending_order_items.ts +23 -62
  160. package/dist/templates/retail/tools/modify_pending_order_payment.ts +16 -78
  161. package/dist/templates/retail/tools/modify_user_address.ts +13 -16
  162. package/dist/templates/retail/tools/return_delivered_order_items.ts +17 -72
  163. package/dist/templates/retail/tools/transfer_to_human_agents.ts +12 -6
  164. package/dist/templates/simple/agent.eval.test.ts +54 -0
  165. package/dist/templates/solo-rpg/agent.eval.test.ts +320 -0
  166. package/dist/templates/solo-rpg/agent.test.ts +227 -48
  167. package/dist/templates/solo-rpg/client.tsx +38 -37
  168. package/dist/templates/solo-rpg/shared.ts +145 -19
  169. package/dist/templates/solo-rpg/system-prompt.md +3 -2
  170. package/dist/templates/solo-rpg/tools/action_roll.ts +82 -63
  171. package/dist/templates/solo-rpg/tools/burn_momentum.ts +85 -50
  172. package/dist/templates/solo-rpg/tools/check_state.ts +24 -7
  173. package/dist/templates/solo-rpg/tools/load_game.ts +13 -1
  174. package/dist/templates/solo-rpg/tools/save_game.ts +16 -7
  175. package/dist/templates/solo-rpg/tools/setup_character.ts +22 -2
  176. package/dist/templates/solo-rpg/tools/update_state.ts +113 -100
  177. package/dist/templates/spoken-summary/agent.eval.test.ts +342 -0
  178. package/dist/templates/spoken-summary/agent.test.ts +68 -97
  179. package/dist/templates/spoken-summary/agent.ts +30 -17
  180. package/dist/templates/spoken-summary/client.tsx +10 -50
  181. package/dist/templates/spoken-summary/workflows/summarize.ts +17 -22
  182. package/dist/templates/spoken-summary/workflows/transcribe.ts +19 -26
  183. package/dist/templates/support-line/agent.eval.test.ts +273 -0
  184. package/dist/templates/support-line/agent.test.ts +11 -16
  185. package/dist/templates/support-line/agent.ts +1 -1
  186. package/dist/templates/support-line/client.tsx +9 -9
  187. package/dist/templates/support-line/nodes.ts +100 -0
  188. package/dist/templates/support-line/procedure.ts +407 -0
  189. package/dist/templates/support-line/system-prompt.md +2 -1
  190. package/dist/templates/support-line/tools/answer_question.ts +66 -4
  191. package/dist/templates/transcription-workflow/agent.eval.test.ts +234 -0
  192. package/dist/templates/transcription-workflow/agent.test.ts +94 -194
  193. package/dist/templates/transcription-workflow/agent.ts +1 -1
  194. package/dist/templates/transcription-workflow/client.tsx +17 -42
  195. package/dist/templates/transcription-workflow/workflows/batch.ts +19 -22
  196. package/dist/templates/transcription-workflow/workflows/normalize.ts +90 -186
  197. package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
  198. package/dist/templates/transcription-workflow/workflows/stream.ts +4 -5
  199. package/dist/templates/transcription-workflow/workflows/sync-api.ts +6 -6
  200. package/dist/templates/transcription-workflow/workflows/transcribe.ts +32 -26
  201. package/dist/templates/transcription-workflow/workflows/wav.ts +0 -31
  202. package/dist/templates/travel-concierge/agent.eval.test.ts +323 -0
  203. package/dist/templates/travel-concierge/agent.test.ts +174 -43
  204. package/dist/templates/travel-concierge/agent.ts +18 -9
  205. package/dist/templates/travel-concierge/client.tsx +11 -23
  206. package/dist/templates/travel-concierge/routing.ts +34 -15
  207. package/dist/templates/travel-concierge/shared.ts +180 -12
  208. package/dist/templates/travel-concierge/system-prompt.md +7 -1
  209. package/dist/templates/travel-concierge/tools/book_car_rental.ts +8 -5
  210. package/dist/templates/travel-concierge/tools/book_excursion.ts +8 -5
  211. package/dist/templates/travel-concierge/tools/book_hotel.ts +8 -5
  212. package/dist/templates/travel-concierge/tools/cancel_ticket.ts +8 -5
  213. package/dist/templates/travel-concierge/tools/search_car_rentals.ts +9 -5
  214. package/dist/templates/travel-concierge/tools/search_excursions.ts +9 -5
  215. package/dist/templates/travel-concierge/tools/search_flights.ts +9 -6
  216. package/dist/templates/travel-concierge/tools/search_hotels.ts +9 -5
  217. package/dist/templates/travel-concierge/tools/update_ticket.ts +8 -5
  218. package/dist/templates/web-researcher/agent.eval.test.ts +121 -0
  219. package/dist/templates/web-researcher/system-prompt.md +15 -4
  220. package/dist/{test-9kPKJI-w.mjs → test-BvyT6sAc.mjs} +24 -10
  221. package/dist/test.d.ts +29 -5
  222. package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-CCVEDjm1.mjs} +241 -3
  223. package/dist/worker-bundler.mjs +1 -1
  224. package/dist/{workflow-CFpxOFfQ.mjs → workflow-BflATMmU.mjs} +47 -12
  225. package/dist/workflow-bundler.d.ts +6 -1
  226. package/dist/workflow.d.ts +1 -1
  227. package/package.json +5 -4
  228. package/dist/eject-31gjtaHF.mjs +0 -116
  229. package/dist/eject.d.ts +0 -60
  230. package/dist/templates/call-audit/workflows/temp-media.ts +0 -138
  231. package/dist/templates/infocom-adventure/tools/game_state_history.ts +0 -14
  232. package/dist/templates/support-line/graph.ts +0 -224
  233. package/dist/{_config-D_s09e7g.mjs → _config-CmJOFsAP.mjs} +1 -1
  234. /package/dist/templates/plan-and-execute/{graph.ts → procedure.ts} +0 -0
@@ -0,0 +1,139 @@
1
+ // Copyright 2026 the AAI authors. MIT license.
2
+ /**
3
+ * A SCHEDULED workflow app — a run that repeats on an interval and outlives
4
+ * everything, including the tab that started it.
5
+ *
6
+ * Its front door is a form, not a microphone: no session, no WebSocket, no
7
+ * voice pipeline. `client.tsx` starts a run over the workflow HTTP API and
8
+ * watches it, and `workflows/digest.ts` does the work.
9
+ *
10
+ * ## What makes this a different KIND of workflow app from `link-digest`
11
+ *
12
+ * `link-digest` is the smallest one: one URL in, one digest out, run over. This
13
+ * is the same front door around a run that **sleeps for days and wakes up
14
+ * again**. Three consequences worth reading before copying it:
15
+ *
16
+ * - **The run is the schedule.** There is no cron anywhere in this template.
17
+ * `sleep()` inside a durable body IS the scheduler — the run suspends,
18
+ * nothing is resident, and the platform brings it back. That is the cheapest
19
+ * recurring job you can write here and it needs no infrastructure.
20
+ * - **It is asked when to stop.** `daysToRun` is an input rather than a
21
+ * constant because a run with no end is a resource nobody can see. It posts
22
+ * what it owes and finishes.
23
+ * - **Storage stops being optional.** A multi-day sleep does not survive in
24
+ * process memory. See `workflows/digest.ts` for what that looks like when you
25
+ * forget (the first digest arrives; the second never does).
26
+ *
27
+ * ## What it needs
28
+ *
29
+ * `ASSEMBLYAI_API_KEY` in the agent env — `.env` under `aai dev`, `aai secret
30
+ * put ASSEMBLYAI_API_KEY` once deployed — because the run really transcribes
31
+ * the episodes and really summarizes them. `requiredEnv` is what makes a deploy
32
+ * check for it rather than letting the first run find out, and it is
33
+ * load-bearing here in a way it is not for a voice agent: a workflow app
34
+ * declares no providers, so nothing else in its config names a credential.
35
+ *
36
+ * A step is handed no `ToolContext`, so it reads that key with `requireStepEnv`
37
+ * rather than `ctx.env` — which the SDK's transcription and model helpers do
38
+ * for you, and which is why nothing in `workflows/` mentions the key by name.
39
+ *
40
+ * The Slack webhook is the other credential and is deliberately NOT env: it is
41
+ * per-run input, so one deployed agent can post to whichever channel each run
42
+ * names.
43
+ */
44
+
45
+ import { type WorkflowDef, workflow, workflowApp } from "@alexkroman1/aai";
46
+ import { isSlackWebhookUrl } from "@alexkroman1/aai/channels";
47
+ import { z } from "zod";
48
+ import { type DailyDigestOutput, dailyDigestFlow } from "./workflows/digest.ts";
49
+
50
+ /** Somebody pastes a list; anything not http(s) is a typo worth catching here. */
51
+ function isHttpUrl(value: string): boolean {
52
+ try {
53
+ const { protocol } = new URL(value);
54
+ return protocol === "http:" || protocol === "https:";
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * The declaration: schema, description, and the body.
62
+ *
63
+ * The `input` schema does double duty in a way it cannot for a voice agent. It
64
+ * validates at `start()` — so a bad feed URL or a non-Slack webhook is a 400 at
65
+ * the call site rather than a failed run discovered an hour later, after
66
+ * transcription has already been paid for — and it is served on
67
+ * `GET /workflows` as JSON Schema, which is what lets a page render a form for
68
+ * a workflow it was not written against.
69
+ *
70
+ * Every field carries `.describe()` for that second reason, and every optional
71
+ * one carries `.default()` so the page and the body agree on the fallback
72
+ * instead of each picking its own.
73
+ */
74
+ const digestInput = z.object({
75
+ podcastChannels: z
76
+ .string()
77
+ .trim()
78
+ .min(1)
79
+ .refine(
80
+ (value) => value.split(",").every((url) => isHttpUrl(url.trim())),
81
+ "Enter comma-separated podcast links",
82
+ )
83
+ .describe("Podcast links — Apple Podcasts, Spotify, RSS, or a show page — comma-separated"),
84
+ // A host check, not a URL check. This value is the target of a POST
85
+ // carrying summarized content, so "is it a URL" would accept an
86
+ // exfiltration endpoint somebody typed into a form.
87
+ slackWebhookUrl: z
88
+ .string()
89
+ .trim()
90
+ .url()
91
+ .refine(isSlackWebhookUrl, "Enter a Slack webhook URL from hooks.slack.com")
92
+ .describe("Slack incoming webhook or workflow trigger URL"),
93
+ slackWorkflowTextParam: z
94
+ .string()
95
+ .trim()
96
+ // A Slack workflow variable name. Constrained because it is used as an
97
+ // object KEY in the request body, and an arbitrary string there is a way
98
+ // to shape a payload the workflow never declared.
99
+ .regex(/^[A-Za-z_][A-Za-z0-9_]*$/)
100
+ .default("text")
101
+ .describe("Slack workflow variable name (workflow trigger URLs only)"),
102
+ maxEpisodesPerDigest: z
103
+ .number()
104
+ .int()
105
+ .min(1)
106
+ .max(20)
107
+ .default(5)
108
+ .describe("Most episodes to include in one digest"),
109
+ intervalEvery: z.number().int().min(1).max(365).default(1).describe("Repeat every"),
110
+ intervalUnit: z
111
+ .enum(["minutes", "hours", "days"])
112
+ .default("days")
113
+ .describe("Unit for the repeat interval"),
114
+ // Bounded on purpose — see the module doc on why a run is asked to end.
115
+ daysToRun: z
116
+ .number()
117
+ .int()
118
+ .min(1)
119
+ .max(30)
120
+ .default(7)
121
+ .describe("How many digests to post before the run completes"),
122
+ });
123
+
124
+ export const dailyDigest: WorkflowDef<typeof digestInput, DailyDigestOutput> = workflow({
125
+ description:
126
+ "Watch podcast feeds, transcribe new episodes with AssemblyAI, summarize them, " +
127
+ "and post a digest to Slack on a repeating schedule",
128
+ input: digestInput,
129
+ run: dailyDigestFlow,
130
+ });
131
+
132
+ export default workflowApp({
133
+ name: "Podcast Digest",
134
+ // The whole product. A workflow app is an agent whose work happens here.
135
+ workflows: { dailyDigest },
136
+ // Checked at deploy time. A workflow app declares no providers, so this is
137
+ // the only thing that can name the credential its steps read.
138
+ requiredEnv: ["ASSEMBLYAI_API_KEY"],
139
+ });
@@ -0,0 +1,154 @@
1
+ // Copyright 2026 the AAI authors. MIT license.
2
+ /**
3
+ * The browser half — a form that starts a run, and a panel that watches one
4
+ * that may outlive the browser by a week.
5
+ *
6
+ * Mounted with `page()` rather than `client()`: there is no session to build, so
7
+ * no socket, no audio graph, no microphone request. `useWorkflowSubmit()` starts
8
+ * the run, follows its STATUS, and hands back the controls bound to it. The API
9
+ * is durable, so the `runId` is the whole state — it survives a reload, another
10
+ * device, or `curl`.
11
+ *
12
+ * ## The form is DECLARED, not written
13
+ *
14
+ * Every property of this workflow's input schema is a scalar, so
15
+ * `<WorkflowFields>` renders the whole form from what `GET /workflows` serves —
16
+ * a `<SelectField>` for the `z.enum`, number spinners for the counts, the
17
+ * `.describe()` text as each hint, and the `.default()` as each control's
18
+ * starting value. Adding an eighth field to `agent.ts` adds an eighth control
19
+ * here with no edit to this file, and the defaults are stated once, in the
20
+ * schema, rather than here AND in the schema AND in the workflow body.
21
+ *
22
+ * `link-digest` is the page that writes its one control by hand, deliberately,
23
+ * so a reader can see what this layer stands on. `redline` is the MIXED case —
24
+ * declared scalars beside one hand-written array field.
25
+ *
26
+ * ## What a SCHEDULED run adds to `link-digest`'s page
27
+ *
28
+ * Two controls that only make sense when a run spends most of its life asleep,
29
+ * and both are the point of copying this file rather than that one:
30
+ *
31
+ * - **Wake.** A sleeping run's next digest is hours or days out. `wake()` cuts
32
+ * the wait short and sends it now. Without it the only handle on a sleeping
33
+ * run is `cancel`, so "send it now" and "throw it away" would be the same
34
+ * button. `wake` answering 0 means the run had already moved past its sleep,
35
+ * which is why nothing here treats that as a failure.
36
+ * - **Cancel.** A run scheduled for thirty digests is a standing commitment, and
37
+ * the page that started it is the obvious place to end it. Cancelling is why
38
+ * `daysToRun` can be generous.
39
+ *
40
+ * Both are bound to the run the submission is following, which is the whole
41
+ * reason this page no longer holds a `createWorkflowApi()` of its own.
42
+ *
43
+ * ## Why the status panel reads `digestsSent` and not a progress bar
44
+ *
45
+ * There is nothing to fill. A run posts digest 3 of 7 and then sleeps for a day
46
+ * — a bar would sit at 43% looking stalled for most of the run's life. The
47
+ * honest display is the count plus the run's own newest line.
48
+ */
49
+
50
+ import {
51
+ Form,
52
+ page,
53
+ SubmitButton,
54
+ useWorkflowSubmit,
55
+ WorkflowFields,
56
+ WorkflowProgress,
57
+ } from "@alexkroman1/aai-ui";
58
+ import "@alexkroman1/aai-ui/styles.css";
59
+ // ERASED at build time, so naming the agent's own type costs the browser bundle
60
+ // nothing — and it is what stops this file restating a shape `workflows/
61
+ // digest.ts` already declares.
62
+ import type { WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
63
+ import type { dailyDigest } from "./agent.ts";
64
+
65
+ /** What a completed run reports, derived from the workflow rather than restated. */
66
+ type Digest = WorkflowOutputOf<typeof dailyDigest>;
67
+
68
+ /** The workflow this page drives. Matches the key in `workflowApp({ workflows })`. */
69
+ const WORKFLOW = "dailyDigest";
70
+
71
+ export function App() {
72
+ // The generic is what makes `run.status === "completed"` narrow to a TYPED
73
+ // `run.output` instead of `unknown`. `error` is the agent's own sentence for a
74
+ // rejected input — better copy than anything this page could write, and the
75
+ // reason there is no `try`/`catch` here.
76
+ const { submit, run, pending, error, wake, cancel } = useWorkflowSubmit<Digest>(WORKFLOW);
77
+
78
+ return (
79
+ <main className="mx-auto flex max-w-2xl flex-col gap-6 p-8">
80
+ <h1 className="text-2xl font-medium">Podcast Digest</h1>
81
+
82
+ {/* `submit()` resolves as soon as the run EXISTS — deliberately not when it
83
+ finishes, which here could be a month away. */}
84
+ <Form onSubmit={(values) => submit(values)} error={error}>
85
+ <WorkflowFields workflow={WORKFLOW} />
86
+ <SubmitButton pending={pending}>
87
+ {pending ? "Digest scheduled" : "Start digest"}
88
+ </SubmitButton>
89
+ </Form>
90
+
91
+ {/* A run that has not settled says so. `pending` is not derivable from the
92
+ snapshot alone — an id the agent never knew leaves `run` undefined,
93
+ which would otherwise read as "still waiting" forever. */}
94
+ {pending && <p>You can close this tab — the run continues without it.</p>}
95
+
96
+ {/* The run's own narration, newest line only. `lines={1}` is the window;
97
+ everything else — the replay, and the "serves no stream" case that is
98
+ otherwise indistinguishable from "wrote nothing yet" — belongs to the
99
+ component. */}
100
+ <WorkflowProgress runId={run?.runId} lines={1} className="text-sm opacity-70" />
101
+
102
+ {pending && (
103
+ <div className="flex gap-2">
104
+ {/* The counterpart of the `sleep` in `workflows/digest.ts` — see the
105
+ module doc on why a scheduled run needs this AND cancel. */}
106
+ <button
107
+ type="button"
108
+ onClick={() => void wake()}
109
+ className="rounded-md border px-3 py-1 text-sm"
110
+ >
111
+ Send the next digest now
112
+ </button>
113
+ <button
114
+ type="button"
115
+ onClick={() => void cancel()}
116
+ className="rounded-md border px-3 py-1 text-sm text-red-600"
117
+ >
118
+ Stop the schedule
119
+ </button>
120
+ </div>
121
+ )}
122
+
123
+ {run?.status === "failed" && <p className="text-red-600">That run failed: {run.error}</p>}
124
+ {run?.status === "cancelled" && <p>Cancelled — no further digests will be posted.</p>}
125
+
126
+ {run?.status === "completed" && (
127
+ <article className="flex flex-col gap-4">
128
+ <p className="text-sm opacity-70">
129
+ Posted {run.output.digestsSent} of {run.output.digestsScheduled} digests to{" "}
130
+ {run.output.deliveryTarget}, every {run.output.scheduleInterval}.
131
+ </p>
132
+ {run.output.lastDigest?.episodes.map((episode) => (
133
+ <section key={episode.id} className="flex flex-col gap-1 border-t pt-3">
134
+ <p className="text-sm opacity-70">{episode.podcastTitle}</p>
135
+ <h2 className="text-lg">
136
+ <a href={episode.url} className="underline">
137
+ {episode.title}
138
+ </a>
139
+ </h2>
140
+ <p>{episode.summary}</p>
141
+ <ul className="flex list-disc flex-col gap-1 pl-5 text-sm">
142
+ {episode.keyPoints.map((point) => (
143
+ <li key={point}>{point}</li>
144
+ ))}
145
+ </ul>
146
+ </section>
147
+ ))}
148
+ </article>
149
+ )}
150
+ </main>
151
+ );
152
+ }
153
+
154
+ page({ name: "Podcast Digest", component: App });