@alexkroman1/aai-cli 6.10.1 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist/{_agent-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
  2. package/dist/{_bundler-C2BDwU7r.mjs → _bundler-B4RqNF5Z.mjs} +2 -2
  3. package/dist/_dev-env.d.ts +22 -2
  4. package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-CiKFH9qw.mjs} +38 -47
  5. package/dist/_dev-server.d.ts +0 -17
  6. package/dist/{_init-C57bWAQ8.mjs → _init-CE4HKdgV.mjs} +3 -2
  7. package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
  8. package/dist/_preflight.d.ts +1 -1
  9. package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-nRFaBEPJ.mjs} +2 -2
  10. package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
  11. package/dist/{_templates-Dxb_P2Wz.mjs → _templates-FI3xPEMj.mjs} +1 -1
  12. package/dist/_workflow-scan.d.ts +108 -0
  13. package/dist/{build-Dfza2pRI.mjs → build-6jfaakgc.mjs} +5 -2
  14. package/dist/cli.mjs +30 -25
  15. package/dist/{client-bundler-BEIqgOtd.mjs → client-bundler-CWnG42cU.mjs} +2 -1
  16. package/dist/client-bundler.mjs +1 -1
  17. package/dist/{delete-vbpjGzqs.mjs → delete-Dl_LSfvI.mjs} +3 -3
  18. package/dist/{deploy-B8lubiRT.mjs → deploy-DvYeD8ia.mjs} +5 -5
  19. package/dist/{dev-Uq5ujP8a.mjs → dev-B59JIfWO.mjs} +1 -1
  20. package/dist/{eject-31gjtaHF.mjs → eject-C9WJyyr2.mjs} +1 -1
  21. package/dist/{init-l6xfU4xX.mjs → init-CpJVjEg0.mjs} +3 -3
  22. package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
  23. package/dist/{logs-BWVGSjLR.mjs → logs-9q-psa4q.mjs} +2 -2
  24. package/dist/project-config.mjs +1 -1
  25. package/dist/scaffold/CLAUDE.md +93 -14
  26. package/dist/scaffold/package.json +5 -3
  27. package/dist/scaffold/server.mjs +13 -4
  28. package/dist/scaffold/vite.config.ts +1 -1
  29. package/dist/{secret-BuMuFR4B.mjs → secret-CVvSLIDV.mjs} +2 -2
  30. package/dist/{storage-DzRZ-eCw.mjs → storage-BvUrnvM3.mjs} +2 -2
  31. package/dist/{studio-BCNUpDgP.mjs → studio--MUV0cid.mjs} +4 -4
  32. package/dist/templates/call-audit/agent.test.ts +818 -0
  33. package/dist/templates/call-audit/agent.ts +171 -0
  34. package/dist/templates/call-audit/client.tsx +198 -0
  35. package/dist/templates/call-audit/workflows/audit.ts +298 -0
  36. package/dist/templates/call-audit/workflows/ingest.ts +251 -0
  37. package/dist/templates/call-audit/workflows/media.ts +637 -0
  38. package/dist/templates/call-audit/workflows/summarize.ts +201 -0
  39. package/dist/templates/call-audit/workflows/sync-api.ts +44 -0
  40. package/dist/templates/dispatch-center/agent.test.ts +149 -25
  41. package/dist/templates/dispatch-center/client.tsx +239 -129
  42. package/dist/templates/dispatch-center/shared.ts +99 -1
  43. package/dist/templates/dispatch-center/system-prompt.md +3 -1
  44. package/dist/templates/dispatch-center/tools/incident_add_note.ts +16 -13
  45. package/dist/templates/dispatch-center/tools/incident_create.ts +19 -1
  46. package/dist/templates/dispatch-center/tools/incident_escalate.ts +68 -59
  47. package/dist/templates/dispatch-center/tools/incident_triage.ts +57 -43
  48. package/dist/templates/dispatch-center/tools/incident_update_status.ts +49 -40
  49. package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +8 -2
  50. package/dist/templates/dispatch-center/tools/resources_dispatch.ts +69 -52
  51. package/dist/templates/dispatch-center/tools/resources_update_status.ts +45 -41
  52. package/dist/templates/embedded-assets/agent.test.ts +17 -14
  53. package/dist/templates/health-assistant/agent.test.ts +22 -4
  54. package/dist/templates/infocom-adventure/agent.test.ts +4 -6
  55. package/dist/templates/infocom-adventure/client.tsx +246 -164
  56. package/dist/templates/link-digest/agent.test.ts +24 -19
  57. package/dist/templates/link-digest/client.tsx +47 -61
  58. package/dist/templates/link-digest/workflows/digest.ts +19 -31
  59. package/dist/templates/night-owl/agent.test.ts +70 -19
  60. package/dist/templates/night-owl/agent.ts +5 -0
  61. package/dist/templates/night-owl/client.tsx +56 -56
  62. package/dist/templates/night-owl/shared.ts +24 -0
  63. package/dist/templates/night-owl/tools/recommend.ts +18 -6
  64. package/dist/templates/pizza-ordering/agent.test.ts +27 -27
  65. package/dist/templates/pizza-ordering/client.tsx +9 -26
  66. package/dist/templates/plan-and-execute/agent.test.ts +99 -58
  67. package/dist/templates/plan-and-execute/agent.ts +1 -1
  68. package/dist/templates/plan-and-execute/client.tsx +12 -15
  69. package/dist/templates/plan-and-execute/shared.ts +71 -2
  70. package/dist/templates/plan-and-execute/tools/plan_status.ts +18 -6
  71. package/dist/templates/plan-and-execute/tools/revise_plan.ts +18 -10
  72. package/dist/templates/plan-and-execute/tools/start_plan.ts +13 -2
  73. package/dist/templates/plan-and-execute/tools/work_next_step.ts +70 -22
  74. package/dist/templates/podcast-digest/agent.test.ts +746 -0
  75. package/dist/templates/podcast-digest/agent.ts +139 -0
  76. package/dist/templates/podcast-digest/client.tsx +154 -0
  77. package/dist/templates/podcast-digest/workflows/digest.ts +411 -0
  78. package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
  79. package/dist/templates/podcast-digest/workflows/slack.ts +209 -0
  80. package/dist/templates/recap-workflow/agent.test.ts +107 -96
  81. package/dist/templates/recap-workflow/tools/recap_progress.ts +9 -12
  82. package/dist/templates/recap-workflow/tools/recap_status.ts +8 -9
  83. package/dist/templates/recap-workflow/tools/request_recap.ts +2 -1
  84. package/dist/templates/recap-workflow/workflows/recap.ts +48 -41
  85. package/dist/templates/redline/agent.test.ts +17 -15
  86. package/dist/templates/redline/client.tsx +12 -12
  87. package/dist/templates/redline/workflows/redline.ts +19 -31
  88. package/dist/templates/research-workflow/agent.test.ts +60 -59
  89. package/dist/templates/research-workflow/tools/research_progress.ts +9 -12
  90. package/dist/templates/research-workflow/tools/research_status.ts +9 -11
  91. package/dist/templates/research-workflow/workflows/research.ts +44 -61
  92. package/dist/templates/retail/agent.test.ts +26 -23
  93. package/dist/templates/retail/client.tsx +226 -117
  94. package/dist/templates/retail/registry.test.ts +38 -6
  95. package/dist/templates/retail/store.test.ts +82 -15
  96. package/dist/templates/retail/store.ts +174 -47
  97. package/dist/templates/retail/system-prompt.md +11 -2
  98. package/dist/templates/retail/tools/cancel_pending_order.ts +2 -2
  99. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +2 -2
  100. package/dist/templates/retail/tools/find_user_id_by_email.ts +8 -5
  101. package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +4 -5
  102. package/dist/templates/retail/tools/get_item_details.ts +3 -4
  103. package/dist/templates/retail/tools/get_order_details.ts +3 -4
  104. package/dist/templates/retail/tools/get_product_details.ts +3 -4
  105. package/dist/templates/retail/tools/get_user_details.ts +2 -2
  106. package/dist/templates/retail/tools/list_all_product_types.ts +5 -5
  107. package/dist/templates/retail/tools/modify_pending_order_address.ts +2 -2
  108. package/dist/templates/retail/tools/modify_pending_order_items.ts +2 -3
  109. package/dist/templates/retail/tools/modify_pending_order_payment.ts +2 -2
  110. package/dist/templates/retail/tools/modify_user_address.ts +2 -2
  111. package/dist/templates/retail/tools/return_delivered_order_items.ts +2 -2
  112. package/dist/templates/retail/tools/transfer_to_human_agents.ts +8 -4
  113. package/dist/templates/solo-rpg/agent.test.ts +227 -48
  114. package/dist/templates/solo-rpg/client.tsx +38 -37
  115. package/dist/templates/solo-rpg/shared.ts +145 -19
  116. package/dist/templates/solo-rpg/system-prompt.md +3 -2
  117. package/dist/templates/solo-rpg/tools/action_roll.ts +82 -63
  118. package/dist/templates/solo-rpg/tools/burn_momentum.ts +85 -50
  119. package/dist/templates/solo-rpg/tools/check_state.ts +24 -7
  120. package/dist/templates/solo-rpg/tools/load_game.ts +13 -1
  121. package/dist/templates/solo-rpg/tools/save_game.ts +16 -7
  122. package/dist/templates/solo-rpg/tools/setup_character.ts +22 -2
  123. package/dist/templates/solo-rpg/tools/update_state.ts +113 -100
  124. package/dist/templates/spoken-summary/agent.test.ts +314 -0
  125. package/dist/templates/spoken-summary/agent.ts +155 -0
  126. package/dist/templates/spoken-summary/client.tsx +185 -0
  127. package/dist/templates/spoken-summary/workflows/summarize.ts +237 -0
  128. package/dist/templates/spoken-summary/workflows/transcribe.ts +138 -0
  129. package/dist/templates/support-line/agent.test.ts +11 -16
  130. package/dist/templates/support-line/agent.ts +1 -1
  131. package/dist/templates/support-line/client.tsx +9 -9
  132. package/dist/templates/support-line/nodes.ts +100 -0
  133. package/dist/templates/support-line/procedure.ts +407 -0
  134. package/dist/templates/support-line/tools/answer_question.ts +17 -4
  135. package/dist/templates/transcription-workflow/agent.test.ts +246 -123
  136. package/dist/templates/transcription-workflow/agent.ts +21 -7
  137. package/dist/templates/transcription-workflow/client.tsx +17 -42
  138. package/dist/templates/transcription-workflow/workflows/batch.ts +79 -180
  139. package/dist/templates/transcription-workflow/workflows/normalize.ts +247 -0
  140. package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
  141. package/dist/templates/transcription-workflow/workflows/stream.ts +10 -9
  142. package/dist/templates/transcription-workflow/workflows/sync-api.ts +26 -94
  143. package/dist/templates/transcription-workflow/workflows/transcribe.ts +55 -40
  144. package/dist/templates/transcription-workflow/workflows/wav.ts +31 -31
  145. package/dist/templates/travel-concierge/agent.test.ts +64 -33
  146. package/dist/templates/travel-concierge/client.tsx +11 -23
  147. package/dist/templates/travel-concierge/routing.ts +34 -15
  148. package/dist/templates/travel-concierge/shared.ts +70 -3
  149. package/dist/templates/travel-concierge/tools/book_car_rental.ts +2 -2
  150. package/dist/templates/travel-concierge/tools/book_excursion.ts +2 -2
  151. package/dist/templates/travel-concierge/tools/book_hotel.ts +2 -2
  152. package/dist/templates/travel-concierge/tools/cancel_ticket.ts +2 -2
  153. package/dist/templates/travel-concierge/tools/update_ticket.ts +2 -2
  154. package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-COxnqstQ.mjs} +221 -3
  155. package/dist/worker-bundler.mjs +1 -1
  156. package/dist/{workflow-CFpxOFfQ.mjs → workflow-D2AQf2Pl.mjs} +28 -1
  157. package/dist/workflow-bundler.d.ts +6 -1
  158. package/dist/workflow.d.ts +1 -1
  159. package/package.json +5 -4
  160. package/dist/templates/support-line/graph.ts +0 -224
  161. package/dist/{_config-D_s09e7g.mjs → _config-CmJOFsAP.mjs} +1 -1
  162. /package/dist/templates/plan-and-execute/{graph.ts → procedure.ts} +0 -0
@@ -3,14 +3,22 @@ import { z } from "zod";
3
3
  import {
4
4
  assertNotResolved,
5
5
  calculateTriageScore,
6
+ callFlow,
6
7
  dispatchSlot,
7
8
  findIncident,
8
9
  logEvent,
9
10
  recommendResources,
10
11
  } from "../shared.ts";
11
12
 
12
- export default dispatchSlot.updateTool({
13
+ /**
14
+ * Gated on `working`, and `ESCALATED` puts the call back in `dispatching`: an
15
+ * incident that outgrew its response needs more units, which is exactly that
16
+ * state's instruction.
17
+ */
18
+ export default callFlow.tool({
13
19
  description: "Escalate an incident when it exceeds current capacity or severity increases.",
20
+ when: "working",
21
+ send: { type: "ESCALATED" },
14
22
  inputSchema: z.object({
15
23
  incidentId: z.string().max(20).describe("The incident ID"),
16
24
  reason: z.string().max(1000).describe("Reason for escalation"),
@@ -20,67 +28,68 @@ export default dispatchSlot.updateTool({
20
28
  .optional(),
21
29
  newSeverity: z.enum(["critical", "urgent"]).describe("Escalated severity level").optional(),
22
30
  }),
23
- execute(args, state) {
24
- const inc = findIncident(state, args.incidentId);
25
- if (isToolFailure(inc)) return inc;
26
- const blocked = assertNotResolved(inc, "escalate");
27
- if (blocked) return blocked;
31
+ execute: (args, ctx) =>
32
+ dispatchSlot.update(ctx, (state) => {
33
+ const inc = findIncident(state, args.incidentId);
34
+ if (isToolFailure(inc)) return inc;
35
+ const blocked = assertNotResolved(inc, "escalate");
36
+ if (blocked) return blocked;
28
37
 
29
- inc.escalationLevel++;
30
- if (args.newSeverity) inc.severity = args.newSeverity;
31
- inc.status = "escalated";
32
- logEvent(inc, `ESCALATED (Level ${inc.escalationLevel}): ${args.reason}`);
38
+ inc.escalationLevel++;
39
+ if (args.newSeverity) inc.severity = args.newSeverity;
40
+ inc.status = "escalated";
41
+ logEvent(inc, `ESCALATED (Level ${inc.escalationLevel}): ${args.reason}`);
33
42
 
34
- if (args.requestMutualAid) {
35
- logEvent(inc, "Mutual aid requested from neighboring jurisdictions");
36
- // Counter-based ids/callsigns: Date.now() collides across rapid
37
- // escalations, and duplicate callsigns break dispatch-by-callsign.
38
- const medicN = ++state.mutualAidCounter;
39
- const engineN = ++state.mutualAidCounter;
40
- state.resources.push(
41
- {
42
- id: `MA-${medicN}`,
43
- type: "ambulance",
44
- callsign: `Mutual-Aid-Medic-${medicN}`,
45
- status: "available",
46
- assignedIncident: null,
47
- eta: null,
48
- capabilities: ["als"],
49
- },
50
- {
51
- id: `MA-${engineN}`,
52
- type: "fire_engine",
53
- callsign: `Mutual-Aid-Engine-${engineN}`,
54
- status: "available",
55
- assignedIncident: null,
56
- eta: null,
57
- capabilities: ["structural"],
58
- },
59
- );
60
- }
43
+ if (args.requestMutualAid) {
44
+ logEvent(inc, "Mutual aid requested from neighboring jurisdictions");
45
+ // Counter-based ids/callsigns: Date.now() collides across rapid
46
+ // escalations, and duplicate callsigns break dispatch-by-callsign.
47
+ const medicN = ++state.mutualAidCounter;
48
+ const engineN = ++state.mutualAidCounter;
49
+ state.resources.push(
50
+ {
51
+ id: `MA-${medicN}`,
52
+ type: "ambulance",
53
+ callsign: `Mutual-Aid-Medic-${medicN}`,
54
+ status: "available",
55
+ assignedIncident: null,
56
+ eta: null,
57
+ capabilities: ["als"],
58
+ },
59
+ {
60
+ id: `MA-${engineN}`,
61
+ type: "fire_engine",
62
+ callsign: `Mutual-Aid-Engine-${engineN}`,
63
+ status: "available",
64
+ assignedIncident: null,
65
+ eta: null,
66
+ capabilities: ["structural"],
67
+ },
68
+ );
69
+ }
61
70
 
62
- inc.triageScore = calculateTriageScore(
63
- inc.severity,
64
- inc.type,
65
- inc.casualties.estimated,
66
- inc.hazards.length,
67
- );
71
+ inc.triageScore = calculateTriageScore(
72
+ inc.severity,
73
+ inc.type,
74
+ inc.casualties.estimated,
75
+ inc.hazards.length,
76
+ );
68
77
 
69
- const additionalResources = recommendResources(inc.type, inc.severity, state).filter(
70
- (r) => !inc.assignedResources.includes(r.id),
71
- );
78
+ const additionalResources = recommendResources(inc.type, inc.severity, state).filter(
79
+ (r) => !inc.assignedResources.includes(r.id),
80
+ );
72
81
 
73
- return {
74
- incidentId: args.incidentId,
75
- escalationLevel: inc.escalationLevel,
76
- newSeverity: inc.severity,
77
- newTriageScore: inc.triageScore,
78
- mutualAidRequested: args.requestMutualAid ?? false,
79
- additionalResourcesAvailable: additionalResources.map((r) => ({
80
- callsign: r.callsign,
81
- type: r.type,
82
- })),
83
- message: `ESCALATION CONFIRMED — ${args.incidentId} now Level ${inc.escalationLevel}. ${additionalResources.length} additional resource(s) available for dispatch.`,
84
- };
85
- },
82
+ return {
83
+ incidentId: args.incidentId,
84
+ escalationLevel: inc.escalationLevel,
85
+ newSeverity: inc.severity,
86
+ newTriageScore: inc.triageScore,
87
+ mutualAidRequested: args.requestMutualAid ?? false,
88
+ additionalResourcesAvailable: additionalResources.map((r) => ({
89
+ callsign: r.callsign,
90
+ type: r.type,
91
+ })),
92
+ message: `ESCALATION CONFIRMED — ${args.incidentId} now Level ${inc.escalationLevel}. ${additionalResources.length} additional resource(s) available for dispatch.`,
93
+ };
94
+ }),
86
95
  });
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import {
4
4
  assertNotResolved,
5
5
  calculateTriageScore,
6
+ callFlow,
6
7
  dispatchSlot,
7
8
  findIncident,
8
9
  getApplicableProtocols,
@@ -12,9 +13,21 @@ import {
12
13
  SEVERITIES,
13
14
  } from "../shared.ts";
14
15
 
15
- export default dispatchSlot.updateTool({
16
+ /**
17
+ * Gated on `working`: there is nothing to triage until something has been
18
+ * logged, and the flow's refusal says so in the dispatcher's own terms where
19
+ * `findIncident` would have answered `Incident INC-0001 not found` — a data
20
+ * answer to a positional question.
21
+ *
22
+ * `TRIAGED` moves the call to `dispatching`, whose instruction is "assign
23
+ * units", so the sequencing the system prompt used to spell out tool by tool
24
+ * arrives in this tool's own result.
25
+ */
26
+ export default callFlow.tool({
16
27
  description:
17
28
  "Triage an incident — confirm or override severity, type, hazards, and casualty count.",
29
+ when: "working",
30
+ send: { type: "TRIAGED" },
18
31
  inputSchema: z.object({
19
32
  incidentId: z.string().max(20).describe("The incident ID"),
20
33
  severity: z.enum(SEVERITIES).describe("Confirmed severity after triage").optional(),
@@ -27,50 +40,51 @@ export default dispatchSlot.updateTool({
27
40
  casualtyUpdate: z.number().int().nonnegative().describe("Updated casualty count").optional(),
28
41
  notes: z.string().max(1000).describe("Triage notes").optional(),
29
42
  }),
30
- execute(args, state) {
31
- const inc = findIncident(state, args.incidentId);
32
- if (isToolFailure(inc)) return inc;
33
- const blocked = assertNotResolved(inc, "triage");
34
- if (blocked) return blocked;
43
+ execute: (args, ctx) =>
44
+ dispatchSlot.update(ctx, (state) => {
45
+ const inc = findIncident(state, args.incidentId);
46
+ if (isToolFailure(inc)) return inc;
47
+ const blocked = assertNotResolved(inc, "triage");
48
+ if (blocked) return blocked;
35
49
 
36
- if (args.severity) inc.severity = args.severity;
37
- if (args.type) inc.type = args.type;
38
- for (const hazard of args.additionalHazards ?? []) {
39
- if (!inc.hazards.includes(hazard)) inc.hazards.push(hazard);
40
- }
41
- if (args.casualtyUpdate !== undefined) {
42
- inc.casualties.estimated = args.casualtyUpdate;
43
- }
44
- if (args.notes) logEvent(inc, `Triage note: ${args.notes}`);
50
+ if (args.severity) inc.severity = args.severity;
51
+ if (args.type) inc.type = args.type;
52
+ for (const hazard of args.additionalHazards ?? []) {
53
+ if (!inc.hazards.includes(hazard)) inc.hazards.push(hazard);
54
+ }
55
+ if (args.casualtyUpdate !== undefined) {
56
+ inc.casualties.estimated = args.casualtyUpdate;
57
+ }
58
+ if (args.notes) logEvent(inc, `Triage note: ${args.notes}`);
45
59
 
46
- inc.triageScore = calculateTriageScore(
47
- inc.severity,
48
- inc.type,
49
- inc.casualties.estimated,
50
- inc.hazards.length,
51
- );
52
- inc.status = "triaged";
53
- logEvent(inc, `Triaged: ${inc.severity} ${inc.type}, score ${inc.triageScore}`);
60
+ inc.triageScore = calculateTriageScore(
61
+ inc.severity,
62
+ inc.type,
63
+ inc.casualties.estimated,
64
+ inc.hazards.length,
65
+ );
66
+ inc.status = "triaged";
67
+ logEvent(inc, `Triaged: ${inc.severity} ${inc.type}, score ${inc.triageScore}`);
54
68
 
55
- const protocols = getApplicableProtocols(inc.type, inc.severity);
56
- const recommended = recommendResources(inc.type, inc.severity, state);
69
+ const protocols = getApplicableProtocols(inc.type, inc.severity);
70
+ const recommended = recommendResources(inc.type, inc.severity, state);
57
71
 
58
- return {
59
- incidentId: args.incidentId,
60
- severity: inc.severity,
61
- type: inc.type,
62
- triageScore: inc.triageScore,
63
- hazards: inc.hazards,
64
- estimatedCasualties: inc.casualties.estimated,
65
- protocols: protocols.map((p) => ({
66
- name: p.name,
67
- steps: p.steps,
68
- requiredResources: p.requiredResources,
69
- })),
70
- recommendedResources: recommended.map((r) => ({
71
- callsign: r.callsign,
72
- type: r.type,
73
- })),
74
- };
75
- },
72
+ return {
73
+ incidentId: args.incidentId,
74
+ severity: inc.severity,
75
+ type: inc.type,
76
+ triageScore: inc.triageScore,
77
+ hazards: inc.hazards,
78
+ estimatedCasualties: inc.casualties.estimated,
79
+ protocols: protocols.map((p) => ({
80
+ name: p.name,
81
+ steps: p.steps,
82
+ requiredResources: p.requiredResources,
83
+ })),
84
+ recommendedResources: recommended.map((r) => ({
85
+ callsign: r.callsign,
86
+ type: r.type,
87
+ })),
88
+ };
89
+ }),
76
90
  });
@@ -1,9 +1,17 @@
1
1
  import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
- import { assertNotResolved, dispatchSlot, findIncident, logEvent } from "../shared.ts";
3
+ import { assertNotResolved, callFlow, dispatchSlot, findIncident, logEvent } from "../shared.ts";
4
4
 
5
- export default dispatchSlot.updateTool({
5
+ /**
6
+ * Gated on `working`; sends nothing. A status update moves the INCIDENT, not
7
+ * the call — the dispatcher is still in `monitoring` afterwards, and closing
8
+ * one incident says nothing about the others still on the board. Omitting both
9
+ * `send` and `sendFrom` is how a flow tool reads without advancing, and the
10
+ * result still carries the position it read.
11
+ */
12
+ export default callFlow.tool({
6
13
  description: "Update an incident's status (en_route, on_scene, resolved, escalated).",
14
+ when: "working",
7
15
  inputSchema: z.object({
8
16
  incidentId: z.string().max(20).describe("The incident ID"),
9
17
  status: z.enum(["en_route", "on_scene", "resolved", "escalated"]).describe("New status"),
@@ -16,51 +24,52 @@ export default dispatchSlot.updateTool({
16
24
  .describe("Updated casualty numbers")
17
25
  .optional(),
18
26
  }),
19
- execute(args, state) {
20
- const inc = findIncident(state, args.incidentId);
21
- if (isToolFailure(inc)) return inc;
22
- const blocked = assertNotResolved(inc, `set status to ${args.status}`);
23
- if (blocked) return blocked;
27
+ execute: (args, ctx) =>
28
+ dispatchSlot.update(ctx, (state) => {
29
+ const inc = findIncident(state, args.incidentId);
30
+ if (isToolFailure(inc)) return inc;
31
+ const blocked = assertNotResolved(inc, `set status to ${args.status}`);
32
+ if (blocked) return blocked;
24
33
 
25
- inc.status = args.status;
26
- logEvent(inc, `Status → ${args.status}${args.notes ? `: ${args.notes}` : ""}`);
34
+ inc.status = args.status;
35
+ logEvent(inc, `Status → ${args.status}${args.notes ? `: ${args.notes}` : ""}`);
27
36
 
28
- if (args.casualtyUpdate) {
29
- if (args.casualtyUpdate.confirmed !== undefined) {
30
- inc.casualties.confirmed = args.casualtyUpdate.confirmed;
37
+ if (args.casualtyUpdate) {
38
+ if (args.casualtyUpdate.confirmed !== undefined) {
39
+ inc.casualties.confirmed = args.casualtyUpdate.confirmed;
40
+ }
41
+ if (args.casualtyUpdate.treated !== undefined) {
42
+ inc.casualties.treated = args.casualtyUpdate.treated;
43
+ }
31
44
  }
32
- if (args.casualtyUpdate.treated !== undefined) {
33
- inc.casualties.treated = args.casualtyUpdate.treated;
34
- }
35
- }
36
45
 
37
- // Only touch resources still assigned to THIS incident — a unit that
38
- // radioed available and was re-dispatched elsewhere belongs to its
39
- // new incident now.
40
- const ownResources = state.resources.filter((r) => r.assignedIncident === inc.id);
46
+ // Only touch resources still assigned to THIS incident — a unit that
47
+ // radioed available and was re-dispatched elsewhere belongs to its
48
+ // new incident now.
49
+ const ownResources = state.resources.filter((r) => r.assignedIncident === inc.id);
41
50
 
42
- if (args.status === "resolved") {
43
- for (const r of ownResources) {
44
- r.status = "returning";
45
- r.assignedIncident = null;
46
- r.eta = null;
51
+ if (args.status === "resolved") {
52
+ for (const r of ownResources) {
53
+ r.status = "returning";
54
+ r.assignedIncident = null;
55
+ r.eta = null;
56
+ }
57
+ inc.assignedResources = [];
58
+ logEvent(inc, "All resources released — incident closed");
47
59
  }
48
- inc.assignedResources = [];
49
- logEvent(inc, "All resources released — incident closed");
50
- }
51
60
 
52
- if (args.status === "en_route" || args.status === "on_scene") {
53
- for (const r of ownResources) {
54
- r.status = args.status;
55
- if (args.status === "on_scene") r.eta = null;
61
+ if (args.status === "en_route" || args.status === "on_scene") {
62
+ for (const r of ownResources) {
63
+ r.status = args.status;
64
+ if (args.status === "on_scene") r.eta = null;
65
+ }
56
66
  }
57
- }
58
67
 
59
- return {
60
- incidentId: args.incidentId,
61
- newStatus: args.status,
62
- timeline: inc.timeline.slice(-5).map((t) => t.event),
63
- casualties: inc.casualties,
64
- };
65
- },
68
+ return {
69
+ incidentId: args.incidentId,
70
+ newStatus: args.status,
71
+ timeline: inc.timeline.slice(-5).map((t) => t.event),
72
+ casualties: inc.casualties,
73
+ };
74
+ }),
66
75
  });
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import type { Incident, IncidentType, Severity } from "../shared.ts";
3
- import { calculateTriageScore, createIncident, dispatchSlot } from "../shared.ts";
3
+ import { calculateTriageScore, callFlow, createIncident, dispatchSlot } from "../shared.ts";
4
4
 
5
5
  type ScenarioIncident = Pick<Incident, "location" | "description" | "type" | "severity">;
6
6
  type ScenarioDef = { narrative: string; incidents: ScenarioIncident[] };
@@ -116,12 +116,15 @@ const scenarios = {
116
116
  type ScenarioName = keyof typeof scenarios;
117
117
  const SCENARIO_NAMES = Object.keys(scenarios) as [ScenarioName, ...ScenarioName[]];
118
118
 
119
+ /** Logs incidents like a real call does, so it sends `LOGGED` for the same
120
+ * reason `incident_create` does — and is ungated for the same reason too: a
121
+ * drill may be started at any point in a shift. */
119
122
  export default dispatchSlot.updateTool({
120
123
  description: "Run a training scenario that creates simulated incidents for dispatch practice.",
121
124
  inputSchema: z.object({
122
125
  scenario: z.enum(SCENARIO_NAMES).describe("Scenario type to simulate"),
123
126
  }),
124
- execute(args, state) {
127
+ execute(args, state, ctx) {
125
128
  const s = scenarios[args.scenario];
126
129
 
127
130
  const created: string[] = [];
@@ -140,6 +143,9 @@ export default dispatchSlot.updateTool({
140
143
  scenario: args.scenario,
141
144
  narrative: s.narrative,
142
145
  incidentsCreated: created,
146
+ // As `incident_create`: the position spread verbatim, so an ungated tool
147
+ // reports it under the same keys a gated one does.
148
+ ...callFlow.send(ctx, { type: "LOGGED" }),
143
149
  message: `SCENARIO ACTIVE: ${s.narrative}. ${created.length} incidents created. Awaiting dispatch orders.`,
144
150
  };
145
151
  },
@@ -3,15 +3,22 @@ import { z } from "zod";
3
3
  import type { Resource } from "../shared.ts";
4
4
  import {
5
5
  assertNotResolved,
6
+ callFlow,
6
7
  dispatchSlot,
7
8
  findIncident,
8
9
  logEvent,
9
10
  recommendResources,
10
11
  } from "../shared.ts";
11
12
 
12
- export default dispatchSlot.updateTool({
13
+ /**
14
+ * Gated on `working`, and legal from `triaging` as well as `dispatching` — the
15
+ * prompt's "critical incidents get immediate dispatch, triage can happen
16
+ * simultaneously" is why the gate is the parent state rather than one child.
17
+ */
18
+ export default callFlow.tool({
13
19
  description:
14
20
  "Dispatch units to an incident. Can auto-dispatch recommended resources or manually specify callsigns.",
21
+ when: "working",
15
22
  inputSchema: z.object({
16
23
  incidentId: z.string().max(20).describe("The incident ID"),
17
24
  callsigns: z
@@ -30,66 +37,76 @@ export default dispatchSlot.updateTool({
30
37
  .describe("Dispatch priority — affects simulated ETA")
31
38
  .optional(),
32
39
  }),
33
- execute(args, state) {
34
- const inc = findIncident(state, args.incidentId);
35
- if (isToolFailure(inc)) return inc;
36
- const blocked = assertNotResolved(inc, "dispatch resources");
37
- if (blocked) return blocked;
40
+ execute: (args, ctx) =>
41
+ dispatchSlot.update(ctx, (state) => {
42
+ const inc = findIncident(state, args.incidentId);
43
+ if (isToolFailure(inc)) return inc;
44
+ const blocked = assertNotResolved(inc, "dispatch resources");
45
+ if (blocked) return blocked;
38
46
 
39
- const dispatched: { callsign: string; type: string; eta: number }[] = [];
40
- const failed: { callsign: string; reason: string }[] = [];
47
+ const dispatched: { callsign: string; type: string; eta: number }[] = [];
48
+ const failed: { callsign: string; reason: string }[] = [];
41
49
 
42
- // The literal "auto" callsign means the same thing as autoDispatch.
43
- const wantsAuto =
44
- args.autoDispatch || args.callsigns?.some((cs) => cs.toLowerCase() === "auto");
50
+ // The literal "auto" callsign means the same thing as autoDispatch.
51
+ const wantsAuto =
52
+ args.autoDispatch || args.callsigns?.some((cs) => cs.toLowerCase() === "auto");
45
53
 
46
- let resourcesToDispatch: Resource[] = [];
54
+ let resourcesToDispatch: Resource[] = [];
47
55
 
48
- if (wantsAuto) {
49
- resourcesToDispatch = recommendResources(inc.type, inc.severity, state);
50
- } else if (args.callsigns) {
51
- for (const cs of args.callsigns) {
52
- const r = state.resources.find((r) => r.callsign.toLowerCase() === cs.toLowerCase());
53
- if (!r) {
54
- failed.push({ callsign: cs, reason: "Not found" });
55
- continue;
56
+ if (wantsAuto) {
57
+ resourcesToDispatch = recommendResources(inc.type, inc.severity, state);
58
+ } else if (args.callsigns) {
59
+ for (const cs of args.callsigns) {
60
+ const r = state.resources.find((r) => r.callsign.toLowerCase() === cs.toLowerCase());
61
+ if (!r) {
62
+ failed.push({ callsign: cs, reason: "Not found" });
63
+ continue;
64
+ }
65
+ if (r.status !== "available") {
66
+ failed.push({ callsign: cs, reason: `Currently ${r.status}` });
67
+ continue;
68
+ }
69
+ resourcesToDispatch.push(r);
56
70
  }
57
- if (r.status !== "available") {
58
- failed.push({ callsign: cs, reason: `Currently ${r.status}` });
59
- continue;
60
- }
61
- resourcesToDispatch.push(r);
62
71
  }
63
- }
64
72
 
65
- const etaBase = args.priority === "emergency" ? 3 : args.priority === "priority" ? 6 : 10;
73
+ const etaBase = args.priority === "emergency" ? 3 : args.priority === "priority" ? 6 : 10;
66
74
 
67
- for (const r of resourcesToDispatch) {
68
- const eta = etaBase + Math.floor(Math.random() * 5);
69
- r.status = "dispatched";
70
- r.assignedIncident = args.incidentId;
71
- r.eta = eta;
72
- inc.assignedResources.push(r.id);
73
- dispatched.push({ callsign: r.callsign, type: r.type, eta });
74
- logEvent(inc, `Dispatched ${r.callsign} — ETA ${eta} min`);
75
- }
75
+ for (const r of resourcesToDispatch) {
76
+ const eta = etaBase + Math.floor(Math.random() * 5);
77
+ r.status = "dispatched";
78
+ r.assignedIncident = args.incidentId;
79
+ r.eta = eta;
80
+ inc.assignedResources.push(r.id);
81
+ dispatched.push({ callsign: r.callsign, type: r.type, eta });
82
+ logEvent(inc, `Dispatched ${r.callsign} — ETA ${eta} min`);
83
+ }
76
84
 
77
- if (dispatched.length > 0) {
78
- inc.status = "dispatched";
79
- }
85
+ if (dispatched.length > 0) {
86
+ inc.status = "dispatched";
87
+ }
80
88
 
81
- const availableCount = state.resources.filter((r) => r.status === "available").length;
89
+ const availableCount = state.resources.filter((r) => r.status === "available").length;
82
90
 
83
- return {
84
- incidentId: args.incidentId,
85
- dispatched,
86
- failed: failed.length > 0 ? failed : undefined,
87
- totalAssignedToIncident: inc.assignedResources.length,
88
- remainingAvailableResources: availableCount,
89
- capacityWarning:
90
- availableCount <= 3
91
- ? "WARNING: Resource capacity critically low. Consider mutual aid."
92
- : undefined,
93
- };
94
- },
91
+ return {
92
+ incidentId: args.incidentId,
93
+ dispatched,
94
+ failed: failed.length > 0 ? failed : undefined,
95
+ totalAssignedToIncident: inc.assignedResources.length,
96
+ remainingAvailableResources: availableCount,
97
+ capacityWarning:
98
+ availableCount <= 3
99
+ ? "WARNING: Resource capacity critically low. Consider mutual aid."
100
+ : undefined,
101
+ };
102
+ }),
103
+ // Only when units actually rolled: a dispatch whose every requested callsign
104
+ // was busy answers `dispatched: []`, which is not a refusal but is not
105
+ // progress either, so the call stays where it is.
106
+ //
107
+ // `result` is the SUCCESS type — `sendFrom` takes `Exclude<R, ToolFailure>`,
108
+ // so the `"dispatched" in result` guard that used to stand in for the failure
109
+ // arm leaking into `R` is a plain property read.
110
+ sendFrom: (result) =>
111
+ result.dispatched.length > 0 ? { type: "DISPATCHED" as const } : undefined,
95
112
  });