@alexkroman1/aai 0.8.3 → 0.8.4
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/cli/tsconfig.tsbuildinfo +1 -0
- package/dist/cli.js +1223 -1701
- package/dist/sdk/_mock_ws.js +2 -2
- package/dist/sdk/_mock_ws.js.map +1 -1
- package/dist/sdk/_render_check.d.ts.map +1 -1
- package/dist/sdk/_render_check.js +29 -2
- package/dist/sdk/_render_check.js.map +1 -1
- package/dist/sdk/_utils.d.ts +4 -0
- package/dist/sdk/_utils.d.ts.map +1 -0
- package/dist/sdk/_utils.js +7 -0
- package/dist/sdk/_utils.js.map +1 -0
- package/dist/sdk/builtin_tools.d.ts +35 -11
- package/dist/sdk/builtin_tools.d.ts.map +1 -1
- package/dist/sdk/builtin_tools.js +118 -76
- package/dist/sdk/builtin_tools.js.map +1 -1
- package/dist/sdk/capnweb.d.ts +76 -47
- package/dist/sdk/capnweb.d.ts.map +1 -1
- package/dist/sdk/capnweb.js +99 -242
- package/dist/sdk/capnweb.js.map +1 -1
- package/dist/sdk/direct_executor.d.ts.map +1 -1
- package/dist/sdk/direct_executor.js +0 -2
- package/dist/sdk/direct_executor.js.map +1 -1
- package/dist/sdk/host.d.ts +59 -0
- package/dist/sdk/host.d.ts.map +1 -0
- package/dist/sdk/host.js +131 -0
- package/dist/sdk/host.js.map +1 -0
- package/dist/sdk/mod.d.ts +2 -4
- package/dist/sdk/mod.d.ts.map +1 -1
- package/dist/sdk/mod.js +2 -3
- package/dist/sdk/mod.js.map +1 -1
- package/dist/sdk/protocol.d.ts +33 -135
- package/dist/sdk/protocol.d.ts.map +1 -1
- package/dist/sdk/protocol.js +49 -51
- package/dist/sdk/protocol.js.map +1 -1
- package/dist/sdk/runtime.d.ts +0 -1
- package/dist/sdk/runtime.d.ts.map +1 -1
- package/dist/sdk/runtime.js +5 -24
- package/dist/sdk/runtime.js.map +1 -1
- package/dist/sdk/s2s.d.ts +14 -3
- package/dist/sdk/s2s.d.ts.map +1 -1
- package/dist/sdk/s2s.js +72 -113
- package/dist/sdk/s2s.js.map +1 -1
- package/dist/sdk/server.d.ts +1 -1
- package/dist/sdk/server.d.ts.map +1 -1
- package/dist/sdk/server.js +26 -10
- package/dist/sdk/server.js.map +1 -1
- package/dist/sdk/session.d.ts +5 -1
- package/dist/sdk/session.d.ts.map +1 -1
- package/dist/sdk/session.js +131 -137
- package/dist/sdk/session.js.map +1 -1
- package/dist/sdk/tsconfig.tsbuildinfo +1 -0
- package/dist/sdk/types.d.ts +30 -3
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/sdk/types.js +37 -0
- package/dist/sdk/types.js.map +1 -1
- package/dist/sdk/winterc_server.d.ts +0 -1
- package/dist/sdk/winterc_server.d.ts.map +1 -1
- package/dist/sdk/winterc_server.js +0 -1
- package/dist/sdk/winterc_server.js.map +1 -1
- package/dist/sdk/worker_entry.d.ts +3 -11
- package/dist/sdk/worker_entry.d.ts.map +1 -1
- package/dist/sdk/worker_entry.js +8 -18
- package/dist/sdk/worker_entry.js.map +1 -1
- package/dist/sdk/worker_shim.d.ts +5 -6
- package/dist/sdk/worker_shim.d.ts.map +1 -1
- package/dist/sdk/worker_shim.js +93 -136
- package/dist/sdk/worker_shim.js.map +1 -1
- package/dist/sdk/ws_handler.d.ts +1 -3
- package/dist/sdk/ws_handler.d.ts.map +1 -1
- package/dist/sdk/ws_handler.js +14 -25
- package/dist/sdk/ws_handler.js.map +1 -1
- package/dist/ui/_cn.d.ts +5 -0
- package/dist/ui/_cn.d.ts.map +1 -0
- package/dist/ui/_cn.js +22 -0
- package/dist/ui/_cn.js.map +1 -0
- package/dist/ui/_components/app.d.ts +3 -1
- package/dist/ui/_components/app.d.ts.map +1 -1
- package/dist/ui/_components/app.js +2 -2
- package/dist/ui/_components/app.js.map +1 -1
- package/dist/ui/_components/button.d.ts +11 -0
- package/dist/ui/_components/button.d.ts.map +1 -0
- package/dist/ui/_components/button.js +17 -0
- package/dist/ui/_components/button.js.map +1 -0
- package/dist/ui/_components/chat_view.d.ts +3 -1
- package/dist/ui/_components/chat_view.d.ts.map +1 -1
- package/dist/ui/_components/chat_view.js +4 -2
- package/dist/ui/_components/chat_view.js.map +1 -1
- package/dist/ui/_components/controls.d.ts +3 -1
- package/dist/ui/_components/controls.d.ts.map +1 -1
- package/dist/ui/_components/controls.js +4 -5
- package/dist/ui/_components/controls.js.map +1 -1
- package/dist/ui/_components/error_banner.d.ts +2 -1
- package/dist/ui/_components/error_banner.d.ts.map +1 -1
- package/dist/ui/_components/error_banner.js +3 -2
- package/dist/ui/_components/error_banner.js.map +1 -1
- package/dist/ui/_components/message_bubble.d.ts +2 -1
- package/dist/ui/_components/message_bubble.d.ts.map +1 -1
- package/dist/ui/_components/message_bubble.js +5 -3
- package/dist/ui/_components/message_bubble.js.map +1 -1
- package/dist/ui/_components/message_list.d.ts +3 -1
- package/dist/ui/_components/message_list.d.ts.map +1 -1
- package/dist/ui/_components/message_list.js +7 -15
- package/dist/ui/_components/message_list.js.map +1 -1
- package/dist/ui/_components/sidebar_layout.d.ts +2 -1
- package/dist/ui/_components/sidebar_layout.d.ts.map +1 -1
- package/dist/ui/_components/sidebar_layout.js +5 -7
- package/dist/ui/_components/sidebar_layout.js.map +1 -1
- package/dist/ui/_components/start_screen.d.ts +2 -1
- package/dist/ui/_components/start_screen.d.ts.map +1 -1
- package/dist/ui/_components/start_screen.js +5 -2
- package/dist/ui/_components/start_screen.js.map +1 -1
- package/dist/ui/_components/state_indicator.d.ts +2 -1
- package/dist/ui/_components/state_indicator.d.ts.map +1 -1
- package/dist/ui/_components/state_indicator.js +3 -2
- package/dist/ui/_components/state_indicator.js.map +1 -1
- package/dist/ui/_components/thinking_indicator.d.ts +3 -1
- package/dist/ui/_components/thinking_indicator.d.ts.map +1 -1
- package/dist/ui/_components/thinking_indicator.js +4 -2
- package/dist/ui/_components/thinking_indicator.js.map +1 -1
- package/dist/ui/_components/tool_call_block.d.ts +2 -1
- package/dist/ui/_components/tool_call_block.d.ts.map +1 -1
- package/dist/ui/_components/tool_call_block.js +13 -25
- package/dist/ui/_components/tool_call_block.js.map +1 -1
- package/dist/ui/_components/transcript.d.ts +2 -1
- package/dist/ui/_components/transcript.d.ts.map +1 -1
- package/dist/ui/_components/transcript.js +3 -2
- package/dist/ui/_components/transcript.js.map +1 -1
- package/dist/ui/_jsdom_setup.d.ts +1 -0
- package/dist/ui/_jsdom_setup.d.ts.map +1 -0
- package/dist/ui/_jsdom_setup.js +6 -0
- package/dist/ui/_jsdom_setup.js.map +1 -0
- package/dist/ui/audio.d.ts.map +1 -1
- package/dist/ui/audio.js +4 -4
- package/dist/ui/audio.js.map +1 -1
- package/dist/ui/components.d.ts +13 -55
- package/dist/ui/components.d.ts.map +1 -1
- package/dist/ui/components.js +13 -42
- package/dist/ui/components.js.map +1 -1
- package/dist/ui/components_mod.d.ts +14 -3
- package/dist/ui/components_mod.d.ts.map +1 -1
- package/dist/ui/components_mod.js +14 -3
- package/dist/ui/components_mod.js.map +1 -1
- package/dist/ui/mod.d.ts +1 -8
- package/dist/ui/mod.d.ts.map +1 -1
- package/dist/ui/mod.js +1 -5
- package/dist/ui/mod.js.map +1 -1
- package/dist/ui/mount.d.ts +1 -1
- package/dist/ui/mount.d.ts.map +1 -1
- package/dist/ui/mount.js +1 -0
- package/dist/ui/mount.js.map +1 -1
- package/dist/ui/session.d.ts +0 -2
- package/dist/ui/session.d.ts.map +1 -1
- package/dist/ui/session.js +9 -18
- package/dist/ui/session.js.map +1 -1
- package/dist/ui/signals.d.ts +8 -3
- package/dist/ui/signals.d.ts.map +1 -1
- package/dist/ui/signals.js +22 -11
- package/dist/ui/signals.js.map +1 -1
- package/dist/ui/tsconfig.tsbuildinfo +1 -0
- package/dist/ui/worklets/playback-processor.js +3 -3
- package/package.json +37 -17
- package/templates/_shared/CLAUDE.md +4 -7
- package/templates/dispatch-center/agent.ts +85 -397
- package/templates/solo-rpg/agent.ts +3 -3
|
@@ -82,117 +82,25 @@ function createState(): DispatchState {
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
const RESOURCE_DEFS: [string, Resource["type"], string, string[]][] = [
|
|
86
|
+
["R1", "ambulance", "Medic-1", ["als", "cardiac", "pediatric"]],
|
|
87
|
+
["R2", "ambulance", "Medic-2", ["als", "trauma"]],
|
|
88
|
+
["R3", "ambulance", "Medic-3", ["bls"]],
|
|
89
|
+
["R4", "fire_engine", "Engine-7", ["structural", "rescue", "ems_first_response"]],
|
|
90
|
+
["R5", "fire_engine", "Ladder-2", ["aerial", "rescue", "ventilation"]],
|
|
91
|
+
["R6", "police", "Unit-12", ["patrol", "traffic_control"]],
|
|
92
|
+
["R7", "police", "Unit-15", ["patrol", "investigation"]],
|
|
93
|
+
["R8", "hazmat_team", "HazMat-1", ["chemical", "biological", "radiological", "decon"]],
|
|
94
|
+
["R9", "helicopter", "LifeFlight-1", ["medevac", "search_rescue", "thermal_imaging"]],
|
|
95
|
+
["R10", "ems_supervisor", "EMS-Sup-1", ["mass_casualty", "triage_lead", "command"]],
|
|
96
|
+
["R11", "k9_unit", "K9-3", ["tracking", "narcotics", "explosives"]],
|
|
97
|
+
["R12", "swat", "TAC-1", ["tactical", "hostage_rescue", "high_risk_warrant"]],
|
|
98
|
+
];
|
|
99
|
+
|
|
85
100
|
function generateResources(): Resource[] {
|
|
86
|
-
return [
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
type: "ambulance",
|
|
90
|
-
callsign: "Medic-1",
|
|
91
|
-
status: "available",
|
|
92
|
-
assignedIncident: null,
|
|
93
|
-
eta: null,
|
|
94
|
-
capabilities: ["als", "cardiac", "pediatric"],
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
id: "R2",
|
|
98
|
-
type: "ambulance",
|
|
99
|
-
callsign: "Medic-2",
|
|
100
|
-
status: "available",
|
|
101
|
-
assignedIncident: null,
|
|
102
|
-
eta: null,
|
|
103
|
-
capabilities: ["als", "trauma"],
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
id: "R3",
|
|
107
|
-
type: "ambulance",
|
|
108
|
-
callsign: "Medic-3",
|
|
109
|
-
status: "available",
|
|
110
|
-
assignedIncident: null,
|
|
111
|
-
eta: null,
|
|
112
|
-
capabilities: ["bls"],
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
id: "R4",
|
|
116
|
-
type: "fire_engine",
|
|
117
|
-
callsign: "Engine-7",
|
|
118
|
-
status: "available",
|
|
119
|
-
assignedIncident: null,
|
|
120
|
-
eta: null,
|
|
121
|
-
capabilities: ["structural", "rescue", "ems_first_response"],
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
id: "R5",
|
|
125
|
-
type: "fire_engine",
|
|
126
|
-
callsign: "Ladder-2",
|
|
127
|
-
status: "available",
|
|
128
|
-
assignedIncident: null,
|
|
129
|
-
eta: null,
|
|
130
|
-
capabilities: ["aerial", "rescue", "ventilation"],
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
id: "R6",
|
|
134
|
-
type: "police",
|
|
135
|
-
callsign: "Unit-12",
|
|
136
|
-
status: "available",
|
|
137
|
-
assignedIncident: null,
|
|
138
|
-
eta: null,
|
|
139
|
-
capabilities: ["patrol", "traffic_control"],
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
id: "R7",
|
|
143
|
-
type: "police",
|
|
144
|
-
callsign: "Unit-15",
|
|
145
|
-
status: "available",
|
|
146
|
-
assignedIncident: null,
|
|
147
|
-
eta: null,
|
|
148
|
-
capabilities: ["patrol", "investigation"],
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
id: "R8",
|
|
152
|
-
type: "hazmat_team",
|
|
153
|
-
callsign: "HazMat-1",
|
|
154
|
-
status: "available",
|
|
155
|
-
assignedIncident: null,
|
|
156
|
-
eta: null,
|
|
157
|
-
capabilities: ["chemical", "biological", "radiological", "decon"],
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
id: "R9",
|
|
161
|
-
type: "helicopter",
|
|
162
|
-
callsign: "LifeFlight-1",
|
|
163
|
-
status: "available",
|
|
164
|
-
assignedIncident: null,
|
|
165
|
-
eta: null,
|
|
166
|
-
capabilities: ["medevac", "search_rescue", "thermal_imaging"],
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
id: "R10",
|
|
170
|
-
type: "ems_supervisor",
|
|
171
|
-
callsign: "EMS-Sup-1",
|
|
172
|
-
status: "available",
|
|
173
|
-
assignedIncident: null,
|
|
174
|
-
eta: null,
|
|
175
|
-
capabilities: ["mass_casualty", "triage_lead", "command"],
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
id: "R11",
|
|
179
|
-
type: "k9_unit",
|
|
180
|
-
callsign: "K9-3",
|
|
181
|
-
status: "available",
|
|
182
|
-
assignedIncident: null,
|
|
183
|
-
eta: null,
|
|
184
|
-
capabilities: ["tracking", "narcotics", "explosives"],
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
id: "R12",
|
|
188
|
-
type: "swat",
|
|
189
|
-
callsign: "TAC-1",
|
|
190
|
-
status: "available",
|
|
191
|
-
assignedIncident: null,
|
|
192
|
-
eta: null,
|
|
193
|
-
capabilities: ["tactical", "hostage_rescue", "high_risk_warrant"],
|
|
194
|
-
},
|
|
195
|
-
];
|
|
101
|
+
return RESOURCE_DEFS.map(([id, type, callsign, capabilities]) => ({
|
|
102
|
+
id, type, callsign, capabilities, status: "available" as const, assignedIncident: null, eta: null,
|
|
103
|
+
}));
|
|
196
104
|
}
|
|
197
105
|
|
|
198
106
|
// ─── Triage & scoring ────────────────────────────────────────────────────────
|
|
@@ -227,118 +135,38 @@ function calculateTriageScore(
|
|
|
227
135
|
return Math.round(Math.min(score, 250));
|
|
228
136
|
}
|
|
229
137
|
|
|
138
|
+
const SEVERITY_KEYWORDS: [Severity, string[]][] = [
|
|
139
|
+
["critical", ["unconscious", "not breathing", "cardiac arrest", "trapped", "collapse", "explosion", "active shooter", "mass casualty"]],
|
|
140
|
+
["urgent", ["bleeding", "chest pain", "difficulty breathing", "fire", "hazmat", "shooting", "stabbing", "multi-vehicle"]],
|
|
141
|
+
["moderate", ["fall", "broken", "fracture", "smoke", "minor fire", "assault", "theft"]],
|
|
142
|
+
];
|
|
143
|
+
|
|
230
144
|
function recommendSeverity(description: string): Severity {
|
|
231
145
|
const d = description.toLowerCase();
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
"cardiac arrest",
|
|
236
|
-
"trapped",
|
|
237
|
-
"collapse",
|
|
238
|
-
"explosion",
|
|
239
|
-
"active shooter",
|
|
240
|
-
"mass casualty",
|
|
241
|
-
];
|
|
242
|
-
const urgentKeywords = [
|
|
243
|
-
"bleeding",
|
|
244
|
-
"chest pain",
|
|
245
|
-
"difficulty breathing",
|
|
246
|
-
"fire",
|
|
247
|
-
"hazmat",
|
|
248
|
-
"shooting",
|
|
249
|
-
"stabbing",
|
|
250
|
-
"multi-vehicle",
|
|
251
|
-
];
|
|
252
|
-
const moderateKeywords = [
|
|
253
|
-
"fall",
|
|
254
|
-
"broken",
|
|
255
|
-
"fracture",
|
|
256
|
-
"smoke",
|
|
257
|
-
"minor fire",
|
|
258
|
-
"assault",
|
|
259
|
-
"theft",
|
|
260
|
-
];
|
|
261
|
-
|
|
262
|
-
if (criticalKeywords.some((k) => d.includes(k))) return "critical";
|
|
263
|
-
if (urgentKeywords.some((k) => d.includes(k))) return "urgent";
|
|
264
|
-
if (moderateKeywords.some((k) => d.includes(k))) return "moderate";
|
|
146
|
+
for (const [sev, kws] of SEVERITY_KEYWORDS) {
|
|
147
|
+
if (kws.some((k) => d.includes(k))) return sev;
|
|
148
|
+
}
|
|
265
149
|
return "minor";
|
|
266
150
|
}
|
|
267
151
|
|
|
152
|
+
const TYPE_KEYWORDS: Record<IncidentType, string[]> = {
|
|
153
|
+
medical: ["chest pain", "breathing", "unconscious", "seizure", "allergic", "overdose", "cardiac", "stroke", "diabetic", "bleeding", "fall", "injury"],
|
|
154
|
+
fire: ["fire", "smoke", "flames", "burning", "arson"],
|
|
155
|
+
hazmat: ["chemical", "spill", "gas leak", "fumes", "radiation", "contamination", "hazmat"],
|
|
156
|
+
traffic: ["accident", "crash", "collision", "vehicle", "rollover", "pedestrian struck", "hit and run"],
|
|
157
|
+
crime: ["robbery", "assault", "shooting", "stabbing", "burglar", "theft", "domestic", "hostage", "active shooter"],
|
|
158
|
+
natural_disaster: ["earthquake", "flood", "tornado", "hurricane", "landslide", "wildfire", "tsunami"],
|
|
159
|
+
utility: ["power outage", "downed line", "water main", "gas main", "transformer"],
|
|
160
|
+
other: [],
|
|
161
|
+
};
|
|
162
|
+
|
|
268
163
|
function recommendType(description: string): IncidentType {
|
|
269
164
|
const d = description.toLowerCase();
|
|
270
|
-
const typeKeywords: Record<IncidentType, string[]> = {
|
|
271
|
-
medical: [
|
|
272
|
-
"chest pain",
|
|
273
|
-
"breathing",
|
|
274
|
-
"unconscious",
|
|
275
|
-
"seizure",
|
|
276
|
-
"allergic",
|
|
277
|
-
"overdose",
|
|
278
|
-
"cardiac",
|
|
279
|
-
"stroke",
|
|
280
|
-
"diabetic",
|
|
281
|
-
"bleeding",
|
|
282
|
-
"fall",
|
|
283
|
-
"injury",
|
|
284
|
-
],
|
|
285
|
-
fire: ["fire", "smoke", "flames", "burning", "arson"],
|
|
286
|
-
hazmat: [
|
|
287
|
-
"chemical",
|
|
288
|
-
"spill",
|
|
289
|
-
"gas leak",
|
|
290
|
-
"fumes",
|
|
291
|
-
"radiation",
|
|
292
|
-
"contamination",
|
|
293
|
-
"hazmat",
|
|
294
|
-
],
|
|
295
|
-
traffic: [
|
|
296
|
-
"accident",
|
|
297
|
-
"crash",
|
|
298
|
-
"collision",
|
|
299
|
-
"vehicle",
|
|
300
|
-
"rollover",
|
|
301
|
-
"pedestrian struck",
|
|
302
|
-
"hit and run",
|
|
303
|
-
],
|
|
304
|
-
crime: [
|
|
305
|
-
"robbery",
|
|
306
|
-
"assault",
|
|
307
|
-
"shooting",
|
|
308
|
-
"stabbing",
|
|
309
|
-
"burglar",
|
|
310
|
-
"theft",
|
|
311
|
-
"domestic",
|
|
312
|
-
"hostage",
|
|
313
|
-
"active shooter",
|
|
314
|
-
],
|
|
315
|
-
"natural_disaster": [
|
|
316
|
-
"earthquake",
|
|
317
|
-
"flood",
|
|
318
|
-
"tornado",
|
|
319
|
-
"hurricane",
|
|
320
|
-
"landslide",
|
|
321
|
-
"wildfire",
|
|
322
|
-
"tsunami",
|
|
323
|
-
],
|
|
324
|
-
utility: [
|
|
325
|
-
"power outage",
|
|
326
|
-
"downed line",
|
|
327
|
-
"water main",
|
|
328
|
-
"gas main",
|
|
329
|
-
"transformer",
|
|
330
|
-
],
|
|
331
|
-
other: [],
|
|
332
|
-
};
|
|
333
|
-
|
|
334
165
|
let best: IncidentType = "other";
|
|
335
166
|
let bestCount = 0;
|
|
336
|
-
for (const [type, keywords] of Object.entries(
|
|
167
|
+
for (const [type, keywords] of Object.entries(TYPE_KEYWORDS)) {
|
|
337
168
|
const count = keywords.filter((k) => d.includes(k)).length;
|
|
338
|
-
if (count > bestCount) {
|
|
339
|
-
bestCount = count;
|
|
340
|
-
best = type as IncidentType;
|
|
341
|
-
}
|
|
169
|
+
if (count > bestCount) { bestCount = count; best = type as IncidentType; }
|
|
342
170
|
}
|
|
343
171
|
return best;
|
|
344
172
|
}
|
|
@@ -353,90 +181,30 @@ interface Protocol {
|
|
|
353
181
|
}
|
|
354
182
|
|
|
355
183
|
const PROTOCOLS: Protocol[] = [
|
|
356
|
-
{
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
},
|
|
362
|
-
steps: [
|
|
363
|
-
"Establish Incident Command",
|
|
364
|
-
"Request mutual aid if more than 10 casualties estimated",
|
|
365
|
-
"Set up triage area using START protocol: Immediate (red), Delayed (yellow), Minor (green), Deceased (black)",
|
|
366
|
-
"Assign triage lead (EMS supervisor)",
|
|
367
|
-
"Establish patient collection point and treatment area",
|
|
368
|
-
"Coordinate helicopter landing zone if needed",
|
|
369
|
-
"Notify receiving hospitals and activate surge protocols",
|
|
370
|
-
],
|
|
371
|
-
requiredResources: ["ambulance", "ems_supervisor", "fire_engine"],
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
name: "Structure Fire - Working Fire",
|
|
184
|
+
{ name: "Mass Casualty Incident (MCI)",
|
|
185
|
+
triggers: { types: ["medical", "fire", "natural_disaster", "traffic"], minSeverity: "critical" },
|
|
186
|
+
steps: ["Establish Incident Command", "Request mutual aid if >10 casualties", "Set up triage: Immediate (red), Delayed (yellow), Minor (green), Deceased (black)", "Assign triage lead (EMS supervisor)", "Establish patient collection point", "Coordinate helicopter landing zone if needed", "Notify receiving hospitals and activate surge protocols"],
|
|
187
|
+
requiredResources: ["ambulance", "ems_supervisor", "fire_engine"] },
|
|
188
|
+
{ name: "Structure Fire - Working Fire",
|
|
375
189
|
triggers: { types: ["fire"], minSeverity: "urgent" },
|
|
376
|
-
steps: [
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
"Confirm water supply — nearest hydrant",
|
|
380
|
-
"Search and rescue primary sweep",
|
|
381
|
-
"Ventilation operations",
|
|
382
|
-
"Establish RIT (Rapid Intervention Team)",
|
|
383
|
-
"Request additional alarms if fire is not contained in 10 minutes",
|
|
384
|
-
],
|
|
385
|
-
requiredResources: ["fire_engine"],
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
name: "Hazardous Materials Response",
|
|
190
|
+
steps: ["Dispatch minimum 2 engines and 1 ladder", "Establish incident command and 360-degree size-up", "Confirm water supply", "Search and rescue primary sweep", "Ventilation operations", "Establish RIT (Rapid Intervention Team)", "Request additional alarms if not contained in 10 min"],
|
|
191
|
+
requiredResources: ["fire_engine"] },
|
|
192
|
+
{ name: "Hazardous Materials Response",
|
|
389
193
|
triggers: { types: ["hazmat"], minSeverity: "moderate" },
|
|
390
|
-
steps: [
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
"Evacuate downwind at minimum 1000 feet for unknown substances",
|
|
394
|
-
"Deploy HazMat team in appropriate PPE level",
|
|
395
|
-
"Set up decontamination corridor",
|
|
396
|
-
"Monitor air quality and wind direction continuously",
|
|
397
|
-
"Coordinate with poison control and environmental agency",
|
|
398
|
-
],
|
|
399
|
-
requiredResources: ["hazmat_team", "fire_engine", "ambulance"],
|
|
400
|
-
},
|
|
401
|
-
{
|
|
402
|
-
name: "Active Threat / Active Shooter",
|
|
194
|
+
steps: ["Identify substance via placard numbers or SDS", "Establish hot, warm, and cold zones", "Evacuate downwind 1000+ feet for unknowns", "Deploy HazMat team in appropriate PPE", "Set up decontamination corridor", "Monitor air quality and wind continuously", "Coordinate with poison control"],
|
|
195
|
+
requiredResources: ["hazmat_team", "fire_engine", "ambulance"] },
|
|
196
|
+
{ name: "Active Threat / Active Shooter",
|
|
403
197
|
triggers: { types: ["crime"], minSeverity: "critical" },
|
|
404
|
-
steps: [
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
"Activate Rescue Task Force protocol — police escort for EMS into warm zone",
|
|
408
|
-
"Stage ambulances at casualty collection point outside hot zone",
|
|
409
|
-
"Request LifeFlight on standby",
|
|
410
|
-
"Coordinate with school/building security for floor plans",
|
|
411
|
-
"Establish family reunification point",
|
|
412
|
-
],
|
|
413
|
-
requiredResources: ["swat", "police", "ambulance", "ems_supervisor"],
|
|
414
|
-
},
|
|
415
|
-
{
|
|
416
|
-
name: "Multi-Vehicle Accident",
|
|
198
|
+
steps: ["Dispatch SWAT and multiple patrol units", "Establish inner and outer perimeters", "Activate Rescue Task Force — police escort EMS into warm zone", "Stage ambulances outside hot zone", "Request LifeFlight on standby", "Get building floor plans", "Establish family reunification point"],
|
|
199
|
+
requiredResources: ["swat", "police", "ambulance", "ems_supervisor"] },
|
|
200
|
+
{ name: "Multi-Vehicle Accident",
|
|
417
201
|
triggers: { types: ["traffic"], minSeverity: "urgent" },
|
|
418
|
-
steps: [
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
"Triage patients using START protocol",
|
|
422
|
-
"Check for fuel or hazmat spills",
|
|
423
|
-
"Establish landing zone if helicopter transport needed",
|
|
424
|
-
"Coordinate with DOT for road closures and detours",
|
|
425
|
-
],
|
|
426
|
-
requiredResources: ["fire_engine", "ambulance", "police"],
|
|
427
|
-
},
|
|
428
|
-
{
|
|
429
|
-
name: "Cardiac Arrest Protocol",
|
|
202
|
+
steps: ["Dispatch engine for extrication", "Request traffic control to shut lanes", "Triage using START protocol", "Check for fuel/hazmat spills", "Establish helicopter landing zone if needed", "Coordinate with DOT for road closures"],
|
|
203
|
+
requiredResources: ["fire_engine", "ambulance", "police"] },
|
|
204
|
+
{ name: "Cardiac Arrest Protocol",
|
|
430
205
|
triggers: { types: ["medical"], minSeverity: "critical" },
|
|
431
|
-
steps: [
|
|
432
|
-
|
|
433
|
-
"Dispatch closest ALS unit and fire engine for first response",
|
|
434
|
-
"Guide caller through AED use if available",
|
|
435
|
-
"Time from call to first defibrillation is critical — target under 8 minutes",
|
|
436
|
-
"Prepare for advanced airway management on arrival",
|
|
437
|
-
],
|
|
438
|
-
requiredResources: ["ambulance", "fire_engine"],
|
|
439
|
-
},
|
|
206
|
+
steps: ["Instruct caller: CPR — 30 compressions, 2 breaths", "Dispatch closest ALS unit and fire engine", "Guide caller through AED use if available", "Target first defibrillation under 8 minutes", "Prepare for advanced airway management"],
|
|
207
|
+
requiredResources: ["ambulance", "fire_engine"] },
|
|
440
208
|
];
|
|
441
209
|
|
|
442
210
|
function getApplicableProtocols(
|
|
@@ -1366,117 +1134,37 @@ Radio style: "Medic-1, respond priority one to 400 Oak Street, report of cardiac
|
|
|
1366
1134
|
}),
|
|
1367
1135
|
execute: async ({ scenario }, ctx) => {
|
|
1368
1136
|
const state = ctx.state as DispatchState;
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
{
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
"Bus crash at Main and 5th. School bus vs delivery truck. Multiple pediatric patients. Fuel spill on roadway.",
|
|
1137
|
+
type ScenarioDef = { narrative: string; incidents: Partial<Incident>[] };
|
|
1138
|
+
const inc = (location: string, description: string, type: IncidentType, severity: Severity): Partial<Incident> =>
|
|
1139
|
+
({ location, description, type, severity });
|
|
1140
|
+
|
|
1141
|
+
const scenarios: Record<string, ScenarioDef> = {
|
|
1142
|
+
mass_casualty: { narrative: "Bus crash at Main and 5th. School bus vs delivery truck. Multiple pediatric patients. Fuel spill.",
|
|
1376
1143
|
incidents: [
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
type: "traffic",
|
|
1382
|
-
severity: "critical",
|
|
1383
|
-
},
|
|
1384
|
-
{
|
|
1385
|
-
location: "Main St and 5th Ave — fuel spill",
|
|
1386
|
-
description:
|
|
1387
|
-
"Diesel fuel spill from delivery truck spreading toward storm drain, approximately 50 gallons",
|
|
1388
|
-
type: "hazmat",
|
|
1389
|
-
severity: "urgent",
|
|
1390
|
-
},
|
|
1391
|
-
],
|
|
1392
|
-
},
|
|
1393
|
-
"multi_alarm_fire": {
|
|
1394
|
-
narrative:
|
|
1395
|
-
"Working structure fire at 200 Industrial Parkway. 3-story warehouse, heavy smoke showing. Reports of workers possibly trapped on upper floors.",
|
|
1144
|
+
inc("Main St and 5th Ave intersection", "School bus collision with delivery truck, multiple children injured, bus on its side, fuel leaking", "traffic", "critical"),
|
|
1145
|
+
inc("Main St and 5th Ave — fuel spill", "Diesel fuel spill from delivery truck spreading toward storm drain, ~50 gallons", "hazmat", "urgent"),
|
|
1146
|
+
] },
|
|
1147
|
+
multi_alarm_fire: { narrative: "Working structure fire at 200 Industrial Parkway. 3-story warehouse, heavy smoke. Workers possibly trapped.",
|
|
1396
1148
|
incidents: [
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
type: "fire",
|
|
1402
|
-
severity: "critical",
|
|
1403
|
-
},
|
|
1404
|
-
{
|
|
1405
|
-
location: "200 Industrial Parkway — medical",
|
|
1406
|
-
description:
|
|
1407
|
-
"2 workers with smoke inhalation evacuated from ground floor, one with burns to hands and arms",
|
|
1408
|
-
type: "medical",
|
|
1409
|
-
severity: "urgent",
|
|
1410
|
-
},
|
|
1411
|
-
],
|
|
1412
|
-
},
|
|
1413
|
-
"active_shooter": {
|
|
1414
|
-
narrative:
|
|
1415
|
-
"Reports of active shooter at Riverside Mall. Multiple shots fired, crowds fleeing. At least 3 victims reported down in food court area.",
|
|
1149
|
+
inc("200 Industrial Parkway", "3-story warehouse fully involved, possible trapped occupants on 2nd/3rd floor", "fire", "critical"),
|
|
1150
|
+
inc("200 Industrial Parkway — medical", "2 workers with smoke inhalation, one with burns", "medical", "urgent"),
|
|
1151
|
+
] },
|
|
1152
|
+
active_shooter: { narrative: "Active shooter at Riverside Mall. Multiple shots fired, crowds fleeing. At least 3 victims down in food court.",
|
|
1416
1153
|
incidents: [
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
type: "crime",
|
|
1422
|
-
severity: "critical",
|
|
1423
|
-
},
|
|
1424
|
-
{
|
|
1425
|
-
location: "Riverside Mall parking lot",
|
|
1426
|
-
description:
|
|
1427
|
-
"Crowd crush injuries as people fled the building, several people trampled near east exit",
|
|
1428
|
-
type: "medical",
|
|
1429
|
-
severity: "urgent",
|
|
1430
|
-
},
|
|
1431
|
-
],
|
|
1432
|
-
},
|
|
1433
|
-
"natural_disaster": {
|
|
1434
|
-
narrative:
|
|
1435
|
-
"EF-3 tornado touched down in residential area. Path of destruction along Oak Street corridor. Multiple structures collapsed. Power lines down.",
|
|
1154
|
+
inc("Riverside Mall, 1500 River Road — food court", "Active shooter, multiple shots, at least 3 victims down, shooter moving toward west entrance", "crime", "critical"),
|
|
1155
|
+
inc("Riverside Mall parking lot", "Crowd crush injuries, several trampled near east exit", "medical", "urgent"),
|
|
1156
|
+
] },
|
|
1157
|
+
natural_disaster: { narrative: "EF-3 tornado in residential area. Oak Street corridor. Multiple structures collapsed. Power lines down.",
|
|
1436
1158
|
incidents: [
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
severity: "critical",
|
|
1443
|
-
},
|
|
1444
|
-
{
|
|
1445
|
-
location: "Oak Street Elementary School",
|
|
1446
|
-
description:
|
|
1447
|
-
"School roof partially collapsed, staff sheltering students in interior rooms, requesting welfare check",
|
|
1448
|
-
type: "natural_disaster",
|
|
1449
|
-
severity: "critical",
|
|
1450
|
-
},
|
|
1451
|
-
{
|
|
1452
|
-
location: "Oak Street and 12th — utility",
|
|
1453
|
-
description:
|
|
1454
|
-
"Multiple downed power lines sparking, gas main rupture, area needs immediate isolation",
|
|
1455
|
-
type: "utility",
|
|
1456
|
-
severity: "urgent",
|
|
1457
|
-
},
|
|
1458
|
-
],
|
|
1459
|
-
},
|
|
1460
|
-
"highway_pileup": {
|
|
1461
|
-
narrative:
|
|
1462
|
-
"20-plus vehicle pileup on Interstate 95 southbound near mile marker 42. Fog conditions. Multiple entrapments. Tanker truck involved.",
|
|
1159
|
+
inc("Oak Street between 10th and 15th", "Tornado damage, homes collapsed, people trapped, gas lines ruptured", "natural_disaster", "critical"),
|
|
1160
|
+
inc("Oak Street Elementary School", "School roof partially collapsed, staff sheltering students", "natural_disaster", "critical"),
|
|
1161
|
+
inc("Oak Street and 12th — utility", "Downed power lines sparking, gas main rupture, area needs isolation", "utility", "urgent"),
|
|
1162
|
+
] },
|
|
1163
|
+
highway_pileup: { narrative: "20+ vehicle pileup on I-95 southbound mile marker 42. Fog. Multiple entrapments. Tanker truck involved.",
|
|
1463
1164
|
incidents: [
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
"Multi-vehicle pileup, 20-plus vehicles, multiple entrapments, tanker truck involved with unknown cargo, heavy fog limiting visibility",
|
|
1468
|
-
type: "traffic",
|
|
1469
|
-
severity: "critical",
|
|
1470
|
-
},
|
|
1471
|
-
{
|
|
1472
|
-
location: "I-95 southbound — hazmat",
|
|
1473
|
-
description:
|
|
1474
|
-
"Tanker truck leaking unknown liquid, placards not yet visible due to fog, setting up exclusion zone",
|
|
1475
|
-
type: "hazmat",
|
|
1476
|
-
severity: "critical",
|
|
1477
|
-
},
|
|
1478
|
-
],
|
|
1479
|
-
},
|
|
1165
|
+
inc("I-95 southbound mile marker 42", "Multi-vehicle pileup, 20+ vehicles, multiple entrapments, tanker with unknown cargo, heavy fog", "traffic", "critical"),
|
|
1166
|
+
inc("I-95 southbound — hazmat", "Tanker leaking unknown liquid, placards not visible, exclusion zone being set up", "hazmat", "critical"),
|
|
1167
|
+
] },
|
|
1480
1168
|
};
|
|
1481
1169
|
|
|
1482
1170
|
const s = scenarios[scenario];
|
|
@@ -609,9 +609,9 @@ VOICE:
|
|
|
609
609
|
|
|
610
610
|
state: () => structuredClone(defaultState),
|
|
611
611
|
|
|
612
|
-
// Auto-load saved game on connect
|
|
612
|
+
// Auto-load saved game on connect.
|
|
613
613
|
onConnect: async (ctx) => {
|
|
614
|
-
const saved = await ctx.kv.get<GameState>(
|
|
614
|
+
const saved = await ctx.kv.get<GameState>("save:game");
|
|
615
615
|
if (saved) Object.assign(ctx.state as GameState, saved);
|
|
616
616
|
},
|
|
617
617
|
|
|
@@ -619,7 +619,7 @@ VOICE:
|
|
|
619
619
|
onTurn: async (_text, ctx) => {
|
|
620
620
|
const state = ctx.state as GameState;
|
|
621
621
|
if (state.initialized) {
|
|
622
|
-
await ctx.kv.set(
|
|
622
|
+
await ctx.kv.set("save:game", state);
|
|
623
623
|
}
|
|
624
624
|
},
|
|
625
625
|
|