@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.
- package/dist/{_agent-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
- package/dist/{_bundler-C2BDwU7r.mjs → _bundler-B4RqNF5Z.mjs} +2 -2
- package/dist/_dev-env.d.ts +22 -2
- package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-CiKFH9qw.mjs} +38 -47
- package/dist/_dev-server.d.ts +0 -17
- package/dist/{_init-C57bWAQ8.mjs → _init-CE4HKdgV.mjs} +3 -2
- package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
- package/dist/_preflight.d.ts +1 -1
- package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-nRFaBEPJ.mjs} +2 -2
- package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
- package/dist/{_templates-Dxb_P2Wz.mjs → _templates-FI3xPEMj.mjs} +1 -1
- package/dist/_workflow-scan.d.ts +108 -0
- package/dist/{build-Dfza2pRI.mjs → build-6jfaakgc.mjs} +5 -2
- package/dist/cli.mjs +30 -25
- 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-DvYeD8ia.mjs} +5 -5
- package/dist/{dev-Uq5ujP8a.mjs → dev-B59JIfWO.mjs} +1 -1
- package/dist/{eject-31gjtaHF.mjs → eject-C9WJyyr2.mjs} +1 -1
- package/dist/{init-l6xfU4xX.mjs → init-CpJVjEg0.mjs} +3 -3
- package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
- package/dist/{logs-BWVGSjLR.mjs → logs-9q-psa4q.mjs} +2 -2
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +93 -14
- package/dist/scaffold/package.json +5 -3
- package/dist/scaffold/server.mjs +13 -4
- package/dist/scaffold/vite.config.ts +1 -1
- package/dist/{secret-BuMuFR4B.mjs → secret-CVvSLIDV.mjs} +2 -2
- package/dist/{storage-DzRZ-eCw.mjs → storage-BvUrnvM3.mjs} +2 -2
- package/dist/{studio-BCNUpDgP.mjs → studio--MUV0cid.mjs} +4 -4
- package/dist/templates/call-audit/agent.test.ts +818 -0
- package/dist/templates/call-audit/agent.ts +171 -0
- package/dist/templates/call-audit/client.tsx +198 -0
- package/dist/templates/call-audit/workflows/audit.ts +298 -0
- package/dist/templates/call-audit/workflows/ingest.ts +251 -0
- package/dist/templates/call-audit/workflows/media.ts +637 -0
- package/dist/templates/call-audit/workflows/summarize.ts +201 -0
- package/dist/templates/call-audit/workflows/sync-api.ts +44 -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.test.ts +17 -14
- package/dist/templates/health-assistant/agent.test.ts +22 -4
- package/dist/templates/infocom-adventure/agent.test.ts +4 -6
- package/dist/templates/infocom-adventure/client.tsx +246 -164
- 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/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/pizza-ordering/agent.test.ts +27 -27
- package/dist/templates/pizza-ordering/client.tsx +9 -26
- 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.test.ts +746 -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 +411 -0
- package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
- package/dist/templates/podcast-digest/workflows/slack.ts +209 -0
- package/dist/templates/recap-workflow/agent.test.ts +107 -96
- 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 +48 -41
- 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.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/agent.test.ts +26 -23
- package/dist/templates/retail/client.tsx +226 -117
- package/dist/templates/retail/registry.test.ts +38 -6
- package/dist/templates/retail/store.test.ts +82 -15
- package/dist/templates/retail/store.ts +174 -47
- package/dist/templates/retail/system-prompt.md +11 -2
- package/dist/templates/retail/tools/cancel_pending_order.ts +2 -2
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +2 -2
- 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 +2 -2
- package/dist/templates/retail/tools/modify_pending_order_items.ts +2 -3
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +2 -2
- package/dist/templates/retail/tools/modify_user_address.ts +2 -2
- package/dist/templates/retail/tools/return_delivered_order_items.ts +2 -2
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +8 -4
- 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.test.ts +314 -0
- package/dist/templates/spoken-summary/agent.ts +155 -0
- package/dist/templates/spoken-summary/client.tsx +185 -0
- package/dist/templates/spoken-summary/workflows/summarize.ts +237 -0
- package/dist/templates/spoken-summary/workflows/transcribe.ts +138 -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/tools/answer_question.ts +17 -4
- package/dist/templates/transcription-workflow/agent.test.ts +246 -123
- package/dist/templates/transcription-workflow/agent.ts +21 -7
- package/dist/templates/transcription-workflow/client.tsx +17 -42
- package/dist/templates/transcription-workflow/workflows/batch.ts +79 -180
- package/dist/templates/transcription-workflow/workflows/normalize.ts +247 -0
- package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
- package/dist/templates/transcription-workflow/workflows/stream.ts +10 -9
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +26 -94
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +55 -40
- package/dist/templates/transcription-workflow/workflows/wav.ts +31 -31
- package/dist/templates/travel-concierge/agent.test.ts +64 -33
- 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 +70 -3
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +2 -2
- package/dist/templates/travel-concierge/tools/book_excursion.ts +2 -2
- package/dist/templates/travel-concierge/tools/book_hotel.ts +2 -2
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +2 -2
- package/dist/templates/travel-concierge/tools/update_ticket.ts +2 -2
- package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-COxnqstQ.mjs} +221 -3
- package/dist/worker-bundler.mjs +1 -1
- package/dist/{workflow-CFpxOFfQ.mjs → workflow-D2AQf2Pl.mjs} +28 -1
- package/dist/workflow-bundler.d.ts +6 -1
- package/dist/workflow.d.ts +1 -1
- package/package.json +5 -4
- 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,5 +1,5 @@
|
|
|
1
1
|
import type { ToolContext, ToolFailure } from "@alexkroman1/aai";
|
|
2
|
-
import { isToolFailure, pushCapped, sessionSlot
|
|
2
|
+
import { dialog, isToolFailure, omitUndefined, pushCapped, sessionSlot } from "@alexkroman1/aai";
|
|
3
3
|
import type { z } from "zod";
|
|
4
4
|
import seedJson from "./seed.json";
|
|
5
5
|
import type {
|
|
@@ -71,6 +71,89 @@ export function seedStore(): Store {
|
|
|
71
71
|
*/
|
|
72
72
|
export const retailSlot = sessionSlot("retail", createDefaultState);
|
|
73
73
|
|
|
74
|
+
// ─── The call, as a machine ──────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Where this call is, and what may be done from here, as a plain state map.
|
|
78
|
+
*
|
|
79
|
+
* A {@link DialogSpec} rather than an XState machine: this dialog said three
|
|
80
|
+
* states, two events and an instruction each, which is exactly what a spec can
|
|
81
|
+
* say — and the `setup({ types: {} as { events: … } })` block it used to carry
|
|
82
|
+
* restated the two event names already written in the `on` maps. The
|
|
83
|
+
* instruction is a declared field now instead of an untyped `meta` bag, so
|
|
84
|
+
* misspelling it is a compile error rather than a refusal that arrives with no
|
|
85
|
+
* recovery text. `as const` is what keeps the `on` keys literal, so `send`
|
|
86
|
+
* below is checked against the events this spec actually declares.
|
|
87
|
+
*
|
|
88
|
+
* The policy's first two sections — "Authenticate first" and "Handing off to a
|
|
89
|
+
* human" — used to be prose plus a boolean. `requiresAuth` was that boolean:
|
|
90
|
+
* fifteen tools declared it (five opting out), the wrapper below read it, and a
|
|
91
|
+
* refusal answered one fixed sentence. Three things change by declaring the
|
|
92
|
+
* states instead.
|
|
93
|
+
*
|
|
94
|
+
* **`transferred` is a real terminal state, and it was not enforced at all.**
|
|
95
|
+
* The policy says to call `transfer_to_human_agents` and then say exactly one
|
|
96
|
+
* sentence "and nothing else" — which nothing checked, so every tool stayed
|
|
97
|
+
* callable after the handoff and a model that kept going would keep acting on a
|
|
98
|
+
* call it had already given away. It is a `final` state now: no tool declares
|
|
99
|
+
* itself legal there, so every one of them refuses.
|
|
100
|
+
*
|
|
101
|
+
* **The refusal quotes the state.** `when` names where a tool may run and the
|
|
102
|
+
* SDK writes the sentence, so the message is one thing rather than a constant
|
|
103
|
+
* threaded through a wrapper — and it says where the call actually is, which
|
|
104
|
+
* "Not authenticated" could not.
|
|
105
|
+
*
|
|
106
|
+
* **The position rides every result.** A flow tool answers the author's value
|
|
107
|
+
* wrapped in the position it landed in, so the stage and its instruction reach
|
|
108
|
+
* the model on every call rather than only when the prompt is still in context.
|
|
109
|
+
*
|
|
110
|
+
* `IDENTIFIED` is declared on `serving` as well, because a caller repeating
|
|
111
|
+
* their email must not hit an error — see `authenticateAs`, which is what
|
|
112
|
+
* refuses a switch to a DIFFERENT customer. Whether the flow is identified and
|
|
113
|
+
* WHO it is identified as are two facts: this holds the first,
|
|
114
|
+
* `authenticatedUserId` holds the second.
|
|
115
|
+
*/
|
|
116
|
+
const callSpec = {
|
|
117
|
+
initial: "identifying",
|
|
118
|
+
states: {
|
|
119
|
+
identifying: {
|
|
120
|
+
// The instruction NAMES the two tools, because this sentence is what a
|
|
121
|
+
// refusal quotes and a refusal is the model's recovery path — the same
|
|
122
|
+
// job the removed `NOT_AUTHENTICATED` constant did, now attached to the
|
|
123
|
+
// state that means it.
|
|
124
|
+
instruction:
|
|
125
|
+
"You do not know who this is yet. Identify the caller with " +
|
|
126
|
+
"find_user_id_by_email, or find_user_id_by_name_zip if they cannot " +
|
|
127
|
+
"remember the email. Do this even if they volunteer a user id.",
|
|
128
|
+
on: { IDENTIFIED: "serving", TRANSFERRED: "transferred" },
|
|
129
|
+
},
|
|
130
|
+
serving: {
|
|
131
|
+
instruction:
|
|
132
|
+
"You are helping one identified customer, and only that one. Say what you " +
|
|
133
|
+
"are about to change — the order, the items, the amounts, where the money " +
|
|
134
|
+
"goes — and wait for an explicit yes before you call anything that changes it.",
|
|
135
|
+
on: { IDENTIFIED: "serving", TRANSFERRED: "transferred" },
|
|
136
|
+
},
|
|
137
|
+
transferred: {
|
|
138
|
+
final: true,
|
|
139
|
+
instruction:
|
|
140
|
+
"The call belongs to a human agent now. Say nothing beyond the transfer " +
|
|
141
|
+
"sentence, and do nothing else.",
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
} as const;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The flow. Its own slot key beside {@link retailSlot}: the flow holds the
|
|
148
|
+
* POSITION and the store holds the customer, the orders and the activity feed.
|
|
149
|
+
*/
|
|
150
|
+
export const callFlow = dialog("call", callSpec);
|
|
151
|
+
|
|
152
|
+
/** Every state a tool may run in before the call is handed to a human — i.e.
|
|
153
|
+
* everything but `transferred`. What the five formerly `requiresAuth: false`
|
|
154
|
+
* tools declare, so the terminal state gates them without an auth gate. */
|
|
155
|
+
export const BEFORE_TRANSFER = ["identifying", "serving"] as const;
|
|
156
|
+
|
|
74
157
|
export function setFocus(
|
|
75
158
|
state: RetailState,
|
|
76
159
|
focus: { orderId?: string; productId?: string },
|
|
@@ -128,12 +211,26 @@ export function findPaymentMethod(user: User, methodId: string): PaymentMethod |
|
|
|
128
211
|
|
|
129
212
|
// ─── Guards ──────────────────────────────────────────────────────────────────
|
|
130
213
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
214
|
+
/**
|
|
215
|
+
* The customer on this call.
|
|
216
|
+
*
|
|
217
|
+
* The null arm is reachable only if the POSITION and the STORE disagree — the
|
|
218
|
+
* flow says `serving` while nothing latched a user id — which no code path
|
|
219
|
+
* produces, since `authenticateAs` is what both writes the id and lets the
|
|
220
|
+
* `IDENTIFIED` event through. It is kept and reported rather than thrown for the
|
|
221
|
+
* reason `travel-concierge`'s `cancel_action` keeps its own: this runs mid-call,
|
|
222
|
+
* and a sentence the model can act on beats an exception. The GATE that a
|
|
223
|
+
* customer is identified at all is `callFlow`'s, declared per tool as `when`.
|
|
224
|
+
*/
|
|
135
225
|
export function authenticatedUser(state: RetailState): User | ToolFailure {
|
|
136
|
-
if (!state.authenticatedUserId)
|
|
226
|
+
if (!state.authenticatedUserId) {
|
|
227
|
+
return {
|
|
228
|
+
error:
|
|
229
|
+
"No customer is latched onto this call yet. Identify them with " +
|
|
230
|
+
"find_user_id_by_email, or find_user_id_by_name_zip if they cannot " +
|
|
231
|
+
"remember their email.",
|
|
232
|
+
};
|
|
233
|
+
}
|
|
137
234
|
return findUser(state, state.authenticatedUserId);
|
|
138
235
|
}
|
|
139
236
|
|
|
@@ -187,24 +284,42 @@ interface RetailToolSpec<S extends z.ZodType<Record<string, unknown>>, R> {
|
|
|
187
284
|
/** Required even for no-arg tools — pass `z.object({})`. One code path in the
|
|
188
285
|
* wrapper is worth more than saving a line at one call site. */
|
|
189
286
|
inputSchema: S;
|
|
190
|
-
/** Default true. Only the two finder tools and the three catalog tools opt
|
|
191
|
-
* out; everything else touches customer data. */
|
|
192
|
-
requiresAuth?: boolean;
|
|
193
|
-
summary: (args: z.output<S>, result: R) => string;
|
|
194
287
|
/**
|
|
195
|
-
*
|
|
288
|
+
* The state(s) this tool may run in, as `callFlow`'s states spell them.
|
|
196
289
|
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
*
|
|
290
|
+
* Replaces the `requiresAuth` boolean this spec used to carry. Ten tools want
|
|
291
|
+
* `"serving"`; the two finders, the three catalog reads and the transfer want
|
|
292
|
+
* {@link BEFORE_TRANSFER}, which is every state but the terminal one — so
|
|
293
|
+
* "does not need a customer" and "is still legal after the handoff" stopped
|
|
294
|
+
* being the same claim, and they were never the same claim.
|
|
295
|
+
*/
|
|
296
|
+
when: string | readonly string[];
|
|
297
|
+
/** The event to send once the body has succeeded, for a tool that MOVES the
|
|
298
|
+
* call. Three do: the two finders send `IDENTIFIED`, the transfer sends
|
|
299
|
+
* `TRANSFERRED`. Nothing is sent when the body answers a `ToolFailure`. */
|
|
300
|
+
send?: { type: "IDENTIFIED" } | { type: "TRANSFERRED" };
|
|
301
|
+
/**
|
|
302
|
+
* One line for the activity feed, from the call that SUCCEEDED.
|
|
303
|
+
*
|
|
304
|
+
* `R` is the SUCCESS type — `execute` below is declared `R | ToolFailure`, so
|
|
305
|
+
* the failure arm is matched against `ToolFailure` during inference and never
|
|
306
|
+
* lands in `R`. That is what lets the fifteen tool files stop writing
|
|
307
|
+
* `isToolFailure(result) ? "… failed" : …` for a case that cannot arrive: the
|
|
308
|
+
* wrapper never calls `summary` on a failure and now says so in the type.
|
|
206
309
|
*
|
|
207
|
-
*
|
|
310
|
+
* `NoInfer` takes this parameter out of the inference race `R` used to be
|
|
311
|
+
* decided by. It does NOT make the source order irrelevant — `execute` is an
|
|
312
|
+
* inline arrow whose parameters are contextually typed, so its return type is
|
|
313
|
+
* inferred in a LATER pass than this signature is checked, and a `summary`
|
|
314
|
+
* written above it still lands on `unknown`. What changed is that the failure
|
|
315
|
+
* is now LOUD: with no `isToolFailure` ternary left to swallow it, the wrong
|
|
316
|
+
* order is a `TS18046` on the first property read rather than a narrowing
|
|
317
|
+
* that quietly stops meaning anything. So the twelve-line warning this used
|
|
318
|
+
* to carry is the compiler's job now.
|
|
319
|
+
*/
|
|
320
|
+
summary: (args: z.output<S>, result: NoInfer<R>) => string;
|
|
321
|
+
/**
|
|
322
|
+
* Handed the store as its second argument, and SYNCHRONOUS.
|
|
208
323
|
*
|
|
209
324
|
* **The draft is passed in rather than re-read**, which is the one change the
|
|
210
325
|
* durable store forced on this template. The body used to open with
|
|
@@ -218,7 +333,7 @@ interface RetailToolSpec<S extends z.ZodType<Record<string, unknown>>, R> {
|
|
|
218
333
|
* so none of them wants to; a tool that DID would await outside the wrapper and
|
|
219
334
|
* call `retailSlot.update` itself, the way `plan-and-execute`'s do.
|
|
220
335
|
*/
|
|
221
|
-
execute: (args: z.output<S>, state: RetailState, ctx: ToolContext) => R;
|
|
336
|
+
execute: (args: z.output<S>, state: RetailState, ctx: ToolContext) => R | ToolFailure;
|
|
222
337
|
}
|
|
223
338
|
|
|
224
339
|
function record(state: RetailState, name: string, summary: string): void {
|
|
@@ -231,13 +346,26 @@ function record(state: RetailState, name: string, summary: string): void {
|
|
|
231
346
|
}
|
|
232
347
|
|
|
233
348
|
/**
|
|
234
|
-
* Every retail tool is built through this. It owns
|
|
235
|
-
*
|
|
349
|
+
* Every retail tool is built through this. It owns two things no tool body may
|
|
350
|
+
* re-implement:
|
|
351
|
+
*
|
|
352
|
+
* 1. the mutation window the body's draft comes from,
|
|
353
|
+
* 2. the `callSeq` increment + activity entry — the reason the UI moves on
|
|
354
|
+
* every tool call rather than only when a projected value happens to differ.
|
|
236
355
|
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
356
|
+
* The third thing it used to own — the authentication gate — is
|
|
357
|
+
* {@link callFlow}'s now, declared per tool as `when`. What that buys is in the
|
|
358
|
+
* machine's own doc; what it COSTS is one line of the activity feed: a refused
|
|
359
|
+
* call short-circuits before this wrapper's body runs, so a blocked call no
|
|
360
|
+
* longer records `blocked: not authenticated` and no longer bumps `callSeq`.
|
|
361
|
+
* That is the right trade — the refusal reaches the model, which the sidebar
|
|
362
|
+
* line never did, and it carries the state and its instruction rather than one
|
|
363
|
+
* fixed sentence.
|
|
364
|
+
*
|
|
365
|
+
* **`callFlow.tool` rather than `retailSlot.updateTool`**, so the body opens the
|
|
366
|
+
* store's window itself. A flow tool's own `execute` is handed `(args, ctx)`;
|
|
367
|
+
* everything else about a tool body here is unchanged, including that it is
|
|
368
|
+
* synchronous — the window cannot span an await.
|
|
241
369
|
*
|
|
242
370
|
* `focus` is deliberately left to tool bodies (`setFocus`): it is a UI nicety,
|
|
243
371
|
* not an invariant, and only the body knows what the call was about.
|
|
@@ -245,26 +373,25 @@ function record(state: RetailState, name: string, summary: string): void {
|
|
|
245
373
|
export function retailTool<S extends z.ZodType<Record<string, unknown>>, R>(
|
|
246
374
|
spec: RetailToolSpec<S, R>,
|
|
247
375
|
) {
|
|
248
|
-
|
|
249
|
-
// `updateTool` rather than `tool` + a hand-written `retailSlot.update`: it runs
|
|
250
|
-
// the body inside the slot's mutation window and hands it the draft, so this
|
|
251
|
-
// wrapper is left with only what is specific to THIS agent.
|
|
252
|
-
return retailSlot.updateTool({
|
|
376
|
+
return callFlow.tool({
|
|
253
377
|
description: spec.description,
|
|
254
378
|
inputSchema: spec.inputSchema,
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
spec.
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
379
|
+
when: spec.when,
|
|
380
|
+
// `omitUndefined` rather than a conditional spread: `exactOptionalPropertyTypes`
|
|
381
|
+
// is on, so an explicit `send: undefined` is not the same as an absent one —
|
|
382
|
+
// and only three of the fifteen tools declare an event (guard-invariants
|
|
383
|
+
// rule 2).
|
|
384
|
+
...omitUndefined({ send: spec.send }),
|
|
385
|
+
execute: (args, ctx) =>
|
|
386
|
+
retailSlot.update(ctx, (state) => {
|
|
387
|
+
const typedArgs = args as z.output<S>;
|
|
388
|
+
const result = spec.execute(typedArgs, state, ctx);
|
|
389
|
+
record(
|
|
390
|
+
state,
|
|
391
|
+
spec.name,
|
|
392
|
+
isToolFailure(result) ? `error: ${result.error}` : spec.summary(typedArgs, result),
|
|
393
|
+
);
|
|
394
|
+
return result;
|
|
395
|
+
}),
|
|
269
396
|
});
|
|
270
397
|
}
|
|
@@ -3,12 +3,20 @@ 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
|
+
# Where the call is
|
|
7
|
+
|
|
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
|
+
|
|
6
13
|
# Authenticate first
|
|
7
14
|
|
|
8
15
|
Before anything else, identify who you are talking to by finding their user id
|
|
9
16
|
— by email (`find_user_id_by_email`), or by first name, last name and zip code
|
|
10
17
|
(`find_user_id_by_name_zip`) if they cannot remember the email. Do this **even
|
|
11
|
-
if the caller volunteers their user id**.
|
|
18
|
+
if the caller volunteers their user id**. Until you have, every other tool will
|
|
19
|
+
refuse.
|
|
12
20
|
|
|
13
21
|
Prefer email. Fall back to name + zip only when they cannot recall the address
|
|
14
22
|
on the account.
|
|
@@ -45,7 +53,8 @@ speak; when you speak, do not also make a tool call.
|
|
|
45
53
|
Transfer only if the caller explicitly asks for a human, or their request
|
|
46
54
|
cannot be handled with your tools and this policy. Call
|
|
47
55
|
`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.
|
|
56
|
+
to a human agent. Please hold on." Say nothing else after that. The call is over
|
|
57
|
+
for you at that point, and every tool will refuse — including that one.
|
|
49
58
|
|
|
50
59
|
# Speaking on the phone
|
|
51
60
|
|
|
@@ -9,6 +9,7 @@ const CANCEL_REASONS = ["no longer needed", "ordered by mistake"] as const;
|
|
|
9
9
|
|
|
10
10
|
export default retailTool({
|
|
11
11
|
name: "cancel_pending_order",
|
|
12
|
+
when: "serving",
|
|
12
13
|
description:
|
|
13
14
|
"Cancel a pending order. Only an order whose status is exactly 'pending' can be cancelled — " +
|
|
14
15
|
"check the status first. The reason must be either 'no longer needed' or 'ordered by mistake'. " +
|
|
@@ -70,6 +71,5 @@ export default retailTool({
|
|
|
70
71
|
}`,
|
|
71
72
|
};
|
|
72
73
|
},
|
|
73
|
-
summary: (_args, result) =>
|
|
74
|
-
isToolFailure(result) ? "cancel failed" : `cancelled ${result.order_id}`,
|
|
74
|
+
summary: (_args, result) => `cancelled ${result.order_id}`,
|
|
75
75
|
});
|
|
@@ -6,6 +6,7 @@ import { assertCanCoverDiff, planItemSwap } from "../swap.ts";
|
|
|
6
6
|
|
|
7
7
|
export default retailTool({
|
|
8
8
|
name: "exchange_delivered_order_items",
|
|
9
|
+
when: "serving",
|
|
9
10
|
description:
|
|
10
11
|
"Request an exchange of items in a delivered order for different options of the SAME products. " +
|
|
11
12
|
"Only a 'delivered' order can be exchanged, and only once. Remind the caller to name EVERY " +
|
|
@@ -87,6 +88,5 @@ export default retailTool({
|
|
|
87
88
|
: `Exchange requested on ${order.order_id}. $${Math.abs(plan.diff).toFixed(2)} will be refunded to ${args.payment_method_id}. An email with return instructions is on its way.`,
|
|
88
89
|
};
|
|
89
90
|
},
|
|
90
|
-
summary: (_args, result) =>
|
|
91
|
-
isToolFailure(result) ? "exchange failed" : `exchange requested on ${result.order_id}`,
|
|
91
|
+
summary: (_args, result) => `exchange requested on ${result.order_id}`,
|
|
92
92
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { isToolFailure } from "@alexkroman1/aai";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
import { authenticateAs } from "../authenticate.ts";
|
|
4
|
-
import { retailTool } from "../store.ts";
|
|
3
|
+
import { BEFORE_TRANSFER, retailTool } from "../store.ts";
|
|
5
4
|
|
|
6
5
|
export default retailTool({
|
|
7
6
|
name: "find_user_id_by_email",
|
|
@@ -12,7 +11,12 @@ export default retailTool({
|
|
|
12
11
|
inputSchema: z.object({
|
|
13
12
|
email: z.string().max(200).describe("The customer's email, e.g. 'something@example.com'"),
|
|
14
13
|
}),
|
|
15
|
-
|
|
14
|
+
// Legal in `serving` too: a caller repeating their email must not hit an
|
|
15
|
+
// error, and `authenticateAs` is what refuses a switch to a DIFFERENT
|
|
16
|
+
// customer. The `IDENTIFIED` event is not sent on that refusal — a flow tool
|
|
17
|
+
// sends nothing when its body answers a `ToolFailure`.
|
|
18
|
+
when: BEFORE_TRANSFER,
|
|
19
|
+
send: { type: "IDENTIFIED" },
|
|
16
20
|
execute: (args, state) => {
|
|
17
21
|
const target = args.email.trim().toLowerCase();
|
|
18
22
|
const match = Object.values(state.store.users).find(
|
|
@@ -23,6 +27,5 @@ export default retailTool({
|
|
|
23
27
|
}
|
|
24
28
|
return authenticateAs(state, match);
|
|
25
29
|
},
|
|
26
|
-
summary: (_args, result) =>
|
|
27
|
-
isToolFailure(result) ? "lookup failed" : `identified ${result.user_id}`,
|
|
30
|
+
summary: (_args, result) => `identified ${result.user_id}`,
|
|
28
31
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { isToolFailure } from "@alexkroman1/aai";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
import { authenticateAs } from "../authenticate.ts";
|
|
4
|
-
import { retailTool } from "../store.ts";
|
|
3
|
+
import { BEFORE_TRANSFER, retailTool } from "../store.ts";
|
|
5
4
|
|
|
6
5
|
export default retailTool({
|
|
7
6
|
name: "find_user_id_by_name_zip",
|
|
@@ -13,7 +12,8 @@ export default retailTool({
|
|
|
13
12
|
last_name: z.string().max(100).describe("Last name, e.g. 'Doe'"),
|
|
14
13
|
zip: z.string().max(20).describe("Zip code, e.g. '12345'"),
|
|
15
14
|
}),
|
|
16
|
-
|
|
15
|
+
when: BEFORE_TRANSFER,
|
|
16
|
+
send: { type: "IDENTIFIED" },
|
|
17
17
|
execute: (args, state) => {
|
|
18
18
|
const first = args.first_name.trim().toLowerCase();
|
|
19
19
|
const last = args.last_name.trim().toLowerCase();
|
|
@@ -31,6 +31,5 @@ export default retailTool({
|
|
|
31
31
|
}
|
|
32
32
|
return authenticateAs(state, match);
|
|
33
33
|
},
|
|
34
|
-
summary: (_args, result) =>
|
|
35
|
-
isToolFailure(result) ? "lookup failed" : `identified ${result.user_id}`,
|
|
34
|
+
summary: (_args, result) => `identified ${result.user_id}`,
|
|
36
35
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isToolFailure } from "@alexkroman1/aai";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { findItem, retailTool } from "../store.ts";
|
|
3
|
+
import { BEFORE_TRANSFER, findItem, retailTool } from "../store.ts";
|
|
4
4
|
|
|
5
5
|
export default retailTool({
|
|
6
6
|
name: "get_item_details",
|
|
@@ -10,7 +10,7 @@ export default retailTool({
|
|
|
10
10
|
inputSchema: z.object({
|
|
11
11
|
item_id: z.string().max(60).describe("The item id, e.g. '1008292230'"),
|
|
12
12
|
}),
|
|
13
|
-
|
|
13
|
+
when: BEFORE_TRANSFER,
|
|
14
14
|
execute: (args, state) => {
|
|
15
15
|
const found = findItem(state, args.item_id);
|
|
16
16
|
if (isToolFailure(found)) return found;
|
|
@@ -23,6 +23,5 @@ export default retailTool({
|
|
|
23
23
|
available: found.variant.available,
|
|
24
24
|
};
|
|
25
25
|
},
|
|
26
|
-
summary: (_args, result) =>
|
|
27
|
-
isToolFailure(result) ? "item read failed" : `read item ${result.item_id}`,
|
|
26
|
+
summary: (_args, result) => `read item ${result.item_id}`,
|
|
28
27
|
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { isToolFailure } from "@alexkroman1/aai";
|
|
2
|
-
import { omitUndefined } from "@alexkroman1/aai/utils";
|
|
1
|
+
import { isToolFailure, omitUndefined } from "@alexkroman1/aai";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
import { OrderIdField, resolveOrder } from "../resolve.ts";
|
|
5
4
|
import { retailTool, setFocus } from "../store.ts";
|
|
6
5
|
|
|
7
6
|
export default retailTool({
|
|
8
7
|
name: "get_order_details",
|
|
8
|
+
when: "serving",
|
|
9
9
|
description:
|
|
10
10
|
"Get the status and full details of one of the authenticated customer's orders. Accepts the " +
|
|
11
11
|
"order id (e.g. '#W0000000' — note the leading '#'), or a spoken reference such as " +
|
|
@@ -33,6 +33,5 @@ export default retailTool({
|
|
|
33
33
|
...(order.cancel_reason ? { cancel_reason: order.cancel_reason } : {}),
|
|
34
34
|
};
|
|
35
35
|
},
|
|
36
|
-
summary: (_args, result) =>
|
|
37
|
-
isToolFailure(result) ? "order read failed" : `read ${result.order_id}`,
|
|
36
|
+
summary: (_args, result) => `read ${result.order_id}`,
|
|
38
37
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isToolFailure } from "@alexkroman1/aai";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { findProduct, retailTool, setFocus } from "../store.ts";
|
|
3
|
+
import { BEFORE_TRANSFER, findProduct, retailTool, setFocus } from "../store.ts";
|
|
4
4
|
|
|
5
5
|
export default retailTool({
|
|
6
6
|
name: "get_product_details",
|
|
@@ -10,7 +10,7 @@ export default retailTool({
|
|
|
10
10
|
inputSchema: z.object({
|
|
11
11
|
product_id: z.string().max(60).describe("The product id, e.g. '6086499569'"),
|
|
12
12
|
}),
|
|
13
|
-
|
|
13
|
+
when: BEFORE_TRANSFER,
|
|
14
14
|
execute: (args, state) => {
|
|
15
15
|
const product = findProduct(state, args.product_id);
|
|
16
16
|
if (isToolFailure(product)) return product;
|
|
@@ -26,6 +26,5 @@ export default retailTool({
|
|
|
26
26
|
})),
|
|
27
27
|
};
|
|
28
28
|
},
|
|
29
|
-
summary: (_args, result) =>
|
|
30
|
-
isToolFailure(result) ? "product read failed" : `read product ${result.name}`,
|
|
29
|
+
summary: (_args, result) => `read product ${result.name}`,
|
|
31
30
|
});
|
|
@@ -4,6 +4,7 @@ import { requireOwnUser, retailTool } from "../store.ts";
|
|
|
4
4
|
|
|
5
5
|
export default retailTool({
|
|
6
6
|
name: "get_user_details",
|
|
7
|
+
when: "serving",
|
|
7
8
|
description:
|
|
8
9
|
"Get the authenticated customer's profile: name, email, default address, payment methods " +
|
|
9
10
|
"(with gift-card balances) and their order ids with each order's status.",
|
|
@@ -32,6 +33,5 @@ export default retailTool({
|
|
|
32
33
|
})),
|
|
33
34
|
};
|
|
34
35
|
},
|
|
35
|
-
summary: (_args, result) =>
|
|
36
|
-
isToolFailure(result) ? "profile read failed" : `read profile ${result.user_id}`,
|
|
36
|
+
summary: (_args, result) => `read profile ${result.user_id}`,
|
|
37
37
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { isToolFailure } from "@alexkroman1/aai";
|
|
2
1
|
import { z } from "zod";
|
|
3
|
-
import { retailTool } from "../store.ts";
|
|
2
|
+
import { BEFORE_TRANSFER, retailTool } from "../store.ts";
|
|
4
3
|
|
|
5
4
|
export default retailTool({
|
|
6
5
|
name: "list_all_product_types",
|
|
@@ -10,13 +9,14 @@ export default retailTool({
|
|
|
10
9
|
// Empty schema rather than omitting it: the wrapper has one code path, and
|
|
11
10
|
// that is where the per-call UI-update invariant lives.
|
|
12
11
|
inputSchema: z.object({}),
|
|
13
|
-
|
|
12
|
+
// The catalogue is not customer data, so it needs no identified caller — but
|
|
13
|
+
// it is still off limits once the call belongs to a human.
|
|
14
|
+
when: BEFORE_TRANSFER,
|
|
14
15
|
execute: (_args, state) => {
|
|
15
16
|
const entries = Object.values(state.store.products)
|
|
16
17
|
.map((product) => [product.name, product.product_id] as const)
|
|
17
18
|
.sort(([a], [b]) => a.localeCompare(b));
|
|
18
19
|
return { count: entries.length, products: Object.fromEntries(entries) };
|
|
19
20
|
},
|
|
20
|
-
summary: (_args, result) =>
|
|
21
|
-
isToolFailure(result) ? "catalog read failed" : `listed ${result.count} product types`,
|
|
21
|
+
summary: (_args, result) => `listed ${result.count} product types`,
|
|
22
22
|
});
|
|
@@ -6,6 +6,7 @@ import { retailTool, setFocus } from "../store.ts";
|
|
|
6
6
|
|
|
7
7
|
export default retailTool({
|
|
8
8
|
name: "modify_pending_order_address",
|
|
9
|
+
when: "serving",
|
|
9
10
|
description:
|
|
10
11
|
"Change the shipping address of a pending order. Read the new address back to the caller and " +
|
|
11
12
|
"get an explicit yes before calling this. This does not change the customer's default address.",
|
|
@@ -34,6 +35,5 @@ export default retailTool({
|
|
|
34
35
|
message: `Order ${order.order_id} now ships to ${formatAddress(order.address)}.`,
|
|
35
36
|
};
|
|
36
37
|
},
|
|
37
|
-
summary: (_args, result) =>
|
|
38
|
-
isToolFailure(result) ? "address change failed" : `re-addressed ${result.order_id}`,
|
|
38
|
+
summary: (_args, result) => `re-addressed ${result.order_id}`,
|
|
39
39
|
});
|
|
@@ -6,6 +6,7 @@ import { applySwap, assertCanCoverDiff, planItemSwap } from "../swap.ts";
|
|
|
6
6
|
|
|
7
7
|
export default retailTool({
|
|
8
8
|
name: "modify_pending_order_items",
|
|
9
|
+
when: "serving",
|
|
9
10
|
description:
|
|
10
11
|
"Change items in a pending order to different options of the SAME products. This can be done " +
|
|
11
12
|
"ONCE per order and is irreversible — afterwards the order can no longer be cancelled or " +
|
|
@@ -81,7 +82,5 @@ export default retailTool({
|
|
|
81
82
|
};
|
|
82
83
|
},
|
|
83
84
|
summary: (_args, result) =>
|
|
84
|
-
|
|
85
|
-
? "item change failed"
|
|
86
|
-
: `modified ${result.order_id} (${result.price_difference >= 0 ? "+" : ""}${result.price_difference})`,
|
|
85
|
+
`modified ${result.order_id} (${result.price_difference >= 0 ? "+" : ""}${result.price_difference})`,
|
|
87
86
|
});
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
|
|
13
13
|
export default retailTool({
|
|
14
14
|
name: "modify_pending_order_payment",
|
|
15
|
+
when: "serving",
|
|
15
16
|
description:
|
|
16
17
|
"Change which payment method a pending order is charged to. The new method must be different " +
|
|
17
18
|
"from the current one, and a gift card must hold enough to cover the whole order. The original " +
|
|
@@ -89,6 +90,5 @@ export default retailTool({
|
|
|
89
90
|
message: `Order ${order.order_id} is now charged to ${args.payment_method_id}, and $${amount.toFixed(2)} is going back to ${original.payment_method_id}.`,
|
|
90
91
|
};
|
|
91
92
|
},
|
|
92
|
-
summary: (_args, result) =>
|
|
93
|
-
isToolFailure(result) ? "payment change failed" : `re-paid ${result.order_id}`,
|
|
93
|
+
summary: (_args, result) => `re-paid ${result.order_id}`,
|
|
94
94
|
});
|
|
@@ -5,6 +5,7 @@ import { requireOwnUser, retailTool } from "../store.ts";
|
|
|
5
5
|
|
|
6
6
|
export default retailTool({
|
|
7
7
|
name: "modify_user_address",
|
|
8
|
+
when: "serving",
|
|
8
9
|
description:
|
|
9
10
|
"Change the customer's default address, used for future orders. Read the new address back and " +
|
|
10
11
|
"get an explicit yes before calling this. This does not change the address on any existing " +
|
|
@@ -24,6 +25,5 @@ export default retailTool({
|
|
|
24
25
|
message: `Default address updated to ${formatAddress(user.address)}. Existing orders keep their own shipping addresses.`,
|
|
25
26
|
};
|
|
26
27
|
},
|
|
27
|
-
summary: (_args, result) =>
|
|
28
|
-
isToolFailure(result) ? "profile address change failed" : `re-addressed ${result.user_id}`,
|
|
28
|
+
summary: (_args, result) => `re-addressed ${result.user_id}`,
|
|
29
29
|
});
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
|
|
12
12
|
export default retailTool({
|
|
13
13
|
name: "return_delivered_order_items",
|
|
14
|
+
when: "serving",
|
|
14
15
|
description:
|
|
15
16
|
"Request a return of items from a delivered order. Only a 'delivered' order can be returned, " +
|
|
16
17
|
"and only once. The refund must go to the order's ORIGINAL payment method or to one of the " +
|
|
@@ -87,6 +88,5 @@ export default retailTool({
|
|
|
87
88
|
message: `Return requested on ${order.order_id} for ${names}. The customer will get an email with return instructions, and the refund goes to ${args.payment_method_id} once the items arrive.`,
|
|
88
89
|
};
|
|
89
90
|
},
|
|
90
|
-
summary: (_args, result) =>
|
|
91
|
-
isToolFailure(result) ? "return failed" : `return requested on ${result.order_id}`,
|
|
91
|
+
summary: (_args, result) => `return requested on ${result.order_id}`,
|
|
92
92
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { retailTool } from "../store.ts";
|
|
2
|
+
import { BEFORE_TRANSFER, retailTool } from "../store.ts";
|
|
3
3
|
|
|
4
4
|
export default retailTool({
|
|
5
5
|
name: "transfer_to_human_agents",
|
|
@@ -10,9 +10,13 @@ export default retailTool({
|
|
|
10
10
|
inputSchema: z.object({
|
|
11
11
|
summary: z.string().max(2000).describe("A short summary of the caller's issue for the human"),
|
|
12
12
|
}),
|
|
13
|
-
//
|
|
14
|
-
// human, and blocking the escape hatch behind
|
|
15
|
-
|
|
13
|
+
// Legal before the handoff from either side: someone who cannot be identified
|
|
14
|
+
// is exactly who needs a human, and blocking the escape hatch behind an auth
|
|
15
|
+
// gate would trap them. `TRANSFERRED` is the one transition into the terminal
|
|
16
|
+
// state, which is what makes "say nothing else after this" enforced rather
|
|
17
|
+
// than asked for — every tool, this one included, refuses afterwards.
|
|
18
|
+
when: BEFORE_TRANSFER,
|
|
19
|
+
send: { type: "TRANSFERRED" },
|
|
16
20
|
summary: () => "transferred to a human agent",
|
|
17
21
|
execute: (args) => ({
|
|
18
22
|
transferred: true,
|