@alexkroman1/aai-cli 5.13.2 → 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.
- package/dist/{_agent-C8Jk-ZcP.mjs → _agent-vGW5SBnX.mjs} +23 -5
- package/dist/_agent.d.ts +18 -0
- package/dist/{_api-client-BX8Qv51n.mjs → _api-client-LgLksMhN.mjs} +45 -8
- package/dist/_api-client.d.ts +39 -0
- package/dist/{_bundler-BuIyDdvs.mjs → _bundler-BapCUUJN.mjs} +42 -14
- package/dist/_bundler.d.ts +43 -5
- package/dist/_cli-common.d.ts +67 -3
- package/dist/{_config-CzwAWOWr.mjs → _config-D_s09e7g.mjs} +36 -18
- package/dist/_deploy.d.ts +2 -5
- package/dist/_dev-restart.d.ts +6 -1
- package/dist/{_dev-server-Ch7K8e8i.mjs → _dev-server-DQ1t9sbw.mjs} +94 -38
- package/dist/_dev-server.d.ts +30 -2
- package/dist/_e2e-test-utils.d.ts +80 -0
- package/dist/_fault-mode.d.ts +175 -0
- package/dist/{_init-CbkrE2wq.mjs → _init-CpISqoR8.mjs} +3 -3
- package/dist/{_output-CC300DzW.mjs → _output-CKkmWs7i.mjs} +8 -1
- package/dist/_output.d.ts +9 -2
- package/dist/{_server-common-61fL__39.mjs → _server-common-6e0QI4mq.mjs} +1 -1
- package/dist/{_slug-api-D30zPmsn.mjs → _slug-api-D2heILuC.mjs} +2 -2
- package/dist/{_studio-D2ciIhbi.mjs → _studio-zAJycZ8j.mjs} +3 -3
- package/dist/{_templates-B1kt3BGt.mjs → _templates-iDGjGiPf.mjs} +4 -6
- package/dist/{_typecheck-gate-4v9UBZHh.mjs → _typecheck-gate-BXvUNnfs.mjs} +1 -1
- package/dist/{_utils-0DHbcqUb.mjs → _utils-B8QmtFhK.mjs} +21 -7
- package/dist/_utils.d.ts +18 -2
- package/dist/{_vite-env-Dg_QlVv0.mjs → _vite-env-BNveawd1.mjs} +31 -1
- package/dist/_vite-env.d.ts +30 -0
- package/dist/build-BO1Ni6oJ.mjs +62 -0
- package/dist/build.d.ts +19 -0
- package/dist/cli-workflow.d.ts +10 -0
- package/dist/cli.d.ts +1 -2
- package/dist/cli.mjs +406 -224
- package/dist/{client-bundler-C9qXL8A2.mjs → client-bundler-B8Q9F8gJ.mjs} +3 -22
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-DCRX3d96.mjs → delete-4I0uNR4a.mjs} +6 -7
- package/dist/delete.d.ts +2 -8
- package/dist/{deploy-DjsEV-40.mjs → deploy-DJ3HRcia.mjs} +13 -14
- package/dist/{dev-CvPH5dbF.mjs → dev-C6SWaAZP.mjs} +3 -3
- package/dist/{eject-LgBRfnbH.mjs → eject-Zrzk5KV3.mjs} +41 -15
- package/dist/eject.d.ts +30 -3
- package/dist/{init-BppxMR8r.mjs → init-BTRGiPQr.mjs} +61 -26
- package/dist/init.d.ts +11 -0
- package/dist/{login-BQQaxJhL.mjs → login-k0Z3VQu9.mjs} +5 -5
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +498 -91
- package/dist/scaffold/package.json +11 -8
- package/dist/scaffold/pnpm-workspace.yaml +32 -0
- package/dist/scaffold/server.mjs +53 -57
- package/dist/{secret-Ds9dvqAj.mjs → secret-DzSQx1Ds.mjs} +6 -3
- package/dist/{storage-DbrQSHFk.mjs → storage-C-Do0mIA.mjs} +18 -6
- package/dist/{studio-GSFd4s5O.mjs → studio-BGVmCA2G.mjs} +10 -10
- package/dist/templates/code-interpreter/agent.ts +0 -2
- package/dist/templates/dispatch-center/agent.test.ts +21 -30
- package/dist/templates/dispatch-center/agent.ts +4 -31
- package/dist/templates/dispatch-center/client.tsx +41 -28
- package/dist/templates/dispatch-center/shared.ts +73 -66
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +13 -15
- package/dist/templates/dispatch-center/tools/incident_create.ts +42 -45
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +60 -62
- package/dist/templates/dispatch-center/tools/incident_get.ts +10 -6
- package/dist/templates/dispatch-center/tools/incident_triage.ts +44 -46
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +40 -42
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +3 -6
- package/dist/templates/dispatch-center/tools/ops_protocols.ts +1 -1
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +21 -24
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +53 -55
- package/dist/templates/dispatch-center/tools/resources_get_available.ts +3 -5
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +40 -43
- package/dist/templates/embedded-assets/agent.test.ts +83 -0
- package/dist/templates/embedded-assets/agent.ts +1 -48
- package/dist/templates/embedded-assets/shared.ts +17 -0
- package/dist/templates/embedded-assets/system-prompt.md +13 -0
- package/dist/templates/embedded-assets/tools/list_topics.ts +9 -0
- package/dist/templates/embedded-assets/tools/search_knowledge.ts +26 -0
- package/dist/templates/health-assistant/agent.test.ts +169 -0
- package/dist/templates/health-assistant/agent.ts +1 -166
- package/dist/templates/health-assistant/fda.ts +100 -0
- package/dist/templates/health-assistant/tools/check_drug_interaction.ts +59 -0
- package/dist/templates/health-assistant/tools/medication_lookup.ts +31 -0
- package/dist/templates/infocom-adventure/agent.test.ts +205 -0
- package/dist/templates/infocom-adventure/agent.ts +3 -106
- package/dist/templates/infocom-adventure/client.tsx +32 -28
- package/dist/templates/infocom-adventure/shared.ts +36 -16
- package/dist/templates/infocom-adventure/tools/game_state_drop.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_flag.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_get.ts +16 -0
- package/dist/templates/infocom-adventure/tools/game_state_history.ts +14 -0
- package/dist/templates/infocom-adventure/tools/game_state_move.ts +14 -0
- package/dist/templates/infocom-adventure/tools/game_state_restart.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_score.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_take.ts +13 -0
- package/dist/templates/link-digest/agent.test.ts +223 -0
- package/dist/templates/link-digest/agent.ts +79 -0
- package/dist/templates/link-digest/client.tsx +146 -0
- package/dist/templates/link-digest/workflows/digest.ts +248 -0
- package/dist/templates/math-buddy/agent.ts +0 -2
- package/dist/templates/night-owl/agent.test.ts +80 -0
- package/dist/templates/night-owl/agent.ts +1 -68
- package/dist/templates/night-owl/system-prompt.md +10 -0
- package/dist/templates/night-owl/tools/recommend.ts +64 -0
- package/dist/templates/personal-finance/agent.ts +0 -2
- package/dist/templates/pizza-ordering/agent.test.ts +59 -58
- package/dist/templates/pizza-ordering/agent.ts +10 -159
- package/dist/templates/pizza-ordering/client.tsx +4 -4
- package/dist/templates/pizza-ordering/shared.ts +44 -27
- package/dist/templates/pizza-ordering/tools/add_pizza.ts +31 -0
- package/dist/templates/pizza-ordering/tools/place_order.ts +22 -0
- package/dist/templates/pizza-ordering/tools/remove_pizza.ts +22 -0
- package/dist/templates/pizza-ordering/tools/set_customer_name.ts +13 -0
- package/dist/templates/pizza-ordering/tools/update_pizza.ts +28 -0
- package/dist/templates/pizza-ordering/tools/view_order.ts +21 -0
- package/dist/templates/plan-and-execute/agent.test.ts +411 -0
- package/dist/templates/plan-and-execute/agent.ts +23 -0
- package/dist/templates/plan-and-execute/client.tsx +115 -0
- package/dist/templates/plan-and-execute/graph.ts +178 -0
- package/dist/templates/plan-and-execute/prompts.ts +110 -0
- package/dist/templates/plan-and-execute/shared.ts +161 -0
- package/dist/templates/plan-and-execute/system-prompt.md +18 -0
- package/dist/templates/plan-and-execute/tools/plan_status.ts +20 -0
- package/dist/templates/plan-and-execute/tools/revise_plan.ts +63 -0
- package/dist/templates/plan-and-execute/tools/start_plan.ts +51 -0
- package/dist/templates/plan-and-execute/tools/work_next_step.ts +101 -0
- package/dist/templates/recap-workflow/agent.test.ts +762 -0
- package/dist/templates/recap-workflow/agent.ts +95 -0
- package/dist/templates/recap-workflow/shared.ts +43 -0
- package/dist/templates/recap-workflow/tools/cancel_recap.ts +23 -0
- package/dist/templates/recap-workflow/tools/keep_transcript.ts +31 -0
- package/dist/templates/recap-workflow/tools/recap_progress.ts +27 -0
- package/dist/templates/recap-workflow/tools/recap_status.ts +46 -0
- package/dist/templates/recap-workflow/tools/request_recap.ts +43 -0
- package/dist/templates/recap-workflow/workflows/recap.ts +544 -0
- package/dist/templates/recap-workflow/workflows/tokens.ts +26 -0
- package/dist/templates/redline/agent.test.ts +222 -0
- package/dist/templates/redline/agent.ts +89 -0
- package/dist/templates/redline/client.tsx +184 -0
- package/dist/templates/redline/workflows/prompts.ts +68 -0
- package/dist/templates/redline/workflows/redline.ts +258 -0
- package/dist/templates/research-workflow/agent.test.ts +525 -0
- package/dist/templates/research-workflow/agent.ts +94 -0
- package/dist/templates/research-workflow/shared.ts +35 -0
- package/dist/templates/research-workflow/tools/file_it_now.ts +22 -0
- package/dist/templates/research-workflow/tools/request_research.ts +33 -0
- package/dist/templates/research-workflow/tools/research_progress.ts +26 -0
- package/dist/templates/research-workflow/tools/research_status.ts +40 -0
- package/dist/templates/research-workflow/workflows/prompts.ts +143 -0
- package/dist/templates/research-workflow/workflows/research.ts +558 -0
- package/dist/templates/retail/agent.test.ts +319 -256
- package/dist/templates/retail/agent.ts +6 -46
- package/dist/templates/retail/authenticate.ts +2 -2
- package/dist/templates/retail/client.tsx +45 -24
- package/dist/templates/retail/registry.test.ts +40 -25
- package/dist/templates/retail/resolve.test.ts +17 -16
- package/dist/templates/retail/resolve.ts +32 -67
- package/dist/templates/retail/shared.test.ts +21 -19
- package/dist/templates/retail/shared.ts +61 -23
- package/dist/templates/retail/store.test.ts +47 -52
- package/dist/templates/retail/store.ts +92 -79
- package/dist/templates/retail/swap.test.ts +20 -17
- package/dist/templates/retail/swap.ts +7 -14
- package/dist/templates/retail/tools/cancel_pending_order.ts +7 -11
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +26 -14
- package/dist/templates/retail/tools/find_user_id_by_email.ts +5 -9
- package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +5 -7
- package/dist/templates/retail/tools/get_item_details.ts +7 -8
- package/dist/templates/retail/tools/get_order_details.ts +7 -8
- package/dist/templates/retail/tools/get_product_details.ts +6 -8
- package/dist/templates/retail/tools/get_user_details.ts +6 -8
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -7
- package/dist/templates/retail/tools/modify_pending_order_address.ts +6 -10
- package/dist/templates/retail/tools/modify_pending_order_items.ts +8 -20
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +7 -13
- package/dist/templates/retail/tools/modify_user_address.ts +6 -10
- package/dist/templates/retail/tools/return_delivered_order_items.ts +7 -13
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +1 -1
- package/dist/templates/solo-rpg/agent.test.ts +169 -54
- package/dist/templates/solo-rpg/agent.ts +4 -22
- package/dist/templates/solo-rpg/client.tsx +19 -6
- package/dist/templates/solo-rpg/shared.ts +20 -20
- package/dist/templates/solo-rpg/tools/action_roll.ts +3 -5
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +3 -5
- package/dist/templates/solo-rpg/tools/check_state.ts +7 -7
- package/dist/templates/solo-rpg/tools/load_game.ts +3 -3
- package/dist/templates/solo-rpg/tools/oracle.ts +19 -12
- package/dist/templates/solo-rpg/tools/save_game.ts +11 -8
- package/dist/templates/solo-rpg/tools/setup_character.ts +3 -3
- package/dist/templates/solo-rpg/tools/update_state.ts +13 -13
- package/dist/templates/support-line/agent.test.ts +343 -0
- package/dist/templates/support-line/agent.ts +27 -0
- package/dist/templates/support-line/client.tsx +122 -0
- package/dist/templates/support-line/graph.ts +224 -0
- package/dist/templates/support-line/knowledge.json +65 -0
- package/dist/templates/support-line/prompts.ts +95 -0
- package/dist/templates/support-line/shared.ts +240 -0
- package/dist/templates/support-line/system-prompt.md +17 -0
- package/dist/templates/support-line/tools/answer_question.ts +72 -0
- package/dist/templates/support-line/tools/list_topics.ts +22 -0
- package/dist/templates/support-line/tools/log_ticket.ts +30 -0
- package/dist/templates/transcription-workflow/agent.test.ts +563 -0
- package/dist/templates/transcription-workflow/agent.ts +92 -0
- package/dist/templates/transcription-workflow/client.tsx +234 -0
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +416 -0
- package/dist/templates/transcription-workflow/workflows/wav.ts +314 -0
- package/dist/templates/travel-concierge/agent.test.ts +338 -0
- package/dist/templates/travel-concierge/agent.ts +29 -0
- package/dist/templates/travel-concierge/client.tsx +161 -0
- package/dist/templates/travel-concierge/routing.ts +138 -0
- package/dist/templates/travel-concierge/shared.ts +547 -0
- package/dist/templates/travel-concierge/system-prompt.md +24 -0
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +20 -0
- package/dist/templates/travel-concierge/tools/book_excursion.ts +18 -0
- package/dist/templates/travel-concierge/tools/book_hotel.ts +20 -0
- package/dist/templates/travel-concierge/tools/cancel_action.ts +7 -0
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +16 -0
- package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +7 -0
- package/dist/templates/travel-concierge/tools/confirm_action.ts +7 -0
- package/dist/templates/travel-concierge/tools/lookup_booking.ts +35 -0
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +33 -0
- package/dist/templates/travel-concierge/tools/search_excursions.ts +37 -0
- package/dist/templates/travel-concierge/tools/search_flights.ts +43 -0
- package/dist/templates/travel-concierge/tools/search_hotels.ts +36 -0
- package/dist/templates/travel-concierge/tools/to_car_rental_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_excursion_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_flight_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_hotel_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/update_ticket.ts +22 -0
- package/dist/templates/web-researcher/agent.ts +0 -2
- package/dist/templates/web-researcher/system-prompt.md +10 -0
- package/dist/{test-2LWBcXvY.mjs → test-B6ZBxSpk.mjs} +2 -2
- package/dist/typecheck.mjs +17 -15
- package/dist/worker-bundler-CIdDhZ0c.mjs +398 -0
- package/dist/worker-bundler.d.ts +14 -0
- package/dist/worker-bundler.mjs +1 -108
- package/dist/workflow-B-O97AqI.mjs +125 -0
- package/dist/workflow-bundler.d.ts +133 -0
- package/dist/workflow.d.ts +67 -0
- package/package.json +13 -9
- package/dist/build-DQJ1ZSm9.mjs +0 -39
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import "@alexkroman1/aai-ui/styles.css";
|
|
2
|
-
import type { ChatMessage } from "@alexkroman1/aai-ui";
|
|
3
|
-
import {
|
|
4
|
-
|
|
2
|
+
import type { AgentState, ChatMessage } from "@alexkroman1/aai-ui";
|
|
3
|
+
import {
|
|
4
|
+
AutoScroll,
|
|
5
|
+
client,
|
|
6
|
+
useAgentState,
|
|
7
|
+
useSession,
|
|
8
|
+
useUserTranscript,
|
|
9
|
+
} from "@alexkroman1/aai-ui";
|
|
5
10
|
import type { DashboardView, DispatchState, IncidentSummary, Severity, Status } from "./shared.ts";
|
|
6
|
-
import { dashboardView } from "./shared.ts";
|
|
11
|
+
import { dashboardView, dispatchSlot } from "./shared.ts";
|
|
7
12
|
|
|
8
13
|
const CSS = `
|
|
9
14
|
@keyframes dc-pulse {
|
|
@@ -54,19 +59,26 @@ const statusColors: Record<string, string> = {
|
|
|
54
59
|
|
|
55
60
|
// The board before the first tool call — derived from the projection so a
|
|
56
61
|
// new DashboardView field can't silently miss the pre-first-tool-call render.
|
|
57
|
-
const EMPTY_DASH = dashboardView(
|
|
62
|
+
const EMPTY_DASH: DashboardView = dispatchSlot.projection(dashboardView)(undefined);
|
|
58
63
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
// The dot's colour per session state, as an EXHAUSTIVE map rather than an
|
|
65
|
+
// if-chain with a grey default.
|
|
66
|
+
//
|
|
67
|
+
// The palette is this template's own — every client here paints the same six
|
|
68
|
+
// states in its own colours, so the SDK has nothing to share but the union. What
|
|
69
|
+
// the SDK does own is `AgentState`, and `satisfies Record<AgentState, string>`
|
|
70
|
+
// is what borrows it: a state added there stops compiling here, where the
|
|
71
|
+
// `state === "…"` chain this replaced answered a new state with a silent grey
|
|
72
|
+
// badge in three separate files and no way to notice.
|
|
73
|
+
const STATE_COLORS = {
|
|
74
|
+
disconnected: "#6b7280",
|
|
75
|
+
connecting: "#6b7280",
|
|
76
|
+
ready: "#22c55e",
|
|
77
|
+
listening: "#22c55e",
|
|
78
|
+
thinking: "#eab308",
|
|
79
|
+
speaking: "#3b82f6",
|
|
80
|
+
error: "#6b7280",
|
|
81
|
+
} satisfies Record<AgentState, string>;
|
|
70
82
|
|
|
71
83
|
function Panel({ title, children }: { title: string; children: React.ReactNode }) {
|
|
72
84
|
return (
|
|
@@ -133,15 +145,14 @@ function IncidentCard({ inc }: { inc: IncidentSummary }) {
|
|
|
133
145
|
|
|
134
146
|
function App() {
|
|
135
147
|
const session = useSession();
|
|
136
|
-
|
|
148
|
+
// `speaking` and `text` in place of a `userTranscript !== null` check: `null`
|
|
149
|
+
// is silence and `""` is speech detected with no words back yet, and reading
|
|
150
|
+
// them as one falsy value hides the indicator at the start of every turn.
|
|
151
|
+
const { speaking, text: userTranscript } = useUserTranscript();
|
|
137
152
|
// The agent's own board, projected by `syncState`. This replaced a
|
|
138
153
|
// useState mirror that merged incident deltas out of tool events — the
|
|
139
154
|
// projection is already the complete list, so there is nothing to merge.
|
|
140
|
-
const dash = useAgentState<DashboardView>()
|
|
141
|
-
|
|
142
|
-
useEffect(() => {
|
|
143
|
-
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
144
|
-
}, [session.messages]);
|
|
155
|
+
const dash = useAgentState<DashboardView>(EMPTY_DASH);
|
|
145
156
|
|
|
146
157
|
const incidentList = [...dash.incidents].reverse();
|
|
147
158
|
const activeIncidents = incidentList.filter((i) => i.status !== "resolved");
|
|
@@ -175,7 +186,7 @@ function App() {
|
|
|
175
186
|
<span
|
|
176
187
|
className="w-2.5 h-2.5 rounded-full inline-block"
|
|
177
188
|
style={{
|
|
178
|
-
background:
|
|
189
|
+
background: STATE_COLORS[session.state],
|
|
179
190
|
animation:
|
|
180
191
|
session.state === "listening"
|
|
181
192
|
? "dc-pulse 1.5s ease-in-out infinite"
|
|
@@ -222,7 +233,10 @@ function App() {
|
|
|
222
233
|
className="flex flex-col overflow-hidden"
|
|
223
234
|
style={{ borderRight: "1px solid #1e293b" }}
|
|
224
235
|
>
|
|
225
|
-
<
|
|
236
|
+
<AutoScroll
|
|
237
|
+
scrollClassName="dc-messages overflow-y-auto"
|
|
238
|
+
contentClassName="p-4 flex flex-col gap-2"
|
|
239
|
+
>
|
|
226
240
|
{session.messages.length === 0 && (
|
|
227
241
|
<div className="text-center p-10 text-[13px]" style={{ color: "#475569" }}>
|
|
228
242
|
Dispatch Command Center standing by. Click START to begin operations.
|
|
@@ -250,10 +264,9 @@ function App() {
|
|
|
250
264
|
{m.content}
|
|
251
265
|
</div>
|
|
252
266
|
))}
|
|
253
|
-
|
|
254
|
-
</div>
|
|
267
|
+
</AutoScroll>
|
|
255
268
|
|
|
256
|
-
{
|
|
269
|
+
{speaking && (
|
|
257
270
|
<div
|
|
258
271
|
className="flex items-center px-4 py-2 text-xs italic min-h-8"
|
|
259
272
|
style={{ background: "#111827", borderTop: "1px solid #1e293b", color: "#64748b" }}
|
|
@@ -262,7 +275,7 @@ function App() {
|
|
|
262
275
|
className="w-2.5 h-2.5 rounded-full inline-block mr-2"
|
|
263
276
|
style={{ background: "#22c55e", animation: "dc-pulse 1.5s ease-in-out infinite" }}
|
|
264
277
|
/>
|
|
265
|
-
{
|
|
278
|
+
{userTranscript}
|
|
266
279
|
</div>
|
|
267
280
|
)}
|
|
268
281
|
{session.error && (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
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
|
|
|
@@ -111,22 +111,16 @@ export interface DashboardView {
|
|
|
111
111
|
incidents: IncidentSummary[];
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
/** The `syncState` projection — the whole contract with client.tsx.
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
/** The `syncState` projection — the whole contract with client.tsx. Takes the
|
|
115
|
+
* board itself, not the slot: `dispatchSlot.projection` supplies a real one
|
|
116
|
+
* even before the first tool call. */
|
|
117
|
+
export function dashboardView(state: FrozenDispatchState): DashboardView {
|
|
117
118
|
return {
|
|
118
|
-
systemAlertLevel:
|
|
119
|
-
incidents: Object.values(
|
|
119
|
+
systemAlertLevel: state.alertLevel,
|
|
120
|
+
incidents: Object.values(state.incidents).map(incidentSummary),
|
|
120
121
|
};
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
// ─── State helpers ───────────────────────────────────────────────────────────
|
|
124
|
-
// The dispatch board lives in `ctx.state`, the agent's per-session mutable
|
|
125
|
-
// state — sessions must not see each other's incidents, and ctx.state gives
|
|
126
|
-
// that isolation by construction. Nothing here needs to outlive the session.
|
|
127
|
-
|
|
128
|
-
export type StateSlot = { dispatch?: DispatchState };
|
|
129
|
-
|
|
130
124
|
// ─── Resource generation ────────────────────────────────────────────────────
|
|
131
125
|
|
|
132
126
|
const RESOURCE_DEFS: [string, Resource["type"], string, string[]][] = [
|
|
@@ -167,15 +161,10 @@ export function createDefaultState(): DispatchState {
|
|
|
167
161
|
};
|
|
168
162
|
}
|
|
169
163
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
slot.dispatch ??= createDefaultState();
|
|
175
|
-
return slot.dispatch;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ─── Serialized state updates ────────────────────────────────────────────────
|
|
164
|
+
// ─── State helpers ───────────────────────────────────────────────────────────
|
|
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.
|
|
179
168
|
|
|
180
169
|
/**
|
|
181
170
|
* Growth caps. The whole dispatch state is one object whose summaries feed
|
|
@@ -183,8 +172,10 @@ export function getState(ctx: ToolContext): DispatchState {
|
|
|
183
172
|
* timelines must be pruned or a long session's payloads grow without bound.
|
|
184
173
|
*/
|
|
185
174
|
const MAX_RESOLVED_KEPT = 10;
|
|
186
|
-
const MAX_TIMELINE_ENTRIES = 50;
|
|
175
|
+
export const MAX_TIMELINE_ENTRIES = 50;
|
|
187
176
|
|
|
177
|
+
/** Resolved incidents only — the timeline cap is held on append by
|
|
178
|
+
* `logEvent`, so nothing here has to re-trim it. */
|
|
188
179
|
function pruneState(state: DispatchState): void {
|
|
189
180
|
const resolved = Object.values(state.incidents)
|
|
190
181
|
.filter((i) => i.status === "resolved")
|
|
@@ -192,46 +183,48 @@ function pruneState(state: DispatchState): void {
|
|
|
192
183
|
for (const inc of resolved.slice(MAX_RESOLVED_KEPT)) {
|
|
193
184
|
delete state.incidents[inc.id];
|
|
194
185
|
}
|
|
195
|
-
for (const inc of Object.values(state.incidents)) {
|
|
196
|
-
if (inc.timeline.length > MAX_TIMELINE_ENTRIES) {
|
|
197
|
-
inc.timeline = inc.timeline.slice(-MAX_TIMELINE_ENTRIES);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
186
|
}
|
|
201
187
|
|
|
202
|
-
const sessionLock = createKeyedLock();
|
|
203
|
-
|
|
204
188
|
/**
|
|
205
|
-
*
|
|
189
|
+
* The session's dispatch board, as one typed slot.
|
|
206
190
|
*
|
|
207
|
-
*
|
|
208
|
-
* `
|
|
209
|
-
*
|
|
210
|
-
* the
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
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.
|
|
214
198
|
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
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.
|
|
219
205
|
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
206
|
+
* `after` is the bookkeeping every mutating tool needs and none of them should
|
|
207
|
+
* have to remember — pruning resolved incidents, and recalculating the alert
|
|
208
|
+
* level the dashboard reads. Declaring it here rather than at each call site is
|
|
209
|
+
* the point: a new mutating tool gets both for free.
|
|
222
210
|
*/
|
|
223
|
-
export
|
|
224
|
-
|
|
225
|
-
mutator: (state: DispatchState) => R | Promise<R>,
|
|
226
|
-
): Promise<R> {
|
|
227
|
-
return withLock(sessionLock, ctx.sessionId, async () => {
|
|
228
|
-
const state = getState(ctx);
|
|
229
|
-
const result = await mutator(state);
|
|
211
|
+
export const dispatchSlot = sessionSlot("dispatch", createDefaultState, {
|
|
212
|
+
after: (state) => {
|
|
230
213
|
pruneState(state);
|
|
231
214
|
recalculateAlertLevel(state);
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
|
|
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>;
|
|
235
228
|
|
|
236
229
|
// ─── Incident helpers ────────────────────────────────────────────────────────
|
|
237
230
|
|
|
@@ -263,32 +256,46 @@ export function createIncident(state: DispatchState, overrides: Partial<Incident
|
|
|
263
256
|
return incident;
|
|
264
257
|
}
|
|
265
258
|
|
|
266
|
-
|
|
267
|
-
|
|
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 } },
|
|
268
271
|
incidentId: string,
|
|
269
|
-
):
|
|
272
|
+
): I | ToolFailure {
|
|
270
273
|
return state.incidents[incidentId] ?? { error: `Incident ${incidentId} not found` };
|
|
271
274
|
}
|
|
272
275
|
|
|
273
|
-
/** Append a timeline entry and touch `updatedAt
|
|
276
|
+
/** Append a timeline entry and touch `updatedAt`, holding
|
|
277
|
+
* {@link MAX_TIMELINE_ENTRIES}. Capped on APPEND rather than swept later, so
|
|
278
|
+
* a single long-running incident cannot outgrow the payload between sweeps. */
|
|
274
279
|
export function logEvent(inc: Incident, event: string): void {
|
|
275
280
|
const time = Date.now();
|
|
276
|
-
inc.timeline
|
|
281
|
+
pushCapped(inc.timeline, { time, event }, MAX_TIMELINE_ENTRIES);
|
|
277
282
|
inc.updatedAt = time;
|
|
278
283
|
}
|
|
279
284
|
|
|
280
285
|
/** Minutes since the incident was created, rounded. */
|
|
281
|
-
export function incidentAgeMinutes(inc: Incident): number {
|
|
286
|
+
export function incidentAgeMinutes(inc: DeepReadonly<Incident>): number {
|
|
282
287
|
return Math.round((Date.now() - inc.createdAt) / 60_000);
|
|
283
288
|
}
|
|
284
289
|
|
|
285
290
|
/** A resource as tool results describe it to the LLM. */
|
|
286
|
-
export function resourceBrief(r: Resource): {
|
|
291
|
+
export function resourceBrief(r: DeepReadonly<Resource>): {
|
|
287
292
|
callsign: string;
|
|
288
293
|
type: Resource["type"];
|
|
289
294
|
capabilities: string[];
|
|
290
295
|
} {
|
|
291
|
-
|
|
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] };
|
|
292
299
|
}
|
|
293
300
|
|
|
294
301
|
/**
|
|
@@ -296,7 +303,7 @@ export function resourceBrief(r: Resource): {
|
|
|
296
303
|
* resources have been released (and possibly reassigned), so escalating,
|
|
297
304
|
* re-resolving, or dispatching to it would corrupt resource assignments.
|
|
298
305
|
*/
|
|
299
|
-
export function assertNotResolved(inc: Incident
|
|
306
|
+
export function assertNotResolved(inc: DeepReadonly<Incident>, action: string): ToolFailure | null {
|
|
300
307
|
if (inc.status === "resolved") {
|
|
301
308
|
return {
|
|
302
309
|
error: `Incident ${inc.id} is resolved — cannot ${action}. Create a new incident if the situation has reopened.`,
|
|
@@ -602,7 +609,7 @@ export function recommendResources(
|
|
|
602
609
|
/** Fraction of resources not currently available, 0–1. The alert level and
|
|
603
610
|
* the dashboard's displayed utilization both derive from this, so they can
|
|
604
611
|
* never disagree about what "utilization" means. */
|
|
605
|
-
export function resourceUtilization(state:
|
|
612
|
+
export function resourceUtilization(state: FrozenDispatchState): number {
|
|
606
613
|
const available = state.resources.filter((r) => r.status === "available").length;
|
|
607
614
|
return 1 - available / state.resources.length;
|
|
608
615
|
}
|
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isToolFailure } from "@alexkroman1/aai";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { findIncident, logEvent
|
|
3
|
+
import { dispatchSlot, findIncident, logEvent } from "../shared.ts";
|
|
4
4
|
|
|
5
|
-
export
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if ("error" in inc) return inc;
|
|
12
|
+
execute(args, state) {
|
|
13
|
+
const inc = findIncident(state, args.incidentId);
|
|
14
|
+
if (isToolFailure(inc)) return inc;
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const entry = args.source ? `[${args.source}] ${args.note}` : args.note;
|
|
17
|
+
logEvent(inc, entry);
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
});
|
|
19
|
+
return {
|
|
20
|
+
incidentId: args.incidentId,
|
|
21
|
+
noteAdded: entry,
|
|
22
|
+
timelineEntries: inc.timeline.length,
|
|
23
|
+
};
|
|
26
24
|
},
|
|
27
25
|
});
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import { tool } from "@alexkroman1/aai";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
import {
|
|
4
3
|
calculateTriageScore,
|
|
5
4
|
createIncident,
|
|
5
|
+
dispatchSlot,
|
|
6
6
|
getApplicableProtocols,
|
|
7
7
|
recommendResources,
|
|
8
8
|
recommendSeverity,
|
|
9
9
|
recommendType,
|
|
10
10
|
resourceBrief,
|
|
11
|
-
updateState,
|
|
12
11
|
} from "../shared.ts";
|
|
13
12
|
|
|
14
|
-
export
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
60
|
+
const protocols = getApplicableProtocols(recType, recSeverity);
|
|
61
|
+
const recommended = recommendResources(recType, recSeverity, state);
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isToolFailure } from "@alexkroman1/aai";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import {
|
|
4
4
|
assertNotResolved,
|
|
5
5
|
calculateTriageScore,
|
|
6
|
+
dispatchSlot,
|
|
6
7
|
findIncident,
|
|
7
8
|
logEvent,
|
|
8
9
|
recommendResources,
|
|
9
|
-
updateState,
|
|
10
10
|
} from "../shared.ts";
|
|
11
11
|
|
|
12
|
-
export
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
});
|