@alexkroman1/aai-cli 5.14.0 → 6.1.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 (231) hide show
  1. package/dist/{_agent-C8Jk-ZcP.mjs → _agent-vGW5SBnX.mjs} +23 -5
  2. package/dist/_agent.d.ts +18 -0
  3. package/dist/{_api-client-BX8Qv51n.mjs → _api-client-LgLksMhN.mjs} +45 -8
  4. package/dist/_api-client.d.ts +39 -0
  5. package/dist/{_bundler-BuIyDdvs.mjs → _bundler-BapCUUJN.mjs} +42 -14
  6. package/dist/_bundler.d.ts +43 -5
  7. package/dist/_cli-common.d.ts +67 -3
  8. package/dist/{_config-CzwAWOWr.mjs → _config-D_s09e7g.mjs} +36 -18
  9. package/dist/_deploy.d.ts +2 -5
  10. package/dist/_dev-restart.d.ts +6 -1
  11. package/dist/{_dev-server-Ch7K8e8i.mjs → _dev-server-DQ1t9sbw.mjs} +94 -38
  12. package/dist/_dev-server.d.ts +30 -2
  13. package/dist/_e2e-test-utils.d.ts +80 -0
  14. package/dist/_fault-mode.d.ts +175 -0
  15. package/dist/{_init-CbkrE2wq.mjs → _init-CpISqoR8.mjs} +3 -3
  16. package/dist/{_output-CC300DzW.mjs → _output-CKkmWs7i.mjs} +8 -1
  17. package/dist/_output.d.ts +9 -2
  18. package/dist/{_server-common-61fL__39.mjs → _server-common-6e0QI4mq.mjs} +1 -1
  19. package/dist/{_slug-api-D30zPmsn.mjs → _slug-api-D2heILuC.mjs} +2 -2
  20. package/dist/{_studio-D2ciIhbi.mjs → _studio-zAJycZ8j.mjs} +3 -3
  21. package/dist/{_templates-B1kt3BGt.mjs → _templates-iDGjGiPf.mjs} +4 -6
  22. package/dist/{_typecheck-gate-4v9UBZHh.mjs → _typecheck-gate-BXvUNnfs.mjs} +1 -1
  23. package/dist/{_utils-0DHbcqUb.mjs → _utils-B8QmtFhK.mjs} +21 -7
  24. package/dist/_utils.d.ts +18 -2
  25. package/dist/{_vite-env-Dg_QlVv0.mjs → _vite-env-BNveawd1.mjs} +31 -1
  26. package/dist/_vite-env.d.ts +30 -0
  27. package/dist/build-BO1Ni6oJ.mjs +62 -0
  28. package/dist/build.d.ts +19 -0
  29. package/dist/cli-workflow.d.ts +10 -0
  30. package/dist/cli.d.ts +1 -2
  31. package/dist/cli.mjs +406 -224
  32. package/dist/{client-bundler-C9qXL8A2.mjs → client-bundler-B8Q9F8gJ.mjs} +3 -22
  33. package/dist/client-bundler.mjs +1 -1
  34. package/dist/{delete-DCRX3d96.mjs → delete-4I0uNR4a.mjs} +6 -7
  35. package/dist/delete.d.ts +2 -8
  36. package/dist/{deploy-DjsEV-40.mjs → deploy-DJ3HRcia.mjs} +13 -14
  37. package/dist/{dev-CvPH5dbF.mjs → dev-C6SWaAZP.mjs} +3 -3
  38. package/dist/{eject-LgBRfnbH.mjs → eject-Zrzk5KV3.mjs} +41 -15
  39. package/dist/eject.d.ts +30 -3
  40. package/dist/{init-BppxMR8r.mjs → init-BTRGiPQr.mjs} +61 -26
  41. package/dist/init.d.ts +11 -0
  42. package/dist/{login-BQQaxJhL.mjs → login-k0Z3VQu9.mjs} +5 -5
  43. package/dist/project-config.mjs +1 -1
  44. package/dist/scaffold/CLAUDE.md +498 -91
  45. package/dist/scaffold/package.json +11 -8
  46. package/dist/scaffold/pnpm-workspace.yaml +8 -0
  47. package/dist/scaffold/server.mjs +53 -57
  48. package/dist/{secret-Ds9dvqAj.mjs → secret-DzSQx1Ds.mjs} +6 -3
  49. package/dist/{storage-DbrQSHFk.mjs → storage-C-Do0mIA.mjs} +18 -6
  50. package/dist/{studio-GSFd4s5O.mjs → studio-BGVmCA2G.mjs} +10 -10
  51. package/dist/templates/code-interpreter/agent.ts +0 -2
  52. package/dist/templates/dispatch-center/agent.test.ts +17 -19
  53. package/dist/templates/dispatch-center/agent.ts +2 -29
  54. package/dist/templates/dispatch-center/client.tsx +33 -16
  55. package/dist/templates/dispatch-center/shared.ts +53 -19
  56. package/dist/templates/dispatch-center/tools/incident_add_note.ts +12 -14
  57. package/dist/templates/dispatch-center/tools/incident_create.ts +41 -44
  58. package/dist/templates/dispatch-center/tools/incident_escalate.ts +59 -61
  59. package/dist/templates/dispatch-center/tools/incident_get.ts +3 -4
  60. package/dist/templates/dispatch-center/tools/incident_triage.ts +43 -45
  61. package/dist/templates/dispatch-center/tools/incident_update_status.ts +39 -41
  62. package/dist/templates/dispatch-center/tools/ops_dashboard.ts +2 -5
  63. package/dist/templates/dispatch-center/tools/ops_protocols.ts +1 -1
  64. package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +20 -23
  65. package/dist/templates/dispatch-center/tools/resources_dispatch.ts +52 -54
  66. package/dist/templates/dispatch-center/tools/resources_get_available.ts +2 -4
  67. package/dist/templates/dispatch-center/tools/resources_update_status.ts +39 -42
  68. package/dist/templates/embedded-assets/agent.test.ts +83 -0
  69. package/dist/templates/embedded-assets/agent.ts +1 -48
  70. package/dist/templates/embedded-assets/shared.ts +17 -0
  71. package/dist/templates/embedded-assets/system-prompt.md +13 -0
  72. package/dist/templates/embedded-assets/tools/list_topics.ts +9 -0
  73. package/dist/templates/embedded-assets/tools/search_knowledge.ts +26 -0
  74. package/dist/templates/health-assistant/agent.test.ts +169 -0
  75. package/dist/templates/health-assistant/agent.ts +1 -166
  76. package/dist/templates/health-assistant/fda.ts +100 -0
  77. package/dist/templates/health-assistant/tools/check_drug_interaction.ts +59 -0
  78. package/dist/templates/health-assistant/tools/medication_lookup.ts +31 -0
  79. package/dist/templates/infocom-adventure/agent.test.ts +205 -0
  80. package/dist/templates/infocom-adventure/agent.ts +3 -106
  81. package/dist/templates/infocom-adventure/client.tsx +28 -12
  82. package/dist/templates/infocom-adventure/shared.ts +28 -11
  83. package/dist/templates/infocom-adventure/tools/game_state_drop.ts +13 -0
  84. package/dist/templates/infocom-adventure/tools/game_state_flag.ts +13 -0
  85. package/dist/templates/infocom-adventure/tools/game_state_get.ts +16 -0
  86. package/dist/templates/infocom-adventure/tools/game_state_history.ts +14 -0
  87. package/dist/templates/infocom-adventure/tools/game_state_move.ts +14 -0
  88. package/dist/templates/infocom-adventure/tools/game_state_restart.ts +13 -0
  89. package/dist/templates/infocom-adventure/tools/game_state_score.ts +13 -0
  90. package/dist/templates/infocom-adventure/tools/game_state_take.ts +13 -0
  91. package/dist/templates/link-digest/agent.test.ts +223 -0
  92. package/dist/templates/link-digest/agent.ts +79 -0
  93. package/dist/templates/link-digest/client.tsx +146 -0
  94. package/dist/templates/link-digest/workflows/digest.ts +248 -0
  95. package/dist/templates/math-buddy/agent.ts +0 -2
  96. package/dist/templates/night-owl/agent.test.ts +80 -0
  97. package/dist/templates/night-owl/agent.ts +1 -68
  98. package/dist/templates/night-owl/system-prompt.md +10 -0
  99. package/dist/templates/night-owl/tools/recommend.ts +64 -0
  100. package/dist/templates/personal-finance/agent.ts +0 -2
  101. package/dist/templates/pizza-ordering/agent.test.ts +52 -47
  102. package/dist/templates/pizza-ordering/agent.ts +9 -158
  103. package/dist/templates/pizza-ordering/shared.ts +35 -12
  104. package/dist/templates/pizza-ordering/tools/add_pizza.ts +31 -0
  105. package/dist/templates/pizza-ordering/tools/place_order.ts +22 -0
  106. package/dist/templates/pizza-ordering/tools/remove_pizza.ts +22 -0
  107. package/dist/templates/pizza-ordering/tools/set_customer_name.ts +13 -0
  108. package/dist/templates/pizza-ordering/tools/update_pizza.ts +28 -0
  109. package/dist/templates/pizza-ordering/tools/view_order.ts +21 -0
  110. package/dist/templates/plan-and-execute/agent.test.ts +411 -0
  111. package/dist/templates/plan-and-execute/agent.ts +23 -0
  112. package/dist/templates/plan-and-execute/client.tsx +115 -0
  113. package/dist/templates/plan-and-execute/graph.ts +178 -0
  114. package/dist/templates/plan-and-execute/prompts.ts +110 -0
  115. package/dist/templates/plan-and-execute/shared.ts +161 -0
  116. package/dist/templates/plan-and-execute/system-prompt.md +18 -0
  117. package/dist/templates/plan-and-execute/tools/plan_status.ts +20 -0
  118. package/dist/templates/plan-and-execute/tools/revise_plan.ts +63 -0
  119. package/dist/templates/plan-and-execute/tools/start_plan.ts +51 -0
  120. package/dist/templates/plan-and-execute/tools/work_next_step.ts +101 -0
  121. package/dist/templates/recap-workflow/agent.test.ts +762 -0
  122. package/dist/templates/recap-workflow/agent.ts +95 -0
  123. package/dist/templates/recap-workflow/shared.ts +43 -0
  124. package/dist/templates/recap-workflow/tools/cancel_recap.ts +23 -0
  125. package/dist/templates/recap-workflow/tools/keep_transcript.ts +31 -0
  126. package/dist/templates/recap-workflow/tools/recap_progress.ts +27 -0
  127. package/dist/templates/recap-workflow/tools/recap_status.ts +46 -0
  128. package/dist/templates/recap-workflow/tools/request_recap.ts +43 -0
  129. package/dist/templates/recap-workflow/workflows/recap.ts +544 -0
  130. package/dist/templates/recap-workflow/workflows/tokens.ts +26 -0
  131. package/dist/templates/redline/agent.test.ts +222 -0
  132. package/dist/templates/redline/agent.ts +89 -0
  133. package/dist/templates/redline/client.tsx +184 -0
  134. package/dist/templates/redline/workflows/prompts.ts +68 -0
  135. package/dist/templates/redline/workflows/redline.ts +258 -0
  136. package/dist/templates/research-workflow/agent.test.ts +525 -0
  137. package/dist/templates/research-workflow/agent.ts +94 -0
  138. package/dist/templates/research-workflow/shared.ts +35 -0
  139. package/dist/templates/research-workflow/tools/file_it_now.ts +22 -0
  140. package/dist/templates/research-workflow/tools/request_research.ts +33 -0
  141. package/dist/templates/research-workflow/tools/research_progress.ts +26 -0
  142. package/dist/templates/research-workflow/tools/research_status.ts +40 -0
  143. package/dist/templates/research-workflow/workflows/prompts.ts +143 -0
  144. package/dist/templates/research-workflow/workflows/research.ts +558 -0
  145. package/dist/templates/retail/agent.test.ts +236 -172
  146. package/dist/templates/retail/agent.ts +5 -42
  147. package/dist/templates/retail/client.tsx +33 -11
  148. package/dist/templates/retail/registry.test.ts +31 -16
  149. package/dist/templates/retail/resolve.ts +28 -64
  150. package/dist/templates/retail/shared.test.ts +4 -3
  151. package/dist/templates/retail/shared.ts +24 -7
  152. package/dist/templates/retail/store.test.ts +12 -10
  153. package/dist/templates/retail/store.ts +69 -30
  154. package/dist/templates/retail/tools/cancel_pending_order.ts +4 -9
  155. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -11
  156. package/dist/templates/retail/tools/find_user_id_by_email.ts +5 -9
  157. package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +5 -7
  158. package/dist/templates/retail/tools/get_item_details.ts +5 -7
  159. package/dist/templates/retail/tools/get_order_details.ts +5 -7
  160. package/dist/templates/retail/tools/get_product_details.ts +4 -7
  161. package/dist/templates/retail/tools/get_user_details.ts +4 -7
  162. package/dist/templates/retail/tools/list_all_product_types.ts +5 -7
  163. package/dist/templates/retail/tools/modify_pending_order_address.ts +4 -9
  164. package/dist/templates/retail/tools/modify_pending_order_items.ts +4 -16
  165. package/dist/templates/retail/tools/modify_pending_order_payment.ts +3 -9
  166. package/dist/templates/retail/tools/modify_user_address.ts +4 -9
  167. package/dist/templates/retail/tools/return_delivered_order_items.ts +3 -9
  168. package/dist/templates/retail/tools/transfer_to_human_agents.ts +1 -1
  169. package/dist/templates/solo-rpg/agent.test.ts +152 -29
  170. package/dist/templates/solo-rpg/agent.ts +3 -21
  171. package/dist/templates/solo-rpg/client.tsx +19 -6
  172. package/dist/templates/solo-rpg/shared.ts +19 -11
  173. package/dist/templates/solo-rpg/tools/action_roll.ts +2 -4
  174. package/dist/templates/solo-rpg/tools/burn_momentum.ts +2 -4
  175. package/dist/templates/solo-rpg/tools/check_state.ts +6 -6
  176. package/dist/templates/solo-rpg/tools/load_game.ts +1 -1
  177. package/dist/templates/solo-rpg/tools/oracle.ts +18 -11
  178. package/dist/templates/solo-rpg/tools/save_game.ts +10 -7
  179. package/dist/templates/solo-rpg/tools/setup_character.ts +1 -1
  180. package/dist/templates/solo-rpg/tools/update_state.ts +3 -4
  181. package/dist/templates/support-line/agent.test.ts +343 -0
  182. package/dist/templates/support-line/agent.ts +27 -0
  183. package/dist/templates/support-line/client.tsx +122 -0
  184. package/dist/templates/support-line/graph.ts +224 -0
  185. package/dist/templates/support-line/knowledge.json +65 -0
  186. package/dist/templates/support-line/prompts.ts +95 -0
  187. package/dist/templates/support-line/shared.ts +240 -0
  188. package/dist/templates/support-line/system-prompt.md +17 -0
  189. package/dist/templates/support-line/tools/answer_question.ts +72 -0
  190. package/dist/templates/support-line/tools/list_topics.ts +22 -0
  191. package/dist/templates/support-line/tools/log_ticket.ts +30 -0
  192. package/dist/templates/transcription-workflow/agent.test.ts +563 -0
  193. package/dist/templates/transcription-workflow/agent.ts +92 -0
  194. package/dist/templates/transcription-workflow/client.tsx +234 -0
  195. package/dist/templates/transcription-workflow/workflows/transcribe.ts +416 -0
  196. package/dist/templates/transcription-workflow/workflows/wav.ts +314 -0
  197. package/dist/templates/travel-concierge/agent.test.ts +338 -0
  198. package/dist/templates/travel-concierge/agent.ts +29 -0
  199. package/dist/templates/travel-concierge/client.tsx +161 -0
  200. package/dist/templates/travel-concierge/routing.ts +138 -0
  201. package/dist/templates/travel-concierge/shared.ts +547 -0
  202. package/dist/templates/travel-concierge/system-prompt.md +24 -0
  203. package/dist/templates/travel-concierge/tools/book_car_rental.ts +20 -0
  204. package/dist/templates/travel-concierge/tools/book_excursion.ts +18 -0
  205. package/dist/templates/travel-concierge/tools/book_hotel.ts +20 -0
  206. package/dist/templates/travel-concierge/tools/cancel_action.ts +7 -0
  207. package/dist/templates/travel-concierge/tools/cancel_ticket.ts +16 -0
  208. package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +7 -0
  209. package/dist/templates/travel-concierge/tools/confirm_action.ts +7 -0
  210. package/dist/templates/travel-concierge/tools/lookup_booking.ts +35 -0
  211. package/dist/templates/travel-concierge/tools/search_car_rentals.ts +33 -0
  212. package/dist/templates/travel-concierge/tools/search_excursions.ts +37 -0
  213. package/dist/templates/travel-concierge/tools/search_flights.ts +43 -0
  214. package/dist/templates/travel-concierge/tools/search_hotels.ts +36 -0
  215. package/dist/templates/travel-concierge/tools/to_car_rental_assistant.ts +11 -0
  216. package/dist/templates/travel-concierge/tools/to_excursion_assistant.ts +11 -0
  217. package/dist/templates/travel-concierge/tools/to_flight_assistant.ts +11 -0
  218. package/dist/templates/travel-concierge/tools/to_hotel_assistant.ts +11 -0
  219. package/dist/templates/travel-concierge/tools/update_ticket.ts +22 -0
  220. package/dist/templates/web-researcher/agent.ts +0 -2
  221. package/dist/templates/web-researcher/system-prompt.md +10 -0
  222. package/dist/{test-2LWBcXvY.mjs → test-B6ZBxSpk.mjs} +2 -2
  223. package/dist/typecheck.mjs +17 -15
  224. package/dist/worker-bundler-CIdDhZ0c.mjs +398 -0
  225. package/dist/worker-bundler.d.ts +14 -0
  226. package/dist/worker-bundler.mjs +1 -108
  227. package/dist/workflow-B-O97AqI.mjs +125 -0
  228. package/dist/workflow-bundler.d.ts +133 -0
  229. package/dist/workflow.d.ts +67 -0
  230. package/package.json +13 -9
  231. package/dist/build-DQJ1ZSm9.mjs +0 -39
@@ -1,7 +1,7 @@
1
1
  // ─── Types ───────────────────────────────────────────────────────────────────
2
2
 
3
- import type { ToolFailure } from "@alexkroman1/aai";
4
- import { pushCapped, type SlotStateOf, sessionSlot } from "@alexkroman1/aai";
3
+ import type { DeepReadonly, ToolFailure } from "@alexkroman1/aai";
4
+ import { 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];
@@ -101,7 +101,7 @@ export interface IncidentSummary {
101
101
  location: string;
102
102
  }
103
103
 
104
- export function incidentSummary(inc: Incident): IncidentSummary {
104
+ export function incidentSummary(inc: DeepReadonly<Incident>): IncidentSummary {
105
105
  return { id: inc.id, severity: inc.severity, status: inc.status, location: inc.location };
106
106
  }
107
107
 
@@ -114,7 +114,7 @@ export interface DashboardView {
114
114
  /** The `syncState` projection — the whole contract with client.tsx. Takes the
115
115
  * board itself, not the slot: `dispatchSlot.projection` supplies a real one
116
116
  * even before the first tool call. */
117
- export function dashboardView(state: DispatchState): DashboardView {
117
+ export function dashboardView(state: FrozenDispatchState): DashboardView {
118
118
  return {
119
119
  systemAlertLevel: state.alertLevel,
120
120
  incidents: Object.values(state.incidents).map(incidentSummary),
@@ -162,9 +162,9 @@ export function createDefaultState(): DispatchState {
162
162
  }
163
163
 
164
164
  // ─── State helpers ───────────────────────────────────────────────────────────
165
- // The dispatch board lives in `ctx.state`, the agent's per-session mutable
166
- // state — sessions must not see each other's incidents, and ctx.state gives
167
- // that isolation by construction. Nothing here needs to outlive the session.
165
+ // The dispatch board lives in one `sessionSlot`, keyed per session — sessions
166
+ // must not see each other's incidents, and a slot gives that isolation by
167
+ // construction.
168
168
 
169
169
  /**
170
170
  * Growth caps. The whole dispatch state is one object whose summaries feed
@@ -186,12 +186,22 @@ function pruneState(state: DispatchState): void {
186
186
  }
187
187
 
188
188
  /**
189
- * The session's dispatch board, as one typed slot inside `ctx.state`.
189
+ * The session's dispatch board, as one typed slot.
190
190
  *
191
- * Mutating tools go through `dispatchSlot.update`, which serializes them per
192
- * session: the LLM loop runs a step's tool calls concurrently, and two
193
- * interleaving async mutators would each observe the other's half-applied
194
- * changes.
191
+ * Every tool here is declared through the slot rather than reaching for it from
192
+ * inside a `tool()` body, and WHICH HALF is the whole decision: `updateTool`
193
+ * runs the body inside the mutation window and hands it a draft that is stored
194
+ * when the body returns, `tool` hands it the deep-frozen stored value. That
195
+ * makes "does this tool write?" a property of the declaration a reader can see,
196
+ * and choosing wrong a compile error rather than a `TypeError` on the first
197
+ * call.
198
+ *
199
+ * The window is SYNCHRONOUS, which is what makes a read-modify-write atomic
200
+ * with no lock — the LLM loop runs a step's tool calls concurrently. It does
201
+ * NOT serialize anything (an earlier version of this comment claimed it did):
202
+ * there is nothing to serialize, because no other JS turn can interleave with
203
+ * a synchronous body. `updateTool` is what enforces the rule, refusing a
204
+ * thenable body by name.
195
205
  *
196
206
  * `after` is the bookkeeping every mutating tool needs and none of them should
197
207
  * have to remember — pruning resolved incidents, and recalculating the alert
@@ -205,7 +215,16 @@ export const dispatchSlot = sessionSlot("dispatch", createDefaultState, {
205
215
  },
206
216
  });
207
217
 
208
- export type StateSlot = SlotStateOf<typeof dispatchSlot>;
218
+ /**
219
+ * The board as a READ hands it out: deep-frozen, and typed to say so.
220
+ *
221
+ * The pure helpers below take this rather than {@link DispatchState}, which is
222
+ * the widening a deep-readonly slot forces and the reason it is worth doing: a
223
+ * mutable board still satisfies it, so a helper called with an `updateTool`
224
+ * draft is unaffected, while a helper that WOULD have mutated stops compiling
225
+ * instead of throwing at its first call in production.
226
+ */
227
+ export type FrozenDispatchState = DeepReadonly<DispatchState>;
209
228
 
210
229
  // ─── Incident helpers ────────────────────────────────────────────────────────
211
230
 
@@ -237,7 +256,20 @@ export function createIncident(state: DispatchState, overrides: Partial<Incident
237
256
  return incident;
238
257
  }
239
258
 
240
- export function findIncident(state: DispatchState, incidentId: string): Incident | ToolFailure {
259
+ /**
260
+ * One incident by id, or the failure the model should read.
261
+ *
262
+ * Generic over the incident's own type rather than pinned to {@link Incident},
263
+ * so ONE lookup serves both halves of the slot: a mutating tool passes its
264
+ * draft and gets a mutable incident back to change, while `incident_get` passes
265
+ * what `dispatchSlot.tool` handed it — deep-frozen — and gets a readonly one.
266
+ * Pinning it would have made the read path the odd one out, and its way out
267
+ * would have been a cast.
268
+ */
269
+ export function findIncident<I extends DeepReadonly<Incident>>(
270
+ state: { readonly incidents: { readonly [id: string]: I } },
271
+ incidentId: string,
272
+ ): I | ToolFailure {
241
273
  return state.incidents[incidentId] ?? { error: `Incident ${incidentId} not found` };
242
274
  }
243
275
 
@@ -251,17 +283,19 @@ export function logEvent(inc: Incident, event: string): void {
251
283
  }
252
284
 
253
285
  /** Minutes since the incident was created, rounded. */
254
- export function incidentAgeMinutes(inc: Incident): number {
286
+ export function incidentAgeMinutes(inc: DeepReadonly<Incident>): number {
255
287
  return Math.round((Date.now() - inc.createdAt) / 60_000);
256
288
  }
257
289
 
258
290
  /** A resource as tool results describe it to the LLM. */
259
- export function resourceBrief(r: Resource): {
291
+ export function resourceBrief(r: DeepReadonly<Resource>): {
260
292
  callsign: string;
261
293
  type: Resource["type"];
262
294
  capabilities: string[];
263
295
  } {
264
- return { callsign: r.callsign, type: r.type, capabilities: r.capabilities };
296
+ // `capabilities` is COPIED: a brief is a fresh object handed to the LLM, and
297
+ // the source list belongs either to a frozen slot value or to a live draft.
298
+ return { callsign: r.callsign, type: r.type, capabilities: [...r.capabilities] };
265
299
  }
266
300
 
267
301
  /**
@@ -269,7 +303,7 @@ export function resourceBrief(r: Resource): {
269
303
  * resources have been released (and possibly reassigned), so escalating,
270
304
  * re-resolving, or dispatching to it would corrupt resource assignments.
271
305
  */
272
- export function assertNotResolved(inc: Incident, action: string): ToolFailure | null {
306
+ export function assertNotResolved(inc: DeepReadonly<Incident>, action: string): ToolFailure | null {
273
307
  if (inc.status === "resolved") {
274
308
  return {
275
309
  error: `Incident ${inc.id} is resolved — cannot ${action}. Create a new incident if the situation has reopened.`,
@@ -575,7 +609,7 @@ export function recommendResources(
575
609
  /** Fraction of resources not currently available, 0–1. The alert level and
576
610
  * the dashboard's displayed utilization both derive from this, so they can
577
611
  * never disagree about what "utilization" means. */
578
- export function resourceUtilization(state: DispatchState): number {
612
+ export function resourceUtilization(state: FrozenDispatchState): number {
579
613
  const available = state.resources.filter((r) => r.status === "available").length;
580
614
  return 1 - available / state.resources.length;
581
615
  }
@@ -1,27 +1,25 @@
1
- import { isToolFailure, tool } from "@alexkroman1/aai";
1
+ import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
3
  import { dispatchSlot, findIncident, logEvent } from "../shared.ts";
4
4
 
5
- export const incidentAddNote = tool({
5
+ export default dispatchSlot.updateTool({
6
6
  description: "Add a situational update note to an incident's timeline.",
7
7
  inputSchema: z.object({
8
8
  incidentId: z.string().max(20).describe("The incident ID"),
9
9
  note: z.string().max(1000).describe("The note to add"),
10
10
  source: z.string().max(100).describe("Who reported this — unit callsign or caller").optional(),
11
11
  }),
12
- async execute(args, ctx) {
13
- return dispatchSlot.update(ctx, (state) => {
14
- const inc = findIncident(state, args.incidentId);
15
- if (isToolFailure(inc)) return inc;
12
+ execute(args, state) {
13
+ const inc = findIncident(state, args.incidentId);
14
+ if (isToolFailure(inc)) return inc;
16
15
 
17
- const entry = args.source ? `[${args.source}] ${args.note}` : args.note;
18
- logEvent(inc, entry);
16
+ const entry = args.source ? `[${args.source}] ${args.note}` : args.note;
17
+ logEvent(inc, entry);
19
18
 
20
- return {
21
- incidentId: args.incidentId,
22
- noteAdded: entry,
23
- timelineEntries: inc.timeline.length,
24
- };
25
- });
19
+ return {
20
+ incidentId: args.incidentId,
21
+ noteAdded: entry,
22
+ timelineEntries: inc.timeline.length,
23
+ };
26
24
  },
27
25
  });
@@ -1,4 +1,3 @@
1
- import { tool } from "@alexkroman1/aai";
2
1
  import { z } from "zod";
3
2
  import {
4
3
  calculateTriageScore,
@@ -11,7 +10,7 @@ import {
11
10
  resourceBrief,
12
11
  } from "../shared.ts";
13
12
 
14
- export const incidentCreate = tool({
13
+ export default dispatchSlot.updateTool({
15
14
  description: "Create a new incident from an incoming emergency call.",
16
15
  inputSchema: z.object({
17
16
  location: z.string().max(300).describe("Address or location description"),
@@ -30,50 +29,48 @@ export const incidentCreate = tool({
30
29
  .describe("Known hazards: fire, chemical, electrical, structural, weapons")
31
30
  .optional(),
32
31
  }),
33
- async execute(args, ctx) {
34
- return dispatchSlot.update(ctx, (state) => {
35
- const recSeverity = recommendSeverity(args.description);
36
- const recType = recommendType(args.description);
37
- const triageScore = calculateTriageScore(
38
- recSeverity,
39
- recType,
40
- args.estimatedCasualties ?? 0,
41
- args.hazards?.length ?? 0,
42
- );
32
+ execute(args, state) {
33
+ const recSeverity = recommendSeverity(args.description);
34
+ const recType = recommendType(args.description);
35
+ const triageScore = calculateTriageScore(
36
+ recSeverity,
37
+ recType,
38
+ args.estimatedCasualties ?? 0,
39
+ args.hazards?.length ?? 0,
40
+ );
43
41
 
44
- const incident = createIncident(state, {
45
- type: recType,
46
- severity: recSeverity,
47
- location: args.location,
48
- description: args.description,
49
- callerName: args.callerName ?? "Unknown",
50
- callerPhone: args.callerPhone ?? "Unknown",
51
- triageScore,
52
- timeline: [{ time: Date.now(), event: `Incident created: ${args.description}` }],
53
- casualties: {
54
- confirmed: 0,
55
- estimated: args.estimatedCasualties ?? 0,
56
- treated: 0,
57
- },
58
- hazards: args.hazards ?? [],
59
- });
60
- const id = incident.id;
42
+ const incident = createIncident(state, {
43
+ type: recType,
44
+ severity: recSeverity,
45
+ location: args.location,
46
+ description: args.description,
47
+ callerName: args.callerName ?? "Unknown",
48
+ callerPhone: args.callerPhone ?? "Unknown",
49
+ triageScore,
50
+ timeline: [{ time: Date.now(), event: `Incident created: ${args.description}` }],
51
+ casualties: {
52
+ confirmed: 0,
53
+ estimated: args.estimatedCasualties ?? 0,
54
+ treated: 0,
55
+ },
56
+ hazards: args.hazards ?? [],
57
+ });
58
+ const id = incident.id;
61
59
 
62
- const protocols = getApplicableProtocols(recType, recSeverity);
63
- const recommended = recommendResources(recType, recSeverity, state);
60
+ const protocols = getApplicableProtocols(recType, recSeverity);
61
+ const recommended = recommendResources(recType, recSeverity, state);
64
62
 
65
- return {
66
- incidentId: id,
67
- recommendedSeverity: recSeverity,
68
- recommendedType: recType,
69
- triageScore,
70
- applicableProtocols: protocols.map((p) => p.name),
71
- recommendedResources: recommended.map(resourceBrief),
72
- message:
73
- recSeverity === "critical"
74
- ? `PRIORITY ONE — ${id} created. Immediate dispatch recommended. ${protocols.length} protocol(s) applicable.`
75
- : `${id} created. Triage score ${triageScore}. ${recommended.length} resource(s) recommended.`,
76
- };
77
- });
63
+ return {
64
+ incidentId: id,
65
+ recommendedSeverity: recSeverity,
66
+ recommendedType: recType,
67
+ triageScore,
68
+ applicableProtocols: protocols.map((p) => p.name),
69
+ recommendedResources: recommended.map(resourceBrief),
70
+ message:
71
+ recSeverity === "critical"
72
+ ? `PRIORITY ONE — ${id} created. Immediate dispatch recommended. ${protocols.length} protocol(s) applicable.`
73
+ : `${id} created. Triage score ${triageScore}. ${recommended.length} resource(s) recommended.`,
74
+ };
78
75
  },
79
76
  });
@@ -1,4 +1,4 @@
1
- import { isToolFailure, tool } from "@alexkroman1/aai";
1
+ import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
3
  import {
4
4
  assertNotResolved,
@@ -9,7 +9,7 @@ import {
9
9
  recommendResources,
10
10
  } from "../shared.ts";
11
11
 
12
- export const incidentEscalate = tool({
12
+ export default dispatchSlot.updateTool({
13
13
  description: "Escalate an incident when it exceeds current capacity or severity increases.",
14
14
  inputSchema: z.object({
15
15
  incidentId: z.string().max(20).describe("The incident ID"),
@@ -20,69 +20,67 @@ export const incidentEscalate = tool({
20
20
  .optional(),
21
21
  newSeverity: z.enum(["critical", "urgent"]).describe("Escalated severity level").optional(),
22
22
  }),
23
- async execute(args, ctx) {
24
- return dispatchSlot.update(ctx, (state) => {
25
- const inc = findIncident(state, args.incidentId);
26
- if (isToolFailure(inc)) return inc;
27
- const blocked = assertNotResolved(inc, "escalate");
28
- if (blocked) return blocked;
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;
29
28
 
30
- inc.escalationLevel++;
31
- if (args.newSeverity) inc.severity = args.newSeverity;
32
- inc.status = "escalated";
33
- logEvent(inc, `ESCALATED (Level ${inc.escalationLevel}): ${args.reason}`);
29
+ inc.escalationLevel++;
30
+ if (args.newSeverity) inc.severity = args.newSeverity;
31
+ inc.status = "escalated";
32
+ logEvent(inc, `ESCALATED (Level ${inc.escalationLevel}): ${args.reason}`);
34
33
 
35
- if (args.requestMutualAid) {
36
- logEvent(inc, "Mutual aid requested from neighboring jurisdictions");
37
- // Counter-based ids/callsigns: Date.now() collides across rapid
38
- // escalations, and duplicate callsigns break dispatch-by-callsign.
39
- const medicN = ++state.mutualAidCounter;
40
- const engineN = ++state.mutualAidCounter;
41
- state.resources.push(
42
- {
43
- id: `MA-${medicN}`,
44
- type: "ambulance",
45
- callsign: `Mutual-Aid-Medic-${medicN}`,
46
- status: "available",
47
- assignedIncident: null,
48
- eta: null,
49
- capabilities: ["als"],
50
- },
51
- {
52
- id: `MA-${engineN}`,
53
- type: "fire_engine",
54
- callsign: `Mutual-Aid-Engine-${engineN}`,
55
- status: "available",
56
- assignedIncident: null,
57
- eta: null,
58
- capabilities: ["structural"],
59
- },
60
- );
61
- }
62
-
63
- inc.triageScore = calculateTriageScore(
64
- inc.severity,
65
- inc.type,
66
- inc.casualties.estimated,
67
- inc.hazards.length,
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
+ },
68
59
  );
60
+ }
69
61
 
70
- const additionalResources = recommendResources(inc.type, inc.severity, state).filter(
71
- (r) => !inc.assignedResources.includes(r.id),
72
- );
62
+ inc.triageScore = calculateTriageScore(
63
+ inc.severity,
64
+ inc.type,
65
+ inc.casualties.estimated,
66
+ inc.hazards.length,
67
+ );
68
+
69
+ const additionalResources = recommendResources(inc.type, inc.severity, state).filter(
70
+ (r) => !inc.assignedResources.includes(r.id),
71
+ );
73
72
 
74
- return {
75
- incidentId: args.incidentId,
76
- escalationLevel: inc.escalationLevel,
77
- newSeverity: inc.severity,
78
- newTriageScore: inc.triageScore,
79
- mutualAidRequested: args.requestMutualAid ?? false,
80
- additionalResourcesAvailable: additionalResources.map((r) => ({
81
- callsign: r.callsign,
82
- type: r.type,
83
- })),
84
- message: `ESCALATION CONFIRMED — ${args.incidentId} now Level ${inc.escalationLevel}. ${additionalResources.length} additional resource(s) available for dispatch.`,
85
- };
86
- });
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
+ };
87
85
  },
88
86
  });
@@ -1,4 +1,4 @@
1
- import { isToolFailure, tool } from "@alexkroman1/aai";
1
+ import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
3
  import {
4
4
  dispatchSlot,
@@ -7,13 +7,12 @@ import {
7
7
  incidentAgeMinutes,
8
8
  } from "../shared.ts";
9
9
 
10
- export const incidentGet = tool({
10
+ export default dispatchSlot.tool({
11
11
  description: "Get full details on a specific incident including timeline and assigned resources.",
12
12
  inputSchema: z.object({
13
13
  incidentId: z.string().max(20).describe("The incident ID"),
14
14
  }),
15
- async execute(args, ctx) {
16
- const state = dispatchSlot.get(ctx);
15
+ execute(args, state) {
17
16
  const inc = findIncident(state, args.incidentId);
18
17
  if (isToolFailure(inc)) return inc;
19
18
 
@@ -1,4 +1,4 @@
1
- import { isToolFailure, tool } from "@alexkroman1/aai";
1
+ import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
3
  import {
4
4
  assertNotResolved,
@@ -12,7 +12,7 @@ import {
12
12
  SEVERITIES,
13
13
  } from "../shared.ts";
14
14
 
15
- export const incidentTriage = tool({
15
+ export default dispatchSlot.updateTool({
16
16
  description:
17
17
  "Triage an incident — confirm or override severity, type, hazards, and casualty count.",
18
18
  inputSchema: z.object({
@@ -27,52 +27,50 @@ export const incidentTriage = tool({
27
27
  casualtyUpdate: z.number().int().nonnegative().describe("Updated casualty count").optional(),
28
28
  notes: z.string().max(1000).describe("Triage notes").optional(),
29
29
  }),
30
- async execute(args, ctx) {
31
- return dispatchSlot.update(ctx, (state) => {
32
- const inc = findIncident(state, args.incidentId);
33
- if (isToolFailure(inc)) return inc;
34
- const blocked = assertNotResolved(inc, "triage");
35
- if (blocked) return blocked;
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;
36
35
 
37
- if (args.severity) inc.severity = args.severity;
38
- if (args.type) inc.type = args.type;
39
- for (const hazard of args.additionalHazards ?? []) {
40
- if (!inc.hazards.includes(hazard)) inc.hazards.push(hazard);
41
- }
42
- if (args.casualtyUpdate !== undefined) {
43
- inc.casualties.estimated = args.casualtyUpdate;
44
- }
45
- if (args.notes) logEvent(inc, `Triage note: ${args.notes}`);
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}`);
46
45
 
47
- inc.triageScore = calculateTriageScore(
48
- inc.severity,
49
- inc.type,
50
- inc.casualties.estimated,
51
- inc.hazards.length,
52
- );
53
- inc.status = "triaged";
54
- logEvent(inc, `Triaged: ${inc.severity} ${inc.type}, score ${inc.triageScore}`);
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}`);
55
54
 
56
- const protocols = getApplicableProtocols(inc.type, inc.severity);
57
- const recommended = recommendResources(inc.type, inc.severity, state);
55
+ const protocols = getApplicableProtocols(inc.type, inc.severity);
56
+ const recommended = recommendResources(inc.type, inc.severity, state);
58
57
 
59
- return {
60
- incidentId: args.incidentId,
61
- severity: inc.severity,
62
- type: inc.type,
63
- triageScore: inc.triageScore,
64
- hazards: inc.hazards,
65
- estimatedCasualties: inc.casualties.estimated,
66
- protocols: protocols.map((p) => ({
67
- name: p.name,
68
- steps: p.steps,
69
- requiredResources: p.requiredResources,
70
- })),
71
- recommendedResources: recommended.map((r) => ({
72
- callsign: r.callsign,
73
- type: r.type,
74
- })),
75
- };
76
- });
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
+ };
77
75
  },
78
76
  });