@alexkroman1/aai-cli 6.10.1 → 7.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.
- package/dist/{_agent-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
- package/dist/{_bundler-C2BDwU7r.mjs → _bundler-B4RqNF5Z.mjs} +2 -2
- package/dist/_dev-env.d.ts +22 -2
- package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-CiKFH9qw.mjs} +38 -47
- package/dist/_dev-server.d.ts +0 -17
- package/dist/{_init-C57bWAQ8.mjs → _init-CE4HKdgV.mjs} +3 -2
- package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
- package/dist/_preflight.d.ts +1 -1
- package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-nRFaBEPJ.mjs} +2 -2
- package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
- package/dist/{_templates-Dxb_P2Wz.mjs → _templates-FI3xPEMj.mjs} +1 -1
- package/dist/_workflow-scan.d.ts +108 -0
- package/dist/{build-Dfza2pRI.mjs → build-6jfaakgc.mjs} +5 -2
- package/dist/cli.mjs +30 -25
- package/dist/{client-bundler-BEIqgOtd.mjs → client-bundler-CWnG42cU.mjs} +2 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-vbpjGzqs.mjs → delete-Dl_LSfvI.mjs} +3 -3
- package/dist/{deploy-B8lubiRT.mjs → deploy-DvYeD8ia.mjs} +5 -5
- package/dist/{dev-Uq5ujP8a.mjs → dev-B59JIfWO.mjs} +1 -1
- package/dist/{eject-31gjtaHF.mjs → eject-C9WJyyr2.mjs} +1 -1
- package/dist/{init-l6xfU4xX.mjs → init-CpJVjEg0.mjs} +3 -3
- package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
- package/dist/{logs-BWVGSjLR.mjs → logs-9q-psa4q.mjs} +2 -2
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +93 -14
- package/dist/scaffold/package.json +5 -3
- package/dist/scaffold/server.mjs +13 -4
- package/dist/scaffold/vite.config.ts +1 -1
- package/dist/{secret-BuMuFR4B.mjs → secret-CVvSLIDV.mjs} +2 -2
- package/dist/{storage-DzRZ-eCw.mjs → storage-BvUrnvM3.mjs} +2 -2
- package/dist/{studio-BCNUpDgP.mjs → studio--MUV0cid.mjs} +4 -4
- package/dist/templates/call-audit/agent.test.ts +818 -0
- package/dist/templates/call-audit/agent.ts +171 -0
- package/dist/templates/call-audit/client.tsx +198 -0
- package/dist/templates/call-audit/workflows/audit.ts +298 -0
- package/dist/templates/call-audit/workflows/ingest.ts +251 -0
- package/dist/templates/call-audit/workflows/media.ts +637 -0
- package/dist/templates/call-audit/workflows/summarize.ts +201 -0
- package/dist/templates/call-audit/workflows/sync-api.ts +44 -0
- package/dist/templates/dispatch-center/agent.test.ts +149 -25
- package/dist/templates/dispatch-center/client.tsx +239 -129
- package/dist/templates/dispatch-center/shared.ts +99 -1
- package/dist/templates/dispatch-center/system-prompt.md +3 -1
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +16 -13
- package/dist/templates/dispatch-center/tools/incident_create.ts +19 -1
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +68 -59
- package/dist/templates/dispatch-center/tools/incident_triage.ts +57 -43
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +49 -40
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +8 -2
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +69 -52
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +45 -41
- package/dist/templates/embedded-assets/agent.test.ts +17 -14
- package/dist/templates/health-assistant/agent.test.ts +22 -4
- package/dist/templates/infocom-adventure/agent.test.ts +4 -6
- package/dist/templates/infocom-adventure/client.tsx +246 -164
- package/dist/templates/link-digest/agent.test.ts +24 -19
- package/dist/templates/link-digest/client.tsx +47 -61
- package/dist/templates/link-digest/workflows/digest.ts +19 -31
- package/dist/templates/night-owl/agent.test.ts +70 -19
- package/dist/templates/night-owl/agent.ts +5 -0
- package/dist/templates/night-owl/client.tsx +56 -56
- package/dist/templates/night-owl/shared.ts +24 -0
- package/dist/templates/night-owl/tools/recommend.ts +18 -6
- package/dist/templates/pizza-ordering/agent.test.ts +27 -27
- package/dist/templates/pizza-ordering/client.tsx +9 -26
- package/dist/templates/plan-and-execute/agent.test.ts +99 -58
- package/dist/templates/plan-and-execute/agent.ts +1 -1
- package/dist/templates/plan-and-execute/client.tsx +12 -15
- package/dist/templates/plan-and-execute/shared.ts +71 -2
- package/dist/templates/plan-and-execute/tools/plan_status.ts +18 -6
- package/dist/templates/plan-and-execute/tools/revise_plan.ts +18 -10
- package/dist/templates/plan-and-execute/tools/start_plan.ts +13 -2
- package/dist/templates/plan-and-execute/tools/work_next_step.ts +70 -22
- package/dist/templates/podcast-digest/agent.test.ts +746 -0
- package/dist/templates/podcast-digest/agent.ts +139 -0
- package/dist/templates/podcast-digest/client.tsx +154 -0
- package/dist/templates/podcast-digest/workflows/digest.ts +411 -0
- package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
- package/dist/templates/podcast-digest/workflows/slack.ts +209 -0
- package/dist/templates/recap-workflow/agent.test.ts +107 -96
- package/dist/templates/recap-workflow/tools/recap_progress.ts +9 -12
- package/dist/templates/recap-workflow/tools/recap_status.ts +8 -9
- package/dist/templates/recap-workflow/tools/request_recap.ts +2 -1
- package/dist/templates/recap-workflow/workflows/recap.ts +48 -41
- package/dist/templates/redline/agent.test.ts +17 -15
- package/dist/templates/redline/client.tsx +12 -12
- package/dist/templates/redline/workflows/redline.ts +19 -31
- package/dist/templates/research-workflow/agent.test.ts +60 -59
- package/dist/templates/research-workflow/tools/research_progress.ts +9 -12
- package/dist/templates/research-workflow/tools/research_status.ts +9 -11
- package/dist/templates/research-workflow/workflows/research.ts +44 -61
- package/dist/templates/retail/agent.test.ts +26 -23
- package/dist/templates/retail/client.tsx +226 -117
- package/dist/templates/retail/registry.test.ts +38 -6
- package/dist/templates/retail/store.test.ts +82 -15
- package/dist/templates/retail/store.ts +174 -47
- package/dist/templates/retail/system-prompt.md +11 -2
- package/dist/templates/retail/tools/cancel_pending_order.ts +2 -2
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +2 -2
- package/dist/templates/retail/tools/find_user_id_by_email.ts +8 -5
- package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +4 -5
- package/dist/templates/retail/tools/get_item_details.ts +3 -4
- package/dist/templates/retail/tools/get_order_details.ts +3 -4
- package/dist/templates/retail/tools/get_product_details.ts +3 -4
- package/dist/templates/retail/tools/get_user_details.ts +2 -2
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -5
- package/dist/templates/retail/tools/modify_pending_order_address.ts +2 -2
- package/dist/templates/retail/tools/modify_pending_order_items.ts +2 -3
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +2 -2
- package/dist/templates/retail/tools/modify_user_address.ts +2 -2
- package/dist/templates/retail/tools/return_delivered_order_items.ts +2 -2
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +8 -4
- package/dist/templates/solo-rpg/agent.test.ts +227 -48
- package/dist/templates/solo-rpg/client.tsx +38 -37
- package/dist/templates/solo-rpg/shared.ts +145 -19
- package/dist/templates/solo-rpg/system-prompt.md +3 -2
- package/dist/templates/solo-rpg/tools/action_roll.ts +82 -63
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +85 -50
- package/dist/templates/solo-rpg/tools/check_state.ts +24 -7
- package/dist/templates/solo-rpg/tools/load_game.ts +13 -1
- package/dist/templates/solo-rpg/tools/save_game.ts +16 -7
- package/dist/templates/solo-rpg/tools/setup_character.ts +22 -2
- package/dist/templates/solo-rpg/tools/update_state.ts +113 -100
- package/dist/templates/spoken-summary/agent.test.ts +314 -0
- package/dist/templates/spoken-summary/agent.ts +155 -0
- package/dist/templates/spoken-summary/client.tsx +185 -0
- package/dist/templates/spoken-summary/workflows/summarize.ts +237 -0
- package/dist/templates/spoken-summary/workflows/transcribe.ts +138 -0
- package/dist/templates/support-line/agent.test.ts +11 -16
- package/dist/templates/support-line/agent.ts +1 -1
- package/dist/templates/support-line/client.tsx +9 -9
- package/dist/templates/support-line/nodes.ts +100 -0
- package/dist/templates/support-line/procedure.ts +407 -0
- package/dist/templates/support-line/tools/answer_question.ts +17 -4
- package/dist/templates/transcription-workflow/agent.test.ts +246 -123
- package/dist/templates/transcription-workflow/agent.ts +21 -7
- package/dist/templates/transcription-workflow/client.tsx +17 -42
- package/dist/templates/transcription-workflow/workflows/batch.ts +79 -180
- package/dist/templates/transcription-workflow/workflows/normalize.ts +247 -0
- package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
- package/dist/templates/transcription-workflow/workflows/stream.ts +10 -9
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +26 -94
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +55 -40
- package/dist/templates/transcription-workflow/workflows/wav.ts +31 -31
- package/dist/templates/travel-concierge/agent.test.ts +64 -33
- package/dist/templates/travel-concierge/client.tsx +11 -23
- package/dist/templates/travel-concierge/routing.ts +34 -15
- package/dist/templates/travel-concierge/shared.ts +70 -3
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +2 -2
- package/dist/templates/travel-concierge/tools/book_excursion.ts +2 -2
- package/dist/templates/travel-concierge/tools/book_hotel.ts +2 -2
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +2 -2
- package/dist/templates/travel-concierge/tools/update_ticket.ts +2 -2
- package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-COxnqstQ.mjs} +221 -3
- package/dist/worker-bundler.mjs +1 -1
- package/dist/{workflow-CFpxOFfQ.mjs → workflow-D2AQf2Pl.mjs} +28 -1
- package/dist/workflow-bundler.d.ts +6 -1
- package/dist/workflow.d.ts +1 -1
- package/package.json +5 -4
- package/dist/templates/support-line/graph.ts +0 -224
- package/dist/{_config-D_s09e7g.mjs → _config-CmJOFsAP.mjs} +1 -1
- /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 { z } from "zod";
|
|
47
|
+
import { type DailyDigestOutput, dailyDigestFlow } from "./workflows/digest.ts";
|
|
48
|
+
import { isSlackWebhookUrl } from "./workflows/slack.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 });
|