@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
@@ -1,7 +1,7 @@
1
1
  // ─── Types ───────────────────────────────────────────────────────────────────
2
2
 
3
3
  import type { DeepReadonly, ToolFailure } from "@alexkroman1/aai";
4
- import { pushCapped, sessionSlot } from "@alexkroman1/aai";
4
+ import { dialog, pushCapped, sessionSlot } from "@alexkroman1/aai";
5
5
 
6
6
  export const SEVERITIES = ["critical", "urgent", "moderate", "minor"] as const;
7
7
  export type Severity = (typeof SEVERITIES)[number];
@@ -218,6 +218,104 @@ export const dispatchSlot = sessionSlot("dispatch", createDefaultState, {
218
218
  /** The projection BOTH ends use: `syncState` on the agent, `useAgentState` in the client. */
219
219
  export const dashboardProjection = dispatchSlot.projection(dashboardView);
220
220
 
221
+ // ─── The call in hand ────────────────────────────────────────────────────────
222
+
223
+ /**
224
+ * Where the dispatcher is on the call in hand, as a plain state map.
225
+ *
226
+ * Declared as a {@link DialogSpec} — `{ initial, states }`, each state carrying
227
+ * its own `instruction` and `on` map — rather than an XState machine. This
228
+ * dialog used exactly the four things a spec can say, and the `setup({ types:
229
+ * {} as { events: … } })` block it used to carry restated the four event names
230
+ * already written in the `on` maps below. The instruction is a TYPED field now
231
+ * instead of an untyped `meta` bag, which matters because a misspelling there
232
+ * produced a refusal with no recovery text and no error anywhere.
233
+ *
234
+ * The system prompt used to carry this as prose — "incident_triage: After
235
+ * creating, assess severity", "Critical incidents get immediate dispatch",
236
+ * "Never leave a critical incident without at least one resource dispatched" —
237
+ * which is advice to a model rather than a property of the agent. Two things
238
+ * change by declaring it.
239
+ *
240
+ * **The gate is real.** Six mutating tools take an incident id and did their own
241
+ * "does this exist" check through {@link findIncident}, whose answer on an empty
242
+ * board is `Incident INC-0001 not found` — a data answer to a positional
243
+ * question, and the wrong sentence for a dispatcher who has logged nothing yet.
244
+ * `when: "working"` is that check now, and the SDK's refusal names where the
245
+ * shift actually is and quotes the state's instruction.
246
+ *
247
+ * **The instruction rides every result.** A flow tool answers with the position
248
+ * it landed in, so `incident_create`'s result now ends with "confirm the
249
+ * severity and type with incident_triage" and `incident_triage`'s with "assign
250
+ * units" — the tool-by-tool sequencing the prompt was spending paragraphs on,
251
+ * arriving in the last thing the model reads before it speaks.
252
+ *
253
+ * **What this position is NOT is per-incident.** A flow is bound to a session
254
+ * and holds one position, and this board holds many incidents at once — so
255
+ * `working.monitoring` means "the incident this dispatcher last touched has
256
+ * units on it", not "every incident does". Per-incident status stays where it
257
+ * belongs, on {@link Incident.status}, and the tools stay addressed by id. That
258
+ * is also why `working` is a PARENT state: what every gated tool actually needs
259
+ * is "something has been logged on this shift", and the three children exist to
260
+ * carry the instruction for the step in front of the dispatcher rather than to
261
+ * gate anything.
262
+ *
263
+ * `LOGGED` is accepted from every state because a new 911 call is always legal —
264
+ * the one transition that is not a progression.
265
+ *
266
+ * `as const` is what keeps the `on` keys literal, so `DialogEvent` synthesizes
267
+ * the event union from them and a misspelled `send` is a compile error.
268
+ */
269
+ const callSpec = {
270
+ initial: "standby",
271
+ states: {
272
+ standby: {
273
+ instruction:
274
+ "Nothing is logged on this shift. Take the call — location first, then the " +
275
+ "nature of the emergency, then the caller's name and callback — and log it " +
276
+ "with incident_create.",
277
+ on: { LOGGED: "working" },
278
+ },
279
+ working: {
280
+ initial: "triaging",
281
+ // Declared on the PARENT so all three children inherit it, which is what
282
+ // makes a new call legal mid-incident without repeating the transition
283
+ // three times. The children re-target each other below.
284
+ on: { LOGGED: ".triaging" },
285
+ states: {
286
+ triaging: {
287
+ instruction:
288
+ "Logged, not yet triaged. Confirm or override the severity and type with " +
289
+ "incident_triage. For a critical call, dispatch first and triage alongside it.",
290
+ on: { TRIAGED: "dispatching", DISPATCHED: "monitoring", ESCALATED: "dispatching" },
291
+ },
292
+ dispatching: {
293
+ instruction:
294
+ "Triaged, nothing rolling. Assign units with resources_dispatch — never " +
295
+ "leave a critical incident without at least one.",
296
+ on: { DISPATCHED: "monitoring", TRIAGED: "dispatching", ESCALATED: "dispatching" },
297
+ },
298
+ monitoring: {
299
+ instruction:
300
+ "Units are assigned. Work it — status updates as they radio in, notes as " +
301
+ "the picture changes, escalate if it outgrows the response, and close it " +
302
+ "with incident_update_status.",
303
+ on: { TRIAGED: "dispatching", DISPATCHED: "monitoring", ESCALATED: "dispatching" },
304
+ },
305
+ },
306
+ },
307
+ },
308
+ } as const;
309
+
310
+ /**
311
+ * The flow. Its own slot key beside {@link dispatchSlot}: the flow holds the
312
+ * POSITION and the board holds the incidents, because the position is one fact
313
+ * about the conversation and the board is many facts about the world. One tool
314
+ * call moves both — every converted tool opens `dispatchSlot.update` inside its
315
+ * `execute` and lets the flow's own `send` follow it.
316
+ */
317
+ export const callFlow = dialog("call", callSpec);
318
+
221
319
  /**
222
320
  * The board as a READ hands it out: deep-frozen, and typed to say so.
223
321
  *
@@ -2,11 +2,13 @@ You are the AI-powered Emergency Dispatch Command Center. You coordinate emergen
2
2
 
3
3
  Your role combines call-taker, dispatcher, and incident commander. You speak like an experienced dispatcher: calm, precise, and authoritative. Never panic. Use brevity codes and dispatch terminology naturally.
4
4
 
5
+ WHERE THE CALL IS: every incident and resource tool answers with the position the call is in and what that position expects next — read it. Those tools refuse outright until something has been logged, and the refusal tells you what to do first. Nothing here has to be remembered between turns.
6
+
5
7
  Your tools:
6
8
 
7
9
  INCIDENT MANAGEMENT:
8
10
  - incident_create: Log a new incident. Ask for location first, then nature of emergency, then caller info. Speed matters for critical calls.
9
- - incident_triage: After creating, assess severity. The system recommends severity, type, and protocols. Review and confirm or override.
11
+ - incident_triage: Assess severity. The system recommends severity, type, and protocols. Review and confirm or override.
10
12
  - incident_update_status: Move incidents through the workflow (en_route, on_scene, resolved, escalated).
11
13
  - incident_get: Get details on a specific incident.
12
14
  - incident_escalate: Escalate when an incident exceeds current capacity or severity increases.
@@ -1,25 +1,28 @@
1
1
  import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
- import { dispatchSlot, findIncident, logEvent } from "../shared.ts";
3
+ import { callFlow, dispatchSlot, findIncident, logEvent } from "../shared.ts";
4
4
 
5
- export default dispatchSlot.updateTool({
5
+ /** Gated on `working` — there is nothing to annotate before anything is logged. */
6
+ export default callFlow.tool({
6
7
  description: "Add a situational update note to an incident's timeline.",
8
+ when: "working",
7
9
  inputSchema: z.object({
8
10
  incidentId: z.string().max(20).describe("The incident ID"),
9
11
  note: z.string().max(1000).describe("The note to add"),
10
12
  source: z.string().max(100).describe("Who reported this — unit callsign or caller").optional(),
11
13
  }),
12
- execute(args, state) {
13
- const inc = findIncident(state, args.incidentId);
14
- if (isToolFailure(inc)) return inc;
14
+ execute: (args, ctx) =>
15
+ dispatchSlot.update(ctx, (state) => {
16
+ const inc = findIncident(state, args.incidentId);
17
+ if (isToolFailure(inc)) return inc;
15
18
 
16
- const entry = args.source ? `[${args.source}] ${args.note}` : args.note;
17
- logEvent(inc, entry);
19
+ const entry = args.source ? `[${args.source}] ${args.note}` : args.note;
20
+ logEvent(inc, entry);
18
21
 
19
- return {
20
- incidentId: args.incidentId,
21
- noteAdded: entry,
22
- timelineEntries: inc.timeline.length,
23
- };
24
- },
22
+ return {
23
+ incidentId: args.incidentId,
24
+ noteAdded: entry,
25
+ timelineEntries: inc.timeline.length,
26
+ };
27
+ }),
25
28
  });
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import {
3
3
  calculateTriageScore,
4
+ callFlow,
4
5
  createIncident,
5
6
  dispatchSlot,
6
7
  getApplicableProtocols,
@@ -10,6 +11,18 @@ import {
10
11
  resourceBrief,
11
12
  } from "../shared.ts";
12
13
 
14
+ /**
15
+ * **Not a `callFlow.tool`, deliberately.** A new 911 call is legal in every
16
+ * state, so a `when` listing all of them would be a gate that gates nothing —
17
+ * the same call `plan-and-execute`'s `start_plan` makes. It sends `LOGGED`
18
+ * itself, which is what `dialog.send` is public for, and reports the position it
19
+ * landed in so the model reads "confirm the severity and type" as part of this
20
+ * result rather than having to remember it from the prompt.
21
+ *
22
+ * The `send` sits INSIDE the board's mutation window, which is safe because it
23
+ * is a different slot: the re-entrancy guard is per slot, and neither window
24
+ * spans an await, so the whole call commits atomically.
25
+ */
13
26
  export default dispatchSlot.updateTool({
14
27
  description: "Create a new incident from an incoming emergency call.",
15
28
  inputSchema: z.object({
@@ -29,7 +42,7 @@ export default dispatchSlot.updateTool({
29
42
  .describe("Known hazards: fire, chemical, electrical, structural, weapons")
30
43
  .optional(),
31
44
  }),
32
- execute(args, state) {
45
+ execute(args, state, ctx) {
33
46
  const recSeverity = recommendSeverity(args.description);
34
47
  const recType = recommendType(args.description);
35
48
  const triageScore = calculateTriageScore(
@@ -62,6 +75,11 @@ export default dispatchSlot.updateTool({
62
75
 
63
76
  return {
64
77
  incidentId: id,
78
+ // Spread the position rather than renaming its fields: this tool is
79
+ // ungated, so the `{ state, instruction }` a gated tool's result carries
80
+ // has to come from here — and under two different key sets the model
81
+ // reads its own position two different ways within one agent.
82
+ ...callFlow.send(ctx, { type: "LOGGED" }),
65
83
  recommendedSeverity: recSeverity,
66
84
  recommendedType: recType,
67
85
  triageScore,
@@ -3,14 +3,22 @@ import { z } from "zod";
3
3
  import {
4
4
  assertNotResolved,
5
5
  calculateTriageScore,
6
+ callFlow,
6
7
  dispatchSlot,
7
8
  findIncident,
8
9
  logEvent,
9
10
  recommendResources,
10
11
  } from "../shared.ts";
11
12
 
12
- export default dispatchSlot.updateTool({
13
+ /**
14
+ * Gated on `working`, and `ESCALATED` puts the call back in `dispatching`: an
15
+ * incident that outgrew its response needs more units, which is exactly that
16
+ * state's instruction.
17
+ */
18
+ export default callFlow.tool({
13
19
  description: "Escalate an incident when it exceeds current capacity or severity increases.",
20
+ when: "working",
21
+ send: { type: "ESCALATED" },
14
22
  inputSchema: z.object({
15
23
  incidentId: z.string().max(20).describe("The incident ID"),
16
24
  reason: z.string().max(1000).describe("Reason for escalation"),
@@ -20,67 +28,68 @@ export default dispatchSlot.updateTool({
20
28
  .optional(),
21
29
  newSeverity: z.enum(["critical", "urgent"]).describe("Escalated severity level").optional(),
22
30
  }),
23
- execute(args, state) {
24
- const inc = findIncident(state, args.incidentId);
25
- if (isToolFailure(inc)) return inc;
26
- const blocked = assertNotResolved(inc, "escalate");
27
- if (blocked) return blocked;
31
+ execute: (args, ctx) =>
32
+ dispatchSlot.update(ctx, (state) => {
33
+ const inc = findIncident(state, args.incidentId);
34
+ if (isToolFailure(inc)) return inc;
35
+ const blocked = assertNotResolved(inc, "escalate");
36
+ if (blocked) return blocked;
28
37
 
29
- inc.escalationLevel++;
30
- if (args.newSeverity) inc.severity = args.newSeverity;
31
- inc.status = "escalated";
32
- logEvent(inc, `ESCALATED (Level ${inc.escalationLevel}): ${args.reason}`);
38
+ inc.escalationLevel++;
39
+ if (args.newSeverity) inc.severity = args.newSeverity;
40
+ inc.status = "escalated";
41
+ logEvent(inc, `ESCALATED (Level ${inc.escalationLevel}): ${args.reason}`);
33
42
 
34
- if (args.requestMutualAid) {
35
- logEvent(inc, "Mutual aid requested from neighboring jurisdictions");
36
- // Counter-based ids/callsigns: Date.now() collides across rapid
37
- // escalations, and duplicate callsigns break dispatch-by-callsign.
38
- const medicN = ++state.mutualAidCounter;
39
- const engineN = ++state.mutualAidCounter;
40
- state.resources.push(
41
- {
42
- id: `MA-${medicN}`,
43
- type: "ambulance",
44
- callsign: `Mutual-Aid-Medic-${medicN}`,
45
- status: "available",
46
- assignedIncident: null,
47
- eta: null,
48
- capabilities: ["als"],
49
- },
50
- {
51
- id: `MA-${engineN}`,
52
- type: "fire_engine",
53
- callsign: `Mutual-Aid-Engine-${engineN}`,
54
- status: "available",
55
- assignedIncident: null,
56
- eta: null,
57
- capabilities: ["structural"],
58
- },
59
- );
60
- }
43
+ if (args.requestMutualAid) {
44
+ logEvent(inc, "Mutual aid requested from neighboring jurisdictions");
45
+ // Counter-based ids/callsigns: Date.now() collides across rapid
46
+ // escalations, and duplicate callsigns break dispatch-by-callsign.
47
+ const medicN = ++state.mutualAidCounter;
48
+ const engineN = ++state.mutualAidCounter;
49
+ state.resources.push(
50
+ {
51
+ id: `MA-${medicN}`,
52
+ type: "ambulance",
53
+ callsign: `Mutual-Aid-Medic-${medicN}`,
54
+ status: "available",
55
+ assignedIncident: null,
56
+ eta: null,
57
+ capabilities: ["als"],
58
+ },
59
+ {
60
+ id: `MA-${engineN}`,
61
+ type: "fire_engine",
62
+ callsign: `Mutual-Aid-Engine-${engineN}`,
63
+ status: "available",
64
+ assignedIncident: null,
65
+ eta: null,
66
+ capabilities: ["structural"],
67
+ },
68
+ );
69
+ }
61
70
 
62
- inc.triageScore = calculateTriageScore(
63
- inc.severity,
64
- inc.type,
65
- inc.casualties.estimated,
66
- inc.hazards.length,
67
- );
71
+ inc.triageScore = calculateTriageScore(
72
+ inc.severity,
73
+ inc.type,
74
+ inc.casualties.estimated,
75
+ inc.hazards.length,
76
+ );
68
77
 
69
- const additionalResources = recommendResources(inc.type, inc.severity, state).filter(
70
- (r) => !inc.assignedResources.includes(r.id),
71
- );
78
+ const additionalResources = recommendResources(inc.type, inc.severity, state).filter(
79
+ (r) => !inc.assignedResources.includes(r.id),
80
+ );
72
81
 
73
- return {
74
- incidentId: args.incidentId,
75
- escalationLevel: inc.escalationLevel,
76
- newSeverity: inc.severity,
77
- newTriageScore: inc.triageScore,
78
- mutualAidRequested: args.requestMutualAid ?? false,
79
- additionalResourcesAvailable: additionalResources.map((r) => ({
80
- callsign: r.callsign,
81
- type: r.type,
82
- })),
83
- message: `ESCALATION CONFIRMED — ${args.incidentId} now Level ${inc.escalationLevel}. ${additionalResources.length} additional resource(s) available for dispatch.`,
84
- };
85
- },
82
+ return {
83
+ incidentId: args.incidentId,
84
+ escalationLevel: inc.escalationLevel,
85
+ newSeverity: inc.severity,
86
+ newTriageScore: inc.triageScore,
87
+ mutualAidRequested: args.requestMutualAid ?? false,
88
+ additionalResourcesAvailable: additionalResources.map((r) => ({
89
+ callsign: r.callsign,
90
+ type: r.type,
91
+ })),
92
+ message: `ESCALATION CONFIRMED — ${args.incidentId} now Level ${inc.escalationLevel}. ${additionalResources.length} additional resource(s) available for dispatch.`,
93
+ };
94
+ }),
86
95
  });
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import {
4
4
  assertNotResolved,
5
5
  calculateTriageScore,
6
+ callFlow,
6
7
  dispatchSlot,
7
8
  findIncident,
8
9
  getApplicableProtocols,
@@ -12,9 +13,21 @@ import {
12
13
  SEVERITIES,
13
14
  } from "../shared.ts";
14
15
 
15
- export default dispatchSlot.updateTool({
16
+ /**
17
+ * Gated on `working`: there is nothing to triage until something has been
18
+ * logged, and the flow's refusal says so in the dispatcher's own terms where
19
+ * `findIncident` would have answered `Incident INC-0001 not found` — a data
20
+ * answer to a positional question.
21
+ *
22
+ * `TRIAGED` moves the call to `dispatching`, whose instruction is "assign
23
+ * units", so the sequencing the system prompt used to spell out tool by tool
24
+ * arrives in this tool's own result.
25
+ */
26
+ export default callFlow.tool({
16
27
  description:
17
28
  "Triage an incident — confirm or override severity, type, hazards, and casualty count.",
29
+ when: "working",
30
+ send: { type: "TRIAGED" },
18
31
  inputSchema: z.object({
19
32
  incidentId: z.string().max(20).describe("The incident ID"),
20
33
  severity: z.enum(SEVERITIES).describe("Confirmed severity after triage").optional(),
@@ -27,50 +40,51 @@ export default dispatchSlot.updateTool({
27
40
  casualtyUpdate: z.number().int().nonnegative().describe("Updated casualty count").optional(),
28
41
  notes: z.string().max(1000).describe("Triage notes").optional(),
29
42
  }),
30
- execute(args, state) {
31
- const inc = findIncident(state, args.incidentId);
32
- if (isToolFailure(inc)) return inc;
33
- const blocked = assertNotResolved(inc, "triage");
34
- if (blocked) return blocked;
43
+ execute: (args, ctx) =>
44
+ dispatchSlot.update(ctx, (state) => {
45
+ const inc = findIncident(state, args.incidentId);
46
+ if (isToolFailure(inc)) return inc;
47
+ const blocked = assertNotResolved(inc, "triage");
48
+ if (blocked) return blocked;
35
49
 
36
- if (args.severity) inc.severity = args.severity;
37
- if (args.type) inc.type = args.type;
38
- for (const hazard of args.additionalHazards ?? []) {
39
- if (!inc.hazards.includes(hazard)) inc.hazards.push(hazard);
40
- }
41
- if (args.casualtyUpdate !== undefined) {
42
- inc.casualties.estimated = args.casualtyUpdate;
43
- }
44
- if (args.notes) logEvent(inc, `Triage note: ${args.notes}`);
50
+ if (args.severity) inc.severity = args.severity;
51
+ if (args.type) inc.type = args.type;
52
+ for (const hazard of args.additionalHazards ?? []) {
53
+ if (!inc.hazards.includes(hazard)) inc.hazards.push(hazard);
54
+ }
55
+ if (args.casualtyUpdate !== undefined) {
56
+ inc.casualties.estimated = args.casualtyUpdate;
57
+ }
58
+ if (args.notes) logEvent(inc, `Triage note: ${args.notes}`);
45
59
 
46
- inc.triageScore = calculateTriageScore(
47
- inc.severity,
48
- inc.type,
49
- inc.casualties.estimated,
50
- inc.hazards.length,
51
- );
52
- inc.status = "triaged";
53
- logEvent(inc, `Triaged: ${inc.severity} ${inc.type}, score ${inc.triageScore}`);
60
+ inc.triageScore = calculateTriageScore(
61
+ inc.severity,
62
+ inc.type,
63
+ inc.casualties.estimated,
64
+ inc.hazards.length,
65
+ );
66
+ inc.status = "triaged";
67
+ logEvent(inc, `Triaged: ${inc.severity} ${inc.type}, score ${inc.triageScore}`);
54
68
 
55
- const protocols = getApplicableProtocols(inc.type, inc.severity);
56
- const recommended = recommendResources(inc.type, inc.severity, state);
69
+ const protocols = getApplicableProtocols(inc.type, inc.severity);
70
+ const recommended = recommendResources(inc.type, inc.severity, state);
57
71
 
58
- return {
59
- incidentId: args.incidentId,
60
- severity: inc.severity,
61
- type: inc.type,
62
- triageScore: inc.triageScore,
63
- hazards: inc.hazards,
64
- estimatedCasualties: inc.casualties.estimated,
65
- protocols: protocols.map((p) => ({
66
- name: p.name,
67
- steps: p.steps,
68
- requiredResources: p.requiredResources,
69
- })),
70
- recommendedResources: recommended.map((r) => ({
71
- callsign: r.callsign,
72
- type: r.type,
73
- })),
74
- };
75
- },
72
+ return {
73
+ incidentId: args.incidentId,
74
+ severity: inc.severity,
75
+ type: inc.type,
76
+ triageScore: inc.triageScore,
77
+ hazards: inc.hazards,
78
+ estimatedCasualties: inc.casualties.estimated,
79
+ protocols: protocols.map((p) => ({
80
+ name: p.name,
81
+ steps: p.steps,
82
+ requiredResources: p.requiredResources,
83
+ })),
84
+ recommendedResources: recommended.map((r) => ({
85
+ callsign: r.callsign,
86
+ type: r.type,
87
+ })),
88
+ };
89
+ }),
76
90
  });
@@ -1,9 +1,17 @@
1
1
  import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
- import { assertNotResolved, dispatchSlot, findIncident, logEvent } from "../shared.ts";
3
+ import { assertNotResolved, callFlow, dispatchSlot, findIncident, logEvent } from "../shared.ts";
4
4
 
5
- export default dispatchSlot.updateTool({
5
+ /**
6
+ * Gated on `working`; sends nothing. A status update moves the INCIDENT, not
7
+ * the call — the dispatcher is still in `monitoring` afterwards, and closing
8
+ * one incident says nothing about the others still on the board. Omitting both
9
+ * `send` and `sendFrom` is how a flow tool reads without advancing, and the
10
+ * result still carries the position it read.
11
+ */
12
+ export default callFlow.tool({
6
13
  description: "Update an incident's status (en_route, on_scene, resolved, escalated).",
14
+ when: "working",
7
15
  inputSchema: z.object({
8
16
  incidentId: z.string().max(20).describe("The incident ID"),
9
17
  status: z.enum(["en_route", "on_scene", "resolved", "escalated"]).describe("New status"),
@@ -16,51 +24,52 @@ export default dispatchSlot.updateTool({
16
24
  .describe("Updated casualty numbers")
17
25
  .optional(),
18
26
  }),
19
- execute(args, state) {
20
- const inc = findIncident(state, args.incidentId);
21
- if (isToolFailure(inc)) return inc;
22
- const blocked = assertNotResolved(inc, `set status to ${args.status}`);
23
- if (blocked) return blocked;
27
+ execute: (args, ctx) =>
28
+ dispatchSlot.update(ctx, (state) => {
29
+ const inc = findIncident(state, args.incidentId);
30
+ if (isToolFailure(inc)) return inc;
31
+ const blocked = assertNotResolved(inc, `set status to ${args.status}`);
32
+ if (blocked) return blocked;
24
33
 
25
- inc.status = args.status;
26
- logEvent(inc, `Status → ${args.status}${args.notes ? `: ${args.notes}` : ""}`);
34
+ inc.status = args.status;
35
+ logEvent(inc, `Status → ${args.status}${args.notes ? `: ${args.notes}` : ""}`);
27
36
 
28
- if (args.casualtyUpdate) {
29
- if (args.casualtyUpdate.confirmed !== undefined) {
30
- inc.casualties.confirmed = args.casualtyUpdate.confirmed;
37
+ if (args.casualtyUpdate) {
38
+ if (args.casualtyUpdate.confirmed !== undefined) {
39
+ inc.casualties.confirmed = args.casualtyUpdate.confirmed;
40
+ }
41
+ if (args.casualtyUpdate.treated !== undefined) {
42
+ inc.casualties.treated = args.casualtyUpdate.treated;
43
+ }
31
44
  }
32
- if (args.casualtyUpdate.treated !== undefined) {
33
- inc.casualties.treated = args.casualtyUpdate.treated;
34
- }
35
- }
36
45
 
37
- // Only touch resources still assigned to THIS incident — a unit that
38
- // radioed available and was re-dispatched elsewhere belongs to its
39
- // new incident now.
40
- const ownResources = state.resources.filter((r) => r.assignedIncident === inc.id);
46
+ // Only touch resources still assigned to THIS incident — a unit that
47
+ // radioed available and was re-dispatched elsewhere belongs to its
48
+ // new incident now.
49
+ const ownResources = state.resources.filter((r) => r.assignedIncident === inc.id);
41
50
 
42
- if (args.status === "resolved") {
43
- for (const r of ownResources) {
44
- r.status = "returning";
45
- r.assignedIncident = null;
46
- r.eta = null;
51
+ if (args.status === "resolved") {
52
+ for (const r of ownResources) {
53
+ r.status = "returning";
54
+ r.assignedIncident = null;
55
+ r.eta = null;
56
+ }
57
+ inc.assignedResources = [];
58
+ logEvent(inc, "All resources released — incident closed");
47
59
  }
48
- inc.assignedResources = [];
49
- logEvent(inc, "All resources released — incident closed");
50
- }
51
60
 
52
- if (args.status === "en_route" || args.status === "on_scene") {
53
- for (const r of ownResources) {
54
- r.status = args.status;
55
- if (args.status === "on_scene") r.eta = null;
61
+ if (args.status === "en_route" || args.status === "on_scene") {
62
+ for (const r of ownResources) {
63
+ r.status = args.status;
64
+ if (args.status === "on_scene") r.eta = null;
65
+ }
56
66
  }
57
- }
58
67
 
59
- return {
60
- incidentId: args.incidentId,
61
- newStatus: args.status,
62
- timeline: inc.timeline.slice(-5).map((t) => t.event),
63
- casualties: inc.casualties,
64
- };
65
- },
68
+ return {
69
+ incidentId: args.incidentId,
70
+ newStatus: args.status,
71
+ timeline: inc.timeline.slice(-5).map((t) => t.event),
72
+ casualties: inc.casualties,
73
+ };
74
+ }),
66
75
  });
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import type { Incident, IncidentType, Severity } from "../shared.ts";
3
- import { calculateTriageScore, createIncident, dispatchSlot } from "../shared.ts";
3
+ import { calculateTriageScore, callFlow, createIncident, dispatchSlot } from "../shared.ts";
4
4
 
5
5
  type ScenarioIncident = Pick<Incident, "location" | "description" | "type" | "severity">;
6
6
  type ScenarioDef = { narrative: string; incidents: ScenarioIncident[] };
@@ -116,12 +116,15 @@ const scenarios = {
116
116
  type ScenarioName = keyof typeof scenarios;
117
117
  const SCENARIO_NAMES = Object.keys(scenarios) as [ScenarioName, ...ScenarioName[]];
118
118
 
119
+ /** Logs incidents like a real call does, so it sends `LOGGED` for the same
120
+ * reason `incident_create` does — and is ungated for the same reason too: a
121
+ * drill may be started at any point in a shift. */
119
122
  export default dispatchSlot.updateTool({
120
123
  description: "Run a training scenario that creates simulated incidents for dispatch practice.",
121
124
  inputSchema: z.object({
122
125
  scenario: z.enum(SCENARIO_NAMES).describe("Scenario type to simulate"),
123
126
  }),
124
- execute(args, state) {
127
+ execute(args, state, ctx) {
125
128
  const s = scenarios[args.scenario];
126
129
 
127
130
  const created: string[] = [];
@@ -140,6 +143,9 @@ export default dispatchSlot.updateTool({
140
143
  scenario: args.scenario,
141
144
  narrative: s.narrative,
142
145
  incidentsCreated: created,
146
+ // As `incident_create`: the position spread verbatim, so an ungated tool
147
+ // reports it under the same keys a gated one does.
148
+ ...callFlow.send(ctx, { type: "LOGGED" }),
143
149
  message: `SCENARIO ACTIVE: ${s.narrative}. ${created.length} incidents created. Awaiting dispatch orders.`,
144
150
  };
145
151
  },