@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.
- package/README.md +1 -0
- package/dist/{_agent-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
- package/dist/{_bundler-C2BDwU7r.mjs → _bundler-CDuYl5Gb.mjs} +2 -2
- package/dist/_dev-env.d.ts +22 -2
- package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-Me06wgeF.mjs} +38 -47
- package/dist/_dev-server.d.ts +0 -17
- package/dist/{_init-C57bWAQ8.mjs → _init-BhEfQ6Yi.mjs} +3 -2
- package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
- package/dist/_preflight.d.ts +1 -1
- package/dist/_resource-commands.d.ts +23 -0
- package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-z-USxPtL.mjs} +16 -12
- package/dist/_slug-api.d.ts +5 -2
- package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
- package/dist/{_templates-Dxb_P2Wz.mjs → _templates-4WcKOjS5.mjs} +5 -6
- package/dist/_templates.d.ts +3 -4
- package/dist/_workflow-scan.d.ts +108 -0
- package/dist/{build-Dfza2pRI.mjs → build-BBxf8vom.mjs} +6 -3
- package/dist/cli.mjs +346 -349
- package/dist/{client-bundler-BEIqgOtd.mjs → client-bundler-CWnG42cU.mjs} +2 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-vbpjGzqs.mjs → delete-Dl_LSfvI.mjs} +3 -3
- package/dist/{deploy-B8lubiRT.mjs → deploy-DRC8jWr8.mjs} +5 -5
- package/dist/{dev-Uq5ujP8a.mjs → dev-BvcctCn2.mjs} +1 -1
- package/dist/eval-DfQ8cm5p.mjs +56 -0
- package/dist/eval.d.ts +43 -0
- package/dist/{init-l6xfU4xX.mjs → init-CUo87gcI.mjs} +3 -3
- package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
- package/dist/{logs-BWVGSjLR.mjs → logs-CQ2T46dr.mjs} +2 -2
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +442 -80
- package/dist/scaffold/package.json +6 -3
- package/dist/scaffold/server.mjs +11 -4
- package/dist/scaffold/tsconfig.json +0 -1
- package/dist/{secret-BuMuFR4B.mjs → secret-Ct34kdgU.mjs} +2 -2
- package/dist/{storage-DzRZ-eCw.mjs → storage-i_79ZwAR.mjs} +2 -2
- package/dist/{studio-BCNUpDgP.mjs → studio-CzTzPqe1.mjs} +4 -4
- package/dist/templates/briefing-desk/agent.test.ts +350 -0
- package/dist/templates/briefing-desk/agent.ts +30 -0
- package/dist/templates/briefing-desk/shared.ts +200 -0
- package/dist/templates/briefing-desk/system-prompt.md +39 -0
- package/dist/templates/briefing-desk/tools/briefing_so_far.ts +35 -0
- package/dist/templates/briefing-desk/tools/research_topic.ts +88 -0
- package/dist/templates/briefing-desk/tools/verify_claim.ts +77 -0
- package/dist/templates/call-audit/agent.eval.test.ts +474 -0
- package/dist/templates/call-audit/agent.test.ts +57 -204
- package/dist/templates/call-audit/agent.ts +32 -19
- package/dist/templates/call-audit/client.tsx +17 -54
- package/dist/templates/call-audit/workflows/audit.ts +11 -18
- package/dist/templates/call-audit/workflows/ingest.ts +106 -114
- package/dist/templates/call-audit/workflows/media.ts +2 -12
- package/dist/templates/call-audit/workflows/summarize.ts +47 -52
- package/dist/templates/call-audit/workflows/sync-api.ts +7 -7
- package/dist/templates/code-interpreter/agent.eval.test.ts +165 -0
- package/dist/templates/dispatch-center/agent.eval.test.ts +349 -0
- package/dist/templates/dispatch-center/agent.test.ts +149 -25
- package/dist/templates/dispatch-center/client.tsx +239 -129
- package/dist/templates/dispatch-center/shared.ts +99 -1
- package/dist/templates/dispatch-center/system-prompt.md +3 -1
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +16 -13
- package/dist/templates/dispatch-center/tools/incident_create.ts +19 -1
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +68 -59
- package/dist/templates/dispatch-center/tools/incident_triage.ts +57 -43
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +49 -40
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +8 -2
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +69 -52
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +45 -41
- package/dist/templates/embedded-assets/agent.eval.test.ts +138 -0
- package/dist/templates/embedded-assets/agent.test.ts +17 -14
- package/dist/templates/health-assistant/agent.eval.test.ts +167 -0
- package/dist/templates/health-assistant/agent.test.ts +22 -4
- package/dist/templates/infocom-adventure/agent.eval.test.ts +158 -0
- package/dist/templates/infocom-adventure/agent.test.ts +53 -24
- package/dist/templates/infocom-adventure/agent.ts +21 -0
- package/dist/templates/infocom-adventure/client.tsx +246 -164
- package/dist/templates/infocom-adventure/shared.ts +20 -9
- package/dist/templates/infocom-adventure/system-prompt.md +1 -1
- package/dist/templates/infocom-adventure/tools/game_state_move.ts +3 -2
- package/dist/templates/link-digest/agent.eval.test.ts +195 -0
- package/dist/templates/link-digest/agent.test.ts +24 -19
- package/dist/templates/link-digest/client.tsx +47 -61
- package/dist/templates/link-digest/workflows/digest.ts +19 -31
- package/dist/templates/math-buddy/agent.eval.test.ts +146 -0
- package/dist/templates/night-owl/agent.eval.test.ts +272 -0
- package/dist/templates/night-owl/agent.test.ts +70 -19
- package/dist/templates/night-owl/agent.ts +5 -0
- package/dist/templates/night-owl/client.tsx +56 -56
- package/dist/templates/night-owl/shared.ts +24 -0
- package/dist/templates/night-owl/tools/recommend.ts +18 -6
- package/dist/templates/personal-finance/agent.eval.test.ts +168 -0
- package/dist/templates/personal-finance/system-prompt.md +4 -0
- package/dist/templates/pipeline-simple/agent.eval.test.ts +78 -0
- package/dist/templates/pipeline-simple/agent.ts +2 -2
- package/dist/templates/pizza-ordering/agent.eval.test.ts +171 -0
- package/dist/templates/pizza-ordering/agent.test.ts +42 -27
- package/dist/templates/pizza-ordering/client.tsx +9 -26
- package/dist/templates/pizza-ordering/shared.ts +20 -1
- package/dist/templates/pizza-ordering/system-prompt.md +6 -0
- package/dist/templates/plan-and-execute/agent.eval.test.ts +209 -0
- package/dist/templates/plan-and-execute/agent.test.ts +99 -58
- package/dist/templates/plan-and-execute/agent.ts +1 -1
- package/dist/templates/plan-and-execute/client.tsx +12 -15
- package/dist/templates/plan-and-execute/shared.ts +71 -2
- package/dist/templates/plan-and-execute/tools/plan_status.ts +18 -6
- package/dist/templates/plan-and-execute/tools/revise_plan.ts +18 -10
- package/dist/templates/plan-and-execute/tools/start_plan.ts +13 -2
- package/dist/templates/plan-and-execute/tools/work_next_step.ts +70 -22
- package/dist/templates/podcast-digest/agent.eval.test.ts +411 -0
- package/dist/templates/podcast-digest/agent.test.ts +717 -0
- package/dist/templates/podcast-digest/agent.ts +139 -0
- package/dist/templates/podcast-digest/client.tsx +154 -0
- package/dist/templates/podcast-digest/workflows/digest.ts +444 -0
- package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
- package/dist/templates/podcast-digest/workflows/slack.ts +88 -0
- package/dist/templates/recap-workflow/agent.eval.test.ts +366 -0
- package/dist/templates/recap-workflow/agent.test.ts +97 -94
- package/dist/templates/recap-workflow/tools/recap_progress.ts +9 -12
- package/dist/templates/recap-workflow/tools/recap_status.ts +8 -9
- package/dist/templates/recap-workflow/tools/request_recap.ts +2 -1
- package/dist/templates/recap-workflow/workflows/recap.ts +36 -40
- package/dist/templates/redline/agent.eval.test.ts +209 -0
- package/dist/templates/redline/agent.test.ts +17 -15
- package/dist/templates/redline/client.tsx +12 -12
- package/dist/templates/redline/workflows/redline.ts +19 -31
- package/dist/templates/research-workflow/agent.eval.test.ts +329 -0
- package/dist/templates/research-workflow/agent.test.ts +60 -59
- package/dist/templates/research-workflow/tools/research_progress.ts +9 -12
- package/dist/templates/research-workflow/tools/research_status.ts +9 -11
- package/dist/templates/research-workflow/workflows/research.ts +44 -61
- package/dist/templates/retail/address.ts +87 -1
- package/dist/templates/retail/agent.eval.test.ts +314 -0
- package/dist/templates/retail/agent.test.ts +195 -112
- package/dist/templates/retail/agent.ts +35 -1
- package/dist/templates/retail/cancel.ts +116 -0
- package/dist/templates/retail/client.tsx +265 -117
- package/dist/templates/retail/payment.ts +107 -0
- package/dist/templates/retail/pending.test.ts +196 -0
- package/dist/templates/retail/pending.ts +137 -0
- package/dist/templates/retail/registry.test.ts +150 -7
- package/dist/templates/retail/returns.ts +102 -0
- package/dist/templates/retail/shared.test.ts +42 -0
- package/dist/templates/retail/shared.ts +35 -0
- package/dist/templates/retail/store.test.ts +84 -15
- package/dist/templates/retail/store.ts +231 -48
- package/dist/templates/retail/swap.test.ts +4 -4
- package/dist/templates/retail/swap.ts +248 -15
- package/dist/templates/retail/system-prompt.md +69 -28
- package/dist/templates/retail/tools/cancel_change.ts +33 -0
- package/dist/templates/retail/tools/cancel_pending_order.ts +16 -60
- package/dist/templates/retail/tools/confirm_change.ts +42 -0
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -67
- package/dist/templates/retail/tools/find_user_id_by_email.ts +8 -5
- package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +4 -5
- package/dist/templates/retail/tools/get_item_details.ts +3 -4
- package/dist/templates/retail/tools/get_order_details.ts +3 -4
- package/dist/templates/retail/tools/get_product_details.ts +3 -4
- package/dist/templates/retail/tools/get_user_details.ts +2 -2
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -5
- package/dist/templates/retail/tools/modify_pending_order_address.ts +13 -25
- package/dist/templates/retail/tools/modify_pending_order_items.ts +23 -62
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +16 -78
- package/dist/templates/retail/tools/modify_user_address.ts +13 -16
- package/dist/templates/retail/tools/return_delivered_order_items.ts +17 -72
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +12 -6
- package/dist/templates/simple/agent.eval.test.ts +54 -0
- package/dist/templates/solo-rpg/agent.eval.test.ts +320 -0
- package/dist/templates/solo-rpg/agent.test.ts +227 -48
- package/dist/templates/solo-rpg/client.tsx +38 -37
- package/dist/templates/solo-rpg/shared.ts +145 -19
- package/dist/templates/solo-rpg/system-prompt.md +3 -2
- package/dist/templates/solo-rpg/tools/action_roll.ts +82 -63
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +85 -50
- package/dist/templates/solo-rpg/tools/check_state.ts +24 -7
- package/dist/templates/solo-rpg/tools/load_game.ts +13 -1
- package/dist/templates/solo-rpg/tools/save_game.ts +16 -7
- package/dist/templates/solo-rpg/tools/setup_character.ts +22 -2
- package/dist/templates/solo-rpg/tools/update_state.ts +113 -100
- package/dist/templates/spoken-summary/agent.eval.test.ts +342 -0
- package/dist/templates/spoken-summary/agent.test.ts +68 -97
- package/dist/templates/spoken-summary/agent.ts +30 -17
- package/dist/templates/spoken-summary/client.tsx +10 -50
- package/dist/templates/spoken-summary/workflows/summarize.ts +17 -22
- package/dist/templates/spoken-summary/workflows/transcribe.ts +19 -26
- package/dist/templates/support-line/agent.eval.test.ts +273 -0
- package/dist/templates/support-line/agent.test.ts +11 -16
- package/dist/templates/support-line/agent.ts +1 -1
- package/dist/templates/support-line/client.tsx +9 -9
- package/dist/templates/support-line/nodes.ts +100 -0
- package/dist/templates/support-line/procedure.ts +407 -0
- package/dist/templates/support-line/system-prompt.md +2 -1
- package/dist/templates/support-line/tools/answer_question.ts +66 -4
- package/dist/templates/transcription-workflow/agent.eval.test.ts +234 -0
- package/dist/templates/transcription-workflow/agent.test.ts +94 -194
- package/dist/templates/transcription-workflow/agent.ts +1 -1
- package/dist/templates/transcription-workflow/client.tsx +17 -42
- package/dist/templates/transcription-workflow/workflows/batch.ts +19 -22
- package/dist/templates/transcription-workflow/workflows/normalize.ts +90 -186
- package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
- package/dist/templates/transcription-workflow/workflows/stream.ts +4 -5
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +6 -6
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +32 -26
- package/dist/templates/transcription-workflow/workflows/wav.ts +0 -31
- package/dist/templates/travel-concierge/agent.eval.test.ts +323 -0
- package/dist/templates/travel-concierge/agent.test.ts +174 -43
- package/dist/templates/travel-concierge/agent.ts +18 -9
- package/dist/templates/travel-concierge/client.tsx +11 -23
- package/dist/templates/travel-concierge/routing.ts +34 -15
- package/dist/templates/travel-concierge/shared.ts +180 -12
- package/dist/templates/travel-concierge/system-prompt.md +7 -1
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +8 -5
- package/dist/templates/travel-concierge/tools/book_excursion.ts +8 -5
- package/dist/templates/travel-concierge/tools/book_hotel.ts +8 -5
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +8 -5
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +9 -5
- package/dist/templates/travel-concierge/tools/search_excursions.ts +9 -5
- package/dist/templates/travel-concierge/tools/search_flights.ts +9 -6
- package/dist/templates/travel-concierge/tools/search_hotels.ts +9 -5
- package/dist/templates/travel-concierge/tools/update_ticket.ts +8 -5
- package/dist/templates/web-researcher/agent.eval.test.ts +121 -0
- package/dist/templates/web-researcher/system-prompt.md +15 -4
- package/dist/{test-9kPKJI-w.mjs → test-BvyT6sAc.mjs} +24 -10
- package/dist/test.d.ts +29 -5
- package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-CCVEDjm1.mjs} +241 -3
- package/dist/worker-bundler.mjs +1 -1
- package/dist/{workflow-CFpxOFfQ.mjs → workflow-BflATMmU.mjs} +47 -12
- package/dist/workflow-bundler.d.ts +6 -1
- package/dist/workflow.d.ts +1 -1
- package/package.json +5 -4
- package/dist/eject-31gjtaHF.mjs +0 -116
- package/dist/eject.d.ts +0 -60
- package/dist/templates/call-audit/workflows/temp-media.ts +0 -138
- package/dist/templates/infocom-adventure/tools/game_state_history.ts +0 -14
- package/dist/templates/support-line/graph.ts +0 -224
- package/dist/{_config-D_s09e7g.mjs → _config-CmJOFsAP.mjs} +1 -1
- /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 {
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
122
|
-
*
|
|
123
|
-
export function
|
|
124
|
-
for (const
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
#
|
|
6
|
+
# Where the call is
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
—
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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.
|
|
47
|
-
|
|
48
|
-
|
|
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".
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
86
|
-
**every** item they want changed
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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.
|
|
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 {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
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
|
|
25
|
-
if (isToolFailure(
|
|
26
|
-
|
|
27
|
-
|
|
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
|
+
});
|