@alexkroman1/aai-cli 6.11.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/README.md +1 -0
  2. package/dist/{_agent-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
  3. package/dist/{_bundler-C2BDwU7r.mjs → _bundler-CDuYl5Gb.mjs} +2 -2
  4. package/dist/_dev-env.d.ts +22 -2
  5. package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-Me06wgeF.mjs} +38 -47
  6. package/dist/_dev-server.d.ts +0 -17
  7. package/dist/{_init-C57bWAQ8.mjs → _init-BhEfQ6Yi.mjs} +3 -2
  8. package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
  9. package/dist/_preflight.d.ts +1 -1
  10. package/dist/_resource-commands.d.ts +23 -0
  11. package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-z-USxPtL.mjs} +16 -12
  12. package/dist/_slug-api.d.ts +5 -2
  13. package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
  14. package/dist/{_templates-Dxb_P2Wz.mjs → _templates-4WcKOjS5.mjs} +5 -6
  15. package/dist/_templates.d.ts +3 -4
  16. package/dist/_workflow-scan.d.ts +108 -0
  17. package/dist/{build-Dfza2pRI.mjs → build-BBxf8vom.mjs} +6 -3
  18. package/dist/cli.mjs +346 -349
  19. package/dist/{client-bundler-BEIqgOtd.mjs → client-bundler-CWnG42cU.mjs} +2 -1
  20. package/dist/client-bundler.mjs +1 -1
  21. package/dist/{delete-vbpjGzqs.mjs → delete-Dl_LSfvI.mjs} +3 -3
  22. package/dist/{deploy-B8lubiRT.mjs → deploy-DRC8jWr8.mjs} +5 -5
  23. package/dist/{dev-Uq5ujP8a.mjs → dev-BvcctCn2.mjs} +1 -1
  24. package/dist/eval-DfQ8cm5p.mjs +56 -0
  25. package/dist/eval.d.ts +43 -0
  26. package/dist/{init-l6xfU4xX.mjs → init-CUo87gcI.mjs} +3 -3
  27. package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
  28. package/dist/{logs-BWVGSjLR.mjs → logs-CQ2T46dr.mjs} +2 -2
  29. package/dist/project-config.mjs +1 -1
  30. package/dist/scaffold/CLAUDE.md +442 -80
  31. package/dist/scaffold/package.json +6 -3
  32. package/dist/scaffold/server.mjs +11 -4
  33. package/dist/scaffold/tsconfig.json +0 -1
  34. package/dist/{secret-BuMuFR4B.mjs → secret-Ct34kdgU.mjs} +2 -2
  35. package/dist/{storage-DzRZ-eCw.mjs → storage-i_79ZwAR.mjs} +2 -2
  36. package/dist/{studio-BCNUpDgP.mjs → studio-CzTzPqe1.mjs} +4 -4
  37. package/dist/templates/briefing-desk/agent.test.ts +350 -0
  38. package/dist/templates/briefing-desk/agent.ts +30 -0
  39. package/dist/templates/briefing-desk/shared.ts +200 -0
  40. package/dist/templates/briefing-desk/system-prompt.md +39 -0
  41. package/dist/templates/briefing-desk/tools/briefing_so_far.ts +35 -0
  42. package/dist/templates/briefing-desk/tools/research_topic.ts +88 -0
  43. package/dist/templates/briefing-desk/tools/verify_claim.ts +77 -0
  44. package/dist/templates/call-audit/agent.eval.test.ts +474 -0
  45. package/dist/templates/call-audit/agent.test.ts +57 -204
  46. package/dist/templates/call-audit/agent.ts +32 -19
  47. package/dist/templates/call-audit/client.tsx +17 -54
  48. package/dist/templates/call-audit/workflows/audit.ts +11 -18
  49. package/dist/templates/call-audit/workflows/ingest.ts +106 -114
  50. package/dist/templates/call-audit/workflows/media.ts +2 -12
  51. package/dist/templates/call-audit/workflows/summarize.ts +47 -52
  52. package/dist/templates/call-audit/workflows/sync-api.ts +7 -7
  53. package/dist/templates/code-interpreter/agent.eval.test.ts +165 -0
  54. package/dist/templates/dispatch-center/agent.eval.test.ts +349 -0
  55. package/dist/templates/dispatch-center/agent.test.ts +149 -25
  56. package/dist/templates/dispatch-center/client.tsx +239 -129
  57. package/dist/templates/dispatch-center/shared.ts +99 -1
  58. package/dist/templates/dispatch-center/system-prompt.md +3 -1
  59. package/dist/templates/dispatch-center/tools/incident_add_note.ts +16 -13
  60. package/dist/templates/dispatch-center/tools/incident_create.ts +19 -1
  61. package/dist/templates/dispatch-center/tools/incident_escalate.ts +68 -59
  62. package/dist/templates/dispatch-center/tools/incident_triage.ts +57 -43
  63. package/dist/templates/dispatch-center/tools/incident_update_status.ts +49 -40
  64. package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +8 -2
  65. package/dist/templates/dispatch-center/tools/resources_dispatch.ts +69 -52
  66. package/dist/templates/dispatch-center/tools/resources_update_status.ts +45 -41
  67. package/dist/templates/embedded-assets/agent.eval.test.ts +138 -0
  68. package/dist/templates/embedded-assets/agent.test.ts +17 -14
  69. package/dist/templates/health-assistant/agent.eval.test.ts +167 -0
  70. package/dist/templates/health-assistant/agent.test.ts +22 -4
  71. package/dist/templates/infocom-adventure/agent.eval.test.ts +158 -0
  72. package/dist/templates/infocom-adventure/agent.test.ts +53 -24
  73. package/dist/templates/infocom-adventure/agent.ts +21 -0
  74. package/dist/templates/infocom-adventure/client.tsx +246 -164
  75. package/dist/templates/infocom-adventure/shared.ts +20 -9
  76. package/dist/templates/infocom-adventure/system-prompt.md +1 -1
  77. package/dist/templates/infocom-adventure/tools/game_state_move.ts +3 -2
  78. package/dist/templates/link-digest/agent.eval.test.ts +195 -0
  79. package/dist/templates/link-digest/agent.test.ts +24 -19
  80. package/dist/templates/link-digest/client.tsx +47 -61
  81. package/dist/templates/link-digest/workflows/digest.ts +19 -31
  82. package/dist/templates/math-buddy/agent.eval.test.ts +146 -0
  83. package/dist/templates/night-owl/agent.eval.test.ts +272 -0
  84. package/dist/templates/night-owl/agent.test.ts +70 -19
  85. package/dist/templates/night-owl/agent.ts +5 -0
  86. package/dist/templates/night-owl/client.tsx +56 -56
  87. package/dist/templates/night-owl/shared.ts +24 -0
  88. package/dist/templates/night-owl/tools/recommend.ts +18 -6
  89. package/dist/templates/personal-finance/agent.eval.test.ts +168 -0
  90. package/dist/templates/personal-finance/system-prompt.md +4 -0
  91. package/dist/templates/pipeline-simple/agent.eval.test.ts +78 -0
  92. package/dist/templates/pipeline-simple/agent.ts +2 -2
  93. package/dist/templates/pizza-ordering/agent.eval.test.ts +171 -0
  94. package/dist/templates/pizza-ordering/agent.test.ts +42 -27
  95. package/dist/templates/pizza-ordering/client.tsx +9 -26
  96. package/dist/templates/pizza-ordering/shared.ts +20 -1
  97. package/dist/templates/pizza-ordering/system-prompt.md +6 -0
  98. package/dist/templates/plan-and-execute/agent.eval.test.ts +209 -0
  99. package/dist/templates/plan-and-execute/agent.test.ts +99 -58
  100. package/dist/templates/plan-and-execute/agent.ts +1 -1
  101. package/dist/templates/plan-and-execute/client.tsx +12 -15
  102. package/dist/templates/plan-and-execute/shared.ts +71 -2
  103. package/dist/templates/plan-and-execute/tools/plan_status.ts +18 -6
  104. package/dist/templates/plan-and-execute/tools/revise_plan.ts +18 -10
  105. package/dist/templates/plan-and-execute/tools/start_plan.ts +13 -2
  106. package/dist/templates/plan-and-execute/tools/work_next_step.ts +70 -22
  107. package/dist/templates/podcast-digest/agent.eval.test.ts +411 -0
  108. package/dist/templates/podcast-digest/agent.test.ts +717 -0
  109. package/dist/templates/podcast-digest/agent.ts +139 -0
  110. package/dist/templates/podcast-digest/client.tsx +154 -0
  111. package/dist/templates/podcast-digest/workflows/digest.ts +444 -0
  112. package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
  113. package/dist/templates/podcast-digest/workflows/slack.ts +88 -0
  114. package/dist/templates/recap-workflow/agent.eval.test.ts +366 -0
  115. package/dist/templates/recap-workflow/agent.test.ts +97 -94
  116. package/dist/templates/recap-workflow/tools/recap_progress.ts +9 -12
  117. package/dist/templates/recap-workflow/tools/recap_status.ts +8 -9
  118. package/dist/templates/recap-workflow/tools/request_recap.ts +2 -1
  119. package/dist/templates/recap-workflow/workflows/recap.ts +36 -40
  120. package/dist/templates/redline/agent.eval.test.ts +209 -0
  121. package/dist/templates/redline/agent.test.ts +17 -15
  122. package/dist/templates/redline/client.tsx +12 -12
  123. package/dist/templates/redline/workflows/redline.ts +19 -31
  124. package/dist/templates/research-workflow/agent.eval.test.ts +329 -0
  125. package/dist/templates/research-workflow/agent.test.ts +60 -59
  126. package/dist/templates/research-workflow/tools/research_progress.ts +9 -12
  127. package/dist/templates/research-workflow/tools/research_status.ts +9 -11
  128. package/dist/templates/research-workflow/workflows/research.ts +44 -61
  129. package/dist/templates/retail/address.ts +87 -1
  130. package/dist/templates/retail/agent.eval.test.ts +314 -0
  131. package/dist/templates/retail/agent.test.ts +195 -112
  132. package/dist/templates/retail/agent.ts +35 -1
  133. package/dist/templates/retail/cancel.ts +116 -0
  134. package/dist/templates/retail/client.tsx +265 -117
  135. package/dist/templates/retail/payment.ts +107 -0
  136. package/dist/templates/retail/pending.test.ts +196 -0
  137. package/dist/templates/retail/pending.ts +137 -0
  138. package/dist/templates/retail/registry.test.ts +150 -7
  139. package/dist/templates/retail/returns.ts +102 -0
  140. package/dist/templates/retail/shared.test.ts +42 -0
  141. package/dist/templates/retail/shared.ts +35 -0
  142. package/dist/templates/retail/store.test.ts +84 -15
  143. package/dist/templates/retail/store.ts +231 -48
  144. package/dist/templates/retail/swap.test.ts +4 -4
  145. package/dist/templates/retail/swap.ts +248 -15
  146. package/dist/templates/retail/system-prompt.md +69 -28
  147. package/dist/templates/retail/tools/cancel_change.ts +33 -0
  148. package/dist/templates/retail/tools/cancel_pending_order.ts +16 -60
  149. package/dist/templates/retail/tools/confirm_change.ts +42 -0
  150. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -67
  151. package/dist/templates/retail/tools/find_user_id_by_email.ts +8 -5
  152. package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +4 -5
  153. package/dist/templates/retail/tools/get_item_details.ts +3 -4
  154. package/dist/templates/retail/tools/get_order_details.ts +3 -4
  155. package/dist/templates/retail/tools/get_product_details.ts +3 -4
  156. package/dist/templates/retail/tools/get_user_details.ts +2 -2
  157. package/dist/templates/retail/tools/list_all_product_types.ts +5 -5
  158. package/dist/templates/retail/tools/modify_pending_order_address.ts +13 -25
  159. package/dist/templates/retail/tools/modify_pending_order_items.ts +23 -62
  160. package/dist/templates/retail/tools/modify_pending_order_payment.ts +16 -78
  161. package/dist/templates/retail/tools/modify_user_address.ts +13 -16
  162. package/dist/templates/retail/tools/return_delivered_order_items.ts +17 -72
  163. package/dist/templates/retail/tools/transfer_to_human_agents.ts +12 -6
  164. package/dist/templates/simple/agent.eval.test.ts +54 -0
  165. package/dist/templates/solo-rpg/agent.eval.test.ts +320 -0
  166. package/dist/templates/solo-rpg/agent.test.ts +227 -48
  167. package/dist/templates/solo-rpg/client.tsx +38 -37
  168. package/dist/templates/solo-rpg/shared.ts +145 -19
  169. package/dist/templates/solo-rpg/system-prompt.md +3 -2
  170. package/dist/templates/solo-rpg/tools/action_roll.ts +82 -63
  171. package/dist/templates/solo-rpg/tools/burn_momentum.ts +85 -50
  172. package/dist/templates/solo-rpg/tools/check_state.ts +24 -7
  173. package/dist/templates/solo-rpg/tools/load_game.ts +13 -1
  174. package/dist/templates/solo-rpg/tools/save_game.ts +16 -7
  175. package/dist/templates/solo-rpg/tools/setup_character.ts +22 -2
  176. package/dist/templates/solo-rpg/tools/update_state.ts +113 -100
  177. package/dist/templates/spoken-summary/agent.eval.test.ts +342 -0
  178. package/dist/templates/spoken-summary/agent.test.ts +68 -97
  179. package/dist/templates/spoken-summary/agent.ts +30 -17
  180. package/dist/templates/spoken-summary/client.tsx +10 -50
  181. package/dist/templates/spoken-summary/workflows/summarize.ts +17 -22
  182. package/dist/templates/spoken-summary/workflows/transcribe.ts +19 -26
  183. package/dist/templates/support-line/agent.eval.test.ts +273 -0
  184. package/dist/templates/support-line/agent.test.ts +11 -16
  185. package/dist/templates/support-line/agent.ts +1 -1
  186. package/dist/templates/support-line/client.tsx +9 -9
  187. package/dist/templates/support-line/nodes.ts +100 -0
  188. package/dist/templates/support-line/procedure.ts +407 -0
  189. package/dist/templates/support-line/system-prompt.md +2 -1
  190. package/dist/templates/support-line/tools/answer_question.ts +66 -4
  191. package/dist/templates/transcription-workflow/agent.eval.test.ts +234 -0
  192. package/dist/templates/transcription-workflow/agent.test.ts +94 -194
  193. package/dist/templates/transcription-workflow/agent.ts +1 -1
  194. package/dist/templates/transcription-workflow/client.tsx +17 -42
  195. package/dist/templates/transcription-workflow/workflows/batch.ts +19 -22
  196. package/dist/templates/transcription-workflow/workflows/normalize.ts +90 -186
  197. package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
  198. package/dist/templates/transcription-workflow/workflows/stream.ts +4 -5
  199. package/dist/templates/transcription-workflow/workflows/sync-api.ts +6 -6
  200. package/dist/templates/transcription-workflow/workflows/transcribe.ts +32 -26
  201. package/dist/templates/transcription-workflow/workflows/wav.ts +0 -31
  202. package/dist/templates/travel-concierge/agent.eval.test.ts +323 -0
  203. package/dist/templates/travel-concierge/agent.test.ts +174 -43
  204. package/dist/templates/travel-concierge/agent.ts +18 -9
  205. package/dist/templates/travel-concierge/client.tsx +11 -23
  206. package/dist/templates/travel-concierge/routing.ts +34 -15
  207. package/dist/templates/travel-concierge/shared.ts +180 -12
  208. package/dist/templates/travel-concierge/system-prompt.md +7 -1
  209. package/dist/templates/travel-concierge/tools/book_car_rental.ts +8 -5
  210. package/dist/templates/travel-concierge/tools/book_excursion.ts +8 -5
  211. package/dist/templates/travel-concierge/tools/book_hotel.ts +8 -5
  212. package/dist/templates/travel-concierge/tools/cancel_ticket.ts +8 -5
  213. package/dist/templates/travel-concierge/tools/search_car_rentals.ts +9 -5
  214. package/dist/templates/travel-concierge/tools/search_excursions.ts +9 -5
  215. package/dist/templates/travel-concierge/tools/search_flights.ts +9 -6
  216. package/dist/templates/travel-concierge/tools/search_hotels.ts +9 -5
  217. package/dist/templates/travel-concierge/tools/update_ticket.ts +8 -5
  218. package/dist/templates/web-researcher/agent.eval.test.ts +121 -0
  219. package/dist/templates/web-researcher/system-prompt.md +15 -4
  220. package/dist/{test-9kPKJI-w.mjs → test-BvyT6sAc.mjs} +24 -10
  221. package/dist/test.d.ts +29 -5
  222. package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-CCVEDjm1.mjs} +241 -3
  223. package/dist/worker-bundler.mjs +1 -1
  224. package/dist/{workflow-CFpxOFfQ.mjs → workflow-BflATMmU.mjs} +47 -12
  225. package/dist/workflow-bundler.d.ts +6 -1
  226. package/dist/workflow.d.ts +1 -1
  227. package/package.json +5 -4
  228. package/dist/eject-31gjtaHF.mjs +0 -116
  229. package/dist/eject.d.ts +0 -60
  230. package/dist/templates/call-audit/workflows/temp-media.ts +0 -138
  231. package/dist/templates/infocom-adventure/tools/game_state_history.ts +0 -14
  232. package/dist/templates/support-line/graph.ts +0 -224
  233. package/dist/{_config-D_s09e7g.mjs → _config-CmJOFsAP.mjs} +1 -1
  234. /package/dist/templates/plan-and-execute/{graph.ts → procedure.ts} +0 -0
@@ -1,6 +1,22 @@
1
+ /**
2
+ * Swapping items for other options of the same product — the validation both
3
+ * item changes share, and the two actions built on it (modifying a pending
4
+ * order, exchanging a delivered one) as plan/apply pairs.
5
+ *
6
+ * See `cancel.ts` for why every mutating action is split that way.
7
+ */
8
+
1
9
  import { isToolFailure, type ToolFailure } from "@alexkroman1/aai";
10
+ import { resolveOrder } from "./resolve.ts";
2
11
  import type { Order, OrderItem, RetailState, User, Variant } from "./shared.ts";
3
- import { findPaymentMethod, findProduct, findVariant, isGiftCard, money } from "./store.ts";
12
+ import {
13
+ authenticatedUser,
14
+ findPaymentMethod,
15
+ findProduct,
16
+ findVariant,
17
+ isGiftCard,
18
+ money,
19
+ } from "./store.ts";
4
20
 
5
21
  export interface SwapPair {
6
22
  /** Index into `order.items`. Matching by index rather than by "first item
@@ -17,12 +33,30 @@ export interface SwapPlan {
17
33
  }
18
34
 
19
35
  /**
20
- * Validate a proposed item swap and compute its price difference, without
21
- * mutating anything. Shared by `modify_pending_order_items` (pending, which
22
- * additionally forbids a no-op swap) and `exchange_delivered_order_items`
23
- * (delivered, which does not).
36
+ * One priced swap, flattened to primitives.
24
37
  *
25
- * Validation order matches tau2's so a bad call fails on the same check.
38
+ * A {@link SwapPair} holds live `OrderItem` and `Variant` references into the
39
+ * store, which is right for a plan computed and applied inside one tool call
40
+ * and wrong for one that waits in a session slot for the caller to say yes —
41
+ * see `CancelPlan` in `cancel.ts`. This is the storable form, and it carries enough that
42
+ * {@link applySwapLines} needs no lookup at all.
43
+ */
44
+ export interface SwapLine {
45
+ index: number;
46
+ fromItemId: string;
47
+ fromName: string;
48
+ fromOptions: Record<string, string>;
49
+ fromPrice: number;
50
+ toItemId: string;
51
+ toName: string;
52
+ toOptions: Record<string, string>;
53
+ toPrice: number;
54
+ }
55
+
56
+ /**
57
+ * Validate a proposed item swap and compute its price difference, without
58
+ * mutating anything. Shared by the pending-order change (which additionally
59
+ * forbids a no-op swap) and the delivered-order exchange (which does not).
26
60
  */
27
61
  export function planItemSwap(
28
62
  state: RetailState,
@@ -103,6 +137,23 @@ export function planItemSwap(
103
137
  return { pairs, diff: money(diff) };
104
138
  }
105
139
 
140
+ /** Flatten a freshly-computed plan into the form a staged action can hold. */
141
+ export function toSwapLines(plan: SwapPlan): SwapLine[] {
142
+ return plan.pairs.map((pair) => ({
143
+ index: pair.index,
144
+ fromItemId: pair.item.item_id,
145
+ fromName: pair.item.name,
146
+ fromOptions: { ...pair.item.options },
147
+ fromPrice: pair.item.price,
148
+ toItemId: pair.newVariant.item_id,
149
+ // Same product by construction — a swap can only reach another option of
150
+ // it — so the name is the item's own and the OPTIONS are what differ.
151
+ toName: pair.item.name,
152
+ toOptions: { ...pair.newVariant.options },
153
+ toPrice: pair.newVariant.price,
154
+ }));
155
+ }
156
+
106
157
  /**
107
158
  * Gate the price difference on the chosen payment method. Only a gift card has
108
159
  * a balance to run out of; a negative difference is a refund and never gated.
@@ -118,14 +169,196 @@ export function assertCanCoverDiff(user: User, methodId: string, diff: number):
118
169
  return null;
119
170
  }
120
171
 
121
- /** Apply a validated plan. Each line takes its OWN new variant's price, options
122
- * and name — see the module note on tau2's leaked loop variable. */
123
- export function applySwap(order: Order, plan: SwapPlan): void {
124
- for (const pair of plan.pairs) {
125
- const line = order.items[pair.index];
126
- if (!line) continue;
127
- line.item_id = pair.newVariant.item_id;
128
- line.price = pair.newVariant.price;
129
- line.options = pair.newVariant.options;
172
+ /** Apply validated lines. Each takes its OWN new option's price and options
173
+ * see the module note on tau2's leaked loop variable. */
174
+ export function applySwapLines(order: Order, lines: readonly SwapLine[]): void {
175
+ for (const line of lines) {
176
+ const item = order.items[line.index];
177
+ if (!item) continue;
178
+ item.item_id = line.toItemId;
179
+ item.price = line.toPrice;
180
+ item.options = { ...line.toOptions };
181
+ }
182
+ }
183
+
184
+ /** "the glass 2 litre one for the stainless steel 1 litre one" — how a swap is
185
+ * said out loud. Item numbers are never in it; the prompt forbids reading one. */
186
+ function describeLine(line: SwapLine): string {
187
+ const from = Object.values(line.fromOptions).join(" ");
188
+ const to = Object.values(line.toOptions).join(" ");
189
+ return `the ${from} ${line.fromName} for the ${to} one`;
190
+ }
191
+
192
+ function describeDiff(diff: number, methodId: string): string {
193
+ if (diff > 0) return `$${diff.toFixed(2)} charged to ${methodId}`;
194
+ if (diff < 0) return `$${Math.abs(diff).toFixed(2)} refunded to ${methodId}`;
195
+ return "no price difference";
196
+ }
197
+
198
+ // ─── Changing the items in a pending order ───────────────────────────────────
199
+
200
+ export interface ModifyItemsPlan {
201
+ readBack: string;
202
+ orderId: string;
203
+ lines: SwapLine[];
204
+ diff: number;
205
+ paymentMethodId: string;
206
+ }
207
+
208
+ export function planModifyItems(
209
+ state: RetailState,
210
+ spokenOrderId: string,
211
+ itemIds: string[],
212
+ newItemIds: string[],
213
+ paymentMethodId: string,
214
+ ): ModifyItemsPlan | ToolFailure {
215
+ const user = authenticatedUser(state);
216
+ if (isToolFailure(user)) return user;
217
+
218
+ const order = resolveOrder(state, spokenOrderId);
219
+ if (isToolFailure(order)) return order;
220
+
221
+ // Exactly 'pending'. A 'pending (item modified)' order has already used its
222
+ // one modification, which is what makes this action terminal.
223
+ if (order.status !== "pending") {
224
+ return {
225
+ error: `Order ${order.order_id} is ${order.status}. Items can only be changed while an order is exactly 'pending', and only once.`,
226
+ };
227
+ }
228
+
229
+ const plan = planItemSwap(state, order, itemIds, newItemIds, { requireDifferent: true });
230
+ if (isToolFailure(plan)) return plan;
231
+
232
+ const blocked = assertCanCoverDiff(user, paymentMethodId, plan.diff);
233
+ if (blocked) return blocked;
234
+
235
+ const lines = toSwapLines(plan);
236
+ return {
237
+ readBack:
238
+ `swap ${lines.map(describeLine).join(", and ")} on order ${order.order_id}, ` +
239
+ `with ${describeDiff(plan.diff, paymentMethodId)} — and this is the ONE change that order ` +
240
+ "allows: after it, it can no longer be cancelled or modified by anyone",
241
+ orderId: order.order_id,
242
+ lines,
243
+ diff: plan.diff,
244
+ paymentMethodId,
245
+ };
246
+ }
247
+
248
+ export function applyModifyItems(state: RetailState, plan: ModifyItemsPlan) {
249
+ const order = state.store.orders[plan.orderId];
250
+ const user = order ? state.store.users[order.user_id] : undefined;
251
+
252
+ if (order && user) {
253
+ const method = user.payment_methods[plan.paymentMethodId];
254
+ if (method && isGiftCard(method)) {
255
+ method.balance = money(method.balance - plan.diff);
256
+ }
257
+ order.payment_history.push({
258
+ transaction_type: plan.diff > 0 ? "payment" : "refund",
259
+ amount: money(Math.abs(plan.diff)),
260
+ payment_method_id: plan.paymentMethodId,
261
+ });
262
+ applySwapLines(order, plan.lines);
263
+ order.status = "pending (item modified)";
130
264
  }
265
+
266
+ return {
267
+ order_id: plan.orderId,
268
+ status: "pending (item modified)" as const,
269
+ price_difference: plan.diff,
270
+ items: (order?.items ?? []).map((item) => ({
271
+ name: item.name,
272
+ item_id: item.item_id,
273
+ options: item.options,
274
+ price: item.price,
275
+ })),
276
+ message:
277
+ plan.diff > 0
278
+ ? `Done. $${plan.diff.toFixed(2)} was charged to ${plan.paymentMethodId}. This order can no longer be modified or cancelled.`
279
+ : `Done. $${Math.abs(plan.diff).toFixed(2)} is being refunded to ${plan.paymentMethodId}. This order can no longer be modified or cancelled.`,
280
+ };
281
+ }
282
+
283
+ // ─── Exchanging a delivered order ────────────────────────────────────────────
284
+
285
+ export interface ExchangePlan {
286
+ readBack: string;
287
+ orderId: string;
288
+ lines: SwapLine[];
289
+ diff: number;
290
+ paymentMethodId: string;
291
+ }
292
+
293
+ export function planExchange(
294
+ state: RetailState,
295
+ spokenOrderId: string,
296
+ itemIds: string[],
297
+ newItemIds: string[],
298
+ paymentMethodId: string,
299
+ ): ExchangePlan | ToolFailure {
300
+ const user = authenticatedUser(state);
301
+ if (isToolFailure(user)) return user;
302
+
303
+ const order = resolveOrder(state, spokenOrderId);
304
+ if (isToolFailure(order)) return order;
305
+
306
+ if (order.status !== "delivered") {
307
+ return {
308
+ error: `Order ${order.order_id} is ${order.status}. Only a delivered order can be exchanged, and only once.`,
309
+ };
310
+ }
311
+
312
+ // requireDifferent is false: a zero-difference line is harmless on a
313
+ // delivered order, and refusing one would reject a caller who listed every
314
+ // item and changed their mind about only some.
315
+ const plan = planItemSwap(state, order, itemIds, newItemIds, { requireDifferent: false });
316
+ if (isToolFailure(plan)) return plan;
317
+
318
+ const blocked = assertCanCoverDiff(user, paymentMethodId, plan.diff);
319
+ if (blocked) return blocked;
320
+
321
+ const lines = toSwapLines(plan);
322
+ return {
323
+ readBack:
324
+ `exchange ${lines.map(describeLine).join(", and ")} on order ${order.order_id}, ` +
325
+ `with ${describeDiff(plan.diff, paymentMethodId)}`,
326
+ orderId: order.order_id,
327
+ lines,
328
+ diff: plan.diff,
329
+ paymentMethodId,
330
+ };
331
+ }
332
+
333
+ export function applyExchange(state: RetailState, plan: ExchangePlan) {
334
+ const order = state.store.orders[plan.orderId];
335
+
336
+ // The pairing AS PRICED, in the order the caller gave it. tau2 stored these
337
+ // as two INDEPENDENTLY SORTED lists, which permuted the second against the
338
+ // first whenever more than one item was named — two sets, not a pairing, and
339
+ // the result had to carry the real pairing separately to avoid telling the
340
+ // caller a quote that was not the one they got. Nothing here is compared
341
+ // against a tau2 end state any more, so the fields hold the pairing itself.
342
+ if (order) {
343
+ order.status = "exchange requested";
344
+ order.exchange_items = plan.lines.map((line) => line.fromItemId);
345
+ order.exchange_new_items = plan.lines.map((line) => line.toItemId);
346
+ order.exchange_payment_method_id = plan.paymentMethodId;
347
+ order.exchange_price_difference = plan.diff;
348
+ }
349
+
350
+ return {
351
+ order_id: plan.orderId,
352
+ status: "exchange requested" as const,
353
+ price_difference: plan.diff,
354
+ exchanges: plan.lines.map((line) => ({
355
+ item_id: line.fromItemId,
356
+ new_item_id: line.toItemId,
357
+ price_difference: money(line.toPrice - line.fromPrice),
358
+ })),
359
+ message:
360
+ plan.diff > 0
361
+ ? `Exchange requested on ${plan.orderId}. $${plan.diff.toFixed(2)} will be charged to ${plan.paymentMethodId}. An email with return instructions is on its way.`
362
+ : `Exchange requested on ${plan.orderId}. $${Math.abs(plan.diff).toFixed(2)} will be refunded to ${plan.paymentMethodId}. An email with return instructions is on its way.`,
363
+ };
131
364
  }
@@ -3,15 +3,40 @@ customer on the phone. You can help them cancel or modify pending orders,
3
3
  return or exchange delivered orders, update their default address, and answer
4
4
  questions about their own profile, orders and our products.
5
5
 
6
- # Authenticate first
6
+ # Where the call is
7
7
 
8
- Before anything else, identify who you are talking to by finding their user id
9
- by email (`find_user_id_by_email`), or by first name, last name and zip code
10
- (`find_user_id_by_name_zip`) if they cannot remember the email. Do this **even
11
- if the caller volunteers their user id**.
8
+ Every tool answers with the stage this call is in and what that stage expects
9
+ next. Read it it is the shortest true statement of what you may do, and you do
10
+ not have to remember it between turns. A tool that is not available yet refuses
11
+ outright and tells you what has to happen first.
12
12
 
13
- Prefer email. Fall back to name + zip only when they cannot recall the address
14
- on the account.
13
+ Three of the rules below are that machine rather than prose, and they are the
14
+ three you cannot get wrong by forgetting: you cannot act before you know who is
15
+ calling, you cannot act after the call has gone to a human, and you cannot
16
+ change anything the customer has not said yes to.
17
+
18
+ # Nothing changes until the customer says yes
19
+
20
+ **No tool here changes anything.** Cancelling, modifying, returning and
21
+ exchanging all do the same thing: they check the request, price it, and hand
22
+ you one sentence describing exactly what would happen. Nothing has happened at
23
+ that point.
24
+
25
+ So every change is three steps, and they are three separate turns:
26
+
27
+ 1. Call the tool for the change. It answers with a sentence.
28
+ 2. **Say that sentence to the customer** — the order, the items, the amounts,
29
+ where the money is going — and stop. Ask them plainly: is that right?
30
+ 3. On an explicit "yes", call `confirm_change`. On anything else — "no",
31
+ hesitation, "wait", a correction, a new subject — call `cancel_change` and
32
+ start again from what they actually want.
33
+
34
+ Never treat silence, a "mm-hm" in the middle of your sentence, or the fact that
35
+ they asked for it a minute ago as a yes. If you are not sure they agreed, ask
36
+ again; nothing is lost by asking, because nothing has happened yet.
37
+
38
+ Only one change can be waiting at a time. If you need to stage a different one,
39
+ settle the first with `confirm_change` or `cancel_change`.
15
40
 
16
41
  # One customer per call
17
42
 
@@ -20,12 +45,9 @@ requests from that person, but you must refuse anything to do with anybody
20
45
  else's account, and you cannot switch to a different customer mid-call — ask
21
46
  them to call back.
22
47
 
23
- # Confirm every change out loud
24
-
25
- Before any action that changes something cancel, modify, return, exchange
26
- say what you are about to do, including the order, the items, the amounts and
27
- where money is going, and wait for an explicit "yes". Never act on an implied
28
- yes.
48
+ Identify them by email (`find_user_id_by_email`), or by first name, last name
49
+ and zip code (`find_user_id_by_name_zip`) if they cannot remember the email.
50
+ Prefer email. Do this **even if the caller volunteers their user id**.
29
51
 
30
52
  # Never invent anything
31
53
 
@@ -43,9 +65,23 @@ speak; when you speak, do not also make a tool call.
43
65
  # Handing off to a human
44
66
 
45
67
  Transfer only if the caller explicitly asks for a human, or their request
46
- cannot be handled with your tools and this policy. Call
47
- `transfer_to_human_agents` first, then say exactly: "You are being transferred
48
- to a human agent. Please hold on." Say nothing else after that.
68
+ cannot be handled with your tools and this policy.
69
+
70
+ **The handoff is a tool call, not a sentence.** Every turn in which the customer
71
+ asks for a person — "a real person", "an agent", "a supervisor", "put me
72
+ through", "this isn't working" — begins with a call to
73
+ `transfer_to_human_agents`. Not a question back, not an offer to try once more,
74
+ and not the sentence below. Do not ask what the problem is first, and do not try
75
+ to talk them out of it.
76
+
77
+ **Never say "You are being transferred" unless `transfer_to_human_agents` has
78
+ already answered in this turn.** Saying it without the call leaves the customer
79
+ holding a line nobody is coming to: nothing has been handed anywhere, and the
80
+ call is still yours. That is the one mistake here you cannot take back.
81
+
82
+ Once the tool has answered, say exactly: "You are being transferred to a human
83
+ agent. Please hold on." Say nothing else after that. The call is over for you at
84
+ that point, and every tool will refuse — including that one.
49
85
 
50
86
  # Speaking on the phone
51
87
 
@@ -58,6 +94,9 @@ to a human agent. Please hold on." Say nothing else after that.
58
94
  - When a customer gives you a number, read it back before you use it.
59
95
  - Prices: "three hundred and twenty dollars and fifty cents", not "320.50".
60
96
  - Keep replies to one or two sentences. This is a phone call, not an email.
97
+ The one place to be longer is the readback in step 2 above — that sentence
98
+ earns its length, and rushing it is the whole failure mode it exists to
99
+ prevent.
61
100
  - Do not spell out tool names, statuses in snake_case, or user ids.
62
101
 
63
102
  # What the tools accept
@@ -71,22 +110,25 @@ product's options.
71
110
 
72
111
  # The rules that bite
73
112
 
113
+ Every rule here is checked when you stage a change, so a request that breaks one
114
+ is refused before the customer is ever asked to agree to it. They are written
115
+ out so you can steer the conversation, not because you have to enforce them.
116
+
74
117
  **All times in the store's records are EST, 24-hour.** "02:30:00" is 2:30 in
75
118
  the morning.
76
119
 
77
120
  **Cancelling a pending order.** Only an order whose status is exactly
78
- "pending". Check the status first. The reason must be either "no longer needed"
79
- or "ordered by mistake" — if the customer gives another reason, tell them those
80
- are the only two we can record and ask which fits. A gift-card refund lands
81
- immediately; everything else takes 5 to 7 business days.
121
+ "pending". The reason must be either "no longer needed" or "ordered by mistake"
122
+ — if the customer gives another reason, tell them those are the only two we can
123
+ record and ask which fits. A gift-card refund lands immediately; everything else
124
+ takes 5 to 7 business days.
82
125
 
83
126
  **Changing the items in a pending order is once-only and irreversible.** After
84
127
  it, the order cannot be cancelled or modified again — not by you, not by
85
- anyone. So before you call it: get the customer to confirm they have named
86
- **every** item they want changed, then read the complete list and the price
87
- difference back. Ask "is that everything you want to change?" explicitly. An
88
- item can only become a different option of the same product — a shirt cannot
89
- become shoes.
128
+ anyone. So before you stage it: get the customer to confirm they have named
129
+ **every** item they want changed. Ask "is that everything you want to change?"
130
+ explicitly, and only then stage the whole list in one call. An item can only
131
+ become a different option of the same product — a shirt cannot become shoes.
90
132
 
91
133
  **Changing a pending order's payment method.** One method only, and it must be
92
134
  different from the current one. A gift card must cover the whole order total.
@@ -94,11 +136,10 @@ The old method is refunded.
94
136
 
95
137
  **Returning a delivered order.** Only "delivered", only once. The refund goes
96
138
  to the original payment method or to one of their gift cards — nothing else.
97
- Confirm the exact item list. The customer gets an email about sending things
98
- back.
139
+ The customer gets an email about sending things back.
99
140
 
100
141
  **Exchanging a delivered order.** Only "delivered", only once. Same-product
101
- options only. Remind them to name every item first, exactly as with modifying a
142
+ options only. Ask them to name every item first, exactly as with modifying a
102
143
  pending order. The price difference is charged to or refunded from a payment
103
144
  method they choose; a gift card must cover a positive difference. No new order
104
145
  is needed.
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ import { retailTool } from "../store.ts";
3
+
4
+ /**
5
+ * Drop the staged change and go back to helping.
6
+ *
7
+ * Clears `pending` UNCONDITIONALLY and always sends `SETTLED`, so this is the
8
+ * way out of `awaitingConfirmation` from any state the store could be in — a
9
+ * caller who says no, changes their mind about one item, or wants to talk about
10
+ * something else entirely must never leave the call wedged on a change nobody
11
+ * is going to make.
12
+ */
13
+ export default retailTool({
14
+ name: "cancel_change",
15
+ when: "serving.awaitingConfirmation",
16
+ send: { type: "SETTLED" },
17
+ description:
18
+ "Drop the change that is waiting, without applying it. Use this when the caller says no, " +
19
+ "hesitates, wants any part of it different, or moves on to something else. Nothing was " +
20
+ "changed, so there is nothing to undo — stage the corrected change afterwards if they want one.",
21
+ inputSchema: z.object({}),
22
+ execute: (_args, state) => {
23
+ const dropped = state.pending;
24
+ state.pending = null;
25
+ return {
26
+ dropped: dropped?.kind ?? null,
27
+ message: dropped
28
+ ? `Dropped — nothing was changed. The caller did not agree to: ${dropped.plan.readBack}.`
29
+ : "Nothing was staged, so nothing was dropped.",
30
+ };
31
+ },
32
+ summary: (_args, result) => `dropped ${result.dropped ?? "nothing"}`,
33
+ });
@@ -1,19 +1,20 @@
1
1
  import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
- import { creditRefund, REFUND_DELAY_NOTE, REFUND_IMMEDIATE_NOTE } from "../refund.ts";
4
- import { OrderIdField, resolveOrder } from "../resolve.ts";
5
- import { authenticatedUser, retailTool, setFocus } from "../store.ts";
6
-
7
- /** tau2 accepts exactly these two. Anything else is refused. */
8
- const CANCEL_REASONS = ["no longer needed", "ordered by mistake"] as const;
3
+ import { CANCEL_REASONS, planCancel } from "../cancel.ts";
4
+ import { stageAction } from "../pending.ts";
5
+ import { OrderIdField } from "../resolve.ts";
6
+ import { retailTool, setFocus } from "../store.ts";
9
7
 
10
8
  export default retailTool({
11
9
  name: "cancel_pending_order",
10
+ when: "serving",
11
+ send: { type: "STAGED" },
12
12
  description:
13
- "Cancel a pending order. Only an order whose status is exactly 'pending' can be cancelled " +
14
- "check the status first. The reason must be either 'no longer needed' or 'ordered by mistake'. " +
15
- "State the order, its total and the refund destination to the caller and get an explicit yes " +
16
- "before calling this.",
13
+ "STAGE a cancellation of a pending order this does NOT cancel anything. Only an order whose " +
14
+ "status is exactly 'pending' can be cancelled, and the reason must be either 'no longer " +
15
+ "needed' or 'ordered by mistake'. The order, its total and where the refund goes come back as " +
16
+ "a sentence to read to the caller; nothing happens until you hear an explicit yes and call " +
17
+ "confirm_change.",
17
18
  inputSchema: z.object({
18
19
  order_id: OrderIdField,
19
20
  reason: z
@@ -21,55 +22,10 @@ export default retailTool({
21
22
  .describe("Either 'no longer needed' or 'ordered by mistake' — no other reason is accepted"),
22
23
  }),
23
24
  execute: (args, state) => {
24
- const user = authenticatedUser(state);
25
- if (isToolFailure(user)) return user;
26
-
27
- const order = resolveOrder(state, args.order_id);
28
- if (isToolFailure(order)) return order;
29
- setFocus(state, { orderId: order.order_id });
30
-
31
- if (order.status !== "pending") {
32
- return {
33
- error: `Order ${order.order_id} is ${order.status}, and only a pending order can be cancelled.`,
34
- };
35
- }
36
- // The enum makes this unreachable from a well-formed call; it stays because
37
- // the reason is the one field a caller supplies in free speech.
38
- if (!CANCEL_REASONS.includes(args.reason)) {
39
- return {
40
- error: `'${args.reason}' is not an accepted cancellation reason. It must be 'no longer needed' or 'ordered by mistake'.`,
41
- };
42
- }
43
-
44
- let immediate = false;
45
- const refunds = order.payment_history
46
- .filter((payment) => payment.transaction_type === "payment")
47
- .map((payment) => {
48
- const credited = creditRefund(user, payment.payment_method_id, payment.amount);
49
- immediate = immediate || credited.immediate;
50
- return {
51
- transaction_type: "refund" as const,
52
- amount: payment.amount,
53
- payment_method_id: payment.payment_method_id,
54
- };
55
- });
56
-
57
- order.status = "cancelled";
58
- order.cancel_reason = args.reason;
59
- order.payment_history.push(...refunds);
60
-
61
- const total = refunds.reduce((sum, refund) => sum + refund.amount, 0);
62
- return {
63
- order_id: order.order_id,
64
- status: order.status,
65
- cancel_reason: order.cancel_reason,
66
- refunded: total,
67
- refund_immediate: immediate,
68
- message: `Order ${order.order_id} is cancelled and $${total.toFixed(2)} is being refunded. ${
69
- immediate ? REFUND_IMMEDIATE_NOTE : REFUND_DELAY_NOTE
70
- }`,
71
- };
25
+ const plan = planCancel(state, args.order_id, args.reason);
26
+ if (isToolFailure(plan)) return plan;
27
+ setFocus(state, { orderId: plan.orderId });
28
+ return stageAction(state, { kind: "cancel_pending_order", plan });
72
29
  },
73
- summary: (_args, result) =>
74
- isToolFailure(result) ? "cancel failed" : `cancelled ${result.order_id}`,
30
+ summary: (_args, result) => `staged: ${result.read_back}`,
75
31
  });
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ import { applyAction } from "../pending.ts";
3
+ import { retailTool } from "../store.ts";
4
+
5
+ /**
6
+ * The only tool in this template that writes to the store.
7
+ *
8
+ * Gated on `serving.awaitingConfirmation`, which is reachable only by staging a
9
+ * change — so "confirm what nobody staged" is refused by the SDK before this
10
+ * body runs, and the refusal quotes the state's own instruction. See
11
+ * `pending.ts`.
12
+ */
13
+ export default retailTool({
14
+ name: "confirm_change",
15
+ when: "serving.awaitingConfirmation",
16
+ send: { type: "SETTLED" },
17
+ description:
18
+ "Apply the change that is waiting, after you have read it back to the caller and heard an " +
19
+ "explicit yes. Never call this on an implied yes, on silence, or before you have said what " +
20
+ "the change is. If they say no, or want any part of it different, call cancel_change instead.",
21
+ inputSchema: z.object({}),
22
+ execute: (_args, state) => {
23
+ const action = state.pending;
24
+ // Unreachable while the gate holds: `awaitingConfirmation` is entered only
25
+ // by a staging tool, which writes `pending` in the same synchronous window
26
+ // it sends `STAGED` in. Kept and reported rather than thrown for the reason
27
+ // `authenticatedUser` keeps its own — this runs mid-call, and a sentence
28
+ // the model can act on beats an exception. `cancel_change` is the way out
29
+ // if it ever did fire, which is why that tool clears unconditionally.
30
+ if (!action) {
31
+ return {
32
+ error:
33
+ "Nothing is staged, so there is nothing to confirm. Stage the change again with the " +
34
+ "tool for it, read it back, and confirm once the caller says yes.",
35
+ };
36
+ }
37
+ const result = applyAction(state, action);
38
+ state.pending = null;
39
+ return { confirmed: action.kind, ...result };
40
+ },
41
+ summary: (_args, result) => `confirmed ${result.confirmed}`,
42
+ });