@alexkroman1/aai-cli 13.3.0 → 15.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-Clbq3a4h.mjs → _agent-BzUeqOdj.mjs} +32 -9
- package/dist/_agent.d.ts +23 -0
- package/dist/_artifacts-BJOYGQPp.mjs +21 -0
- package/dist/_artifacts.d.ts +16 -0
- package/dist/_build-target.d.ts +172 -0
- package/dist/{_bundler-DEkjO1d8.mjs → _bundler-DM0d0M7m.mjs} +8 -7
- package/dist/_cli-common.d.ts +45 -6
- package/dist/{_config-ZY2euhdh.mjs → _config-DYzC6WMD.mjs} +2 -2
- package/dist/_dev-env.d.ts +1 -1
- package/dist/{_dev-server-C13xuGB1.mjs → _dev-server-BzWB6-4y.mjs} +25 -24
- package/dist/_dev-vite-config.d.ts +3 -3
- package/dist/_e2e-test-utils.d.ts +1 -1
- package/dist/{_init-Ci4QGjp8.mjs → _init-Bsi3DZNJ.mjs} +9 -23
- package/dist/{_output-DbJt9gsl.mjs → _output-DBP9Op_d.mjs} +22 -13
- package/dist/_output.d.ts +10 -9
- package/dist/{_preflight-B2laWPdU.mjs → _preflight-CcAR577V.mjs} +2 -2
- package/dist/_resource-commands.d.ts +9 -9
- package/dist/_server-common-De0haHr9.mjs +70 -0
- package/dist/_server-common.d.ts +20 -1
- package/dist/{_slug-api-DlKpj91X.mjs → _slug-api-CGvc2YSQ.mjs} +18 -9
- package/dist/_slug-api.d.ts +19 -0
- package/dist/{_studio-DTi3EdXQ.mjs → _studio-DnR_BqFp.mjs} +7 -3
- package/dist/_studio-commands.d.ts +13 -13
- package/dist/{_templates-V4l5sZJy.mjs → _templates-CIlJ3Vay.mjs} +4 -4
- package/dist/_templates.d.ts +1 -1
- package/dist/{_typecheck-gate-sV4N5F93.mjs → _typecheck-gate-U3jcil8L.mjs} +3 -2
- package/dist/_typecheck-gate.d.ts +3 -1
- package/dist/{_utils-Dhd058D9.mjs → _utils-D5JGcjiW.mjs} +43 -1
- package/dist/_utils.d.ts +35 -0
- package/dist/_vercel-output.d.ts +63 -0
- package/dist/{_workflow-determinism-C9l8uC2R.mjs → _workflow-determinism-CvpqMpdh.mjs} +18 -26
- package/dist/_workflow-determinism.d.ts +3 -3
- package/dist/build-BhEaxBPu.mjs +481 -0
- package/dist/build.d.ts +23 -10
- package/dist/cli.d.ts +1 -2
- package/dist/cli.mjs +155 -138
- package/dist/{client-bundler-CT-_26c5.mjs → client-bundler-6mTLs6ny.mjs} +5 -5
- package/dist/client-bundler.d.ts +1 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-RqlQZ6Y_.mjs → delete-BdFnbnVR.mjs} +4 -4
- package/dist/{deploy-B7f3CK7v.mjs → deploy-CGqPU5U-.mjs} +11 -11
- package/dist/{dev-DamXieW_.mjs → dev-Bx9gYBHM.mjs} +3 -3
- package/dist/{eval-h3DTVfRw.mjs → eval-B3I7FqN9.mjs} +3 -3
- package/dist/{init-D-vFrV8h.mjs → init-CFyusRbq.mjs} +48 -20
- package/dist/init.d.ts +14 -0
- package/dist/{login-CDiemNmC.mjs → login-DaLrPOFg.mjs} +4 -6
- package/dist/{logs-DGwPNCzB.mjs → logs-7BRwq0cG.mjs} +21 -14
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +155 -76
- package/dist/scaffold/package.json +6 -6
- package/dist/{secret-V1w7y9-j.mjs → secret-Cjp912yF.mjs} +5 -5
- package/dist/secret.d.ts +8 -6
- package/dist/start.d.ts +112 -0
- package/dist/start.mjs +156 -0
- package/dist/{studio-BpMCfUNx.mjs → studio-C_zuRC_z.mjs} +13 -15
- package/dist/templates/briefing-desk/agent.eval.test.ts +156 -0
- package/dist/templates/call-audit/agent.test.ts +11 -6
- package/dist/templates/call-audit/agent.ts +2 -2
- package/dist/templates/call-audit/client.tsx +27 -42
- package/dist/templates/call-audit/workflows/audit.ts +7 -5
- package/dist/templates/call-audit/workflows/ingest.ts +7 -7
- package/dist/templates/call-audit/workflows/media.ts +12 -37
- package/dist/templates/call-audit/workflows/summarize.ts +8 -8
- package/dist/templates/call-audit/workflows/sync-api.ts +3 -3
- package/dist/templates/code-interpreter/agent.test.ts +103 -0
- package/dist/templates/dispatch-center/agent.test.ts +7 -7
- package/dist/templates/dispatch-center/client.tsx +53 -61
- package/dist/templates/dispatch-center/shared.ts +0 -8
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +4 -1
- package/dist/templates/embedded-assets/agent.eval.test.ts +4 -2
- package/dist/templates/health-assistant/agent.eval.test.ts +4 -2
- package/dist/templates/infocom-adventure/client.tsx +40 -36
- package/dist/templates/link-digest/agent.test.ts +3 -3
- package/dist/templates/link-digest/agent.ts +2 -2
- package/dist/templates/link-digest/client.tsx +81 -19
- package/dist/templates/link-digest/workflows/digest.ts +13 -16
- package/dist/templates/math-buddy/agent.test.ts +126 -0
- package/dist/templates/night-owl/agent.eval.test.ts +4 -2
- package/dist/templates/night-owl/client.tsx +8 -2
- package/dist/templates/personal-finance/agent.test.ts +127 -0
- package/dist/templates/pipeline-simple/agent.test.ts +2 -2
- package/dist/templates/pizza-ordering/client.tsx +5 -4
- package/dist/templates/pizza-ordering/shared.ts +3 -8
- package/dist/templates/pizza-ordering/tools/add_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/place_order.ts +3 -2
- package/dist/templates/pizza-ordering/tools/remove_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/update_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/view_order.ts +3 -2
- package/dist/templates/plan-and-execute/agent.test.ts +10 -5
- package/dist/templates/plan-and-execute/client.tsx +2 -2
- package/dist/templates/podcast-digest/agent.test.ts +20 -11
- package/dist/templates/podcast-digest/client.tsx +16 -22
- package/dist/templates/podcast-digest/workflows/digest.ts +21 -19
- package/dist/templates/podcast-digest/workflows/feeds.ts +61 -33
- package/dist/templates/podcast-digest/workflows/slack.ts +7 -7
- package/dist/templates/recap-workflow/agent.eval.test.ts +21 -13
- package/dist/templates/recap-workflow/agent.test.ts +27 -23
- package/dist/templates/recap-workflow/workflows/recap.ts +24 -28
- package/dist/templates/redline/agent.eval.test.ts +5 -8
- package/dist/templates/redline/agent.test.ts +1 -1
- package/dist/templates/redline/client.tsx +36 -34
- package/dist/templates/redline/workflows/redline.ts +17 -17
- package/dist/templates/research-workflow/agent.eval.test.ts +5 -5
- package/dist/templates/research-workflow/agent.test.ts +10 -7
- package/dist/templates/research-workflow/workflows/research.ts +28 -30
- package/dist/templates/retail/agent.test.ts +17 -15
- package/dist/templates/retail/cancel.ts +7 -6
- package/dist/templates/retail/client.tsx +39 -58
- package/dist/templates/retail/payment.ts +4 -3
- package/dist/templates/retail/pending.test.ts +19 -19
- package/dist/templates/retail/store.test.ts +5 -5
- package/dist/templates/retail/swap.ts +8 -11
- package/dist/templates/solo-rpg/agent.eval.test.ts +2 -16
- package/dist/templates/solo-rpg/agent.test.ts +12 -12
- package/dist/templates/solo-rpg/client.tsx +3 -3
- package/dist/templates/spoken-summary/agent.test.ts +9 -9
- package/dist/templates/spoken-summary/agent.ts +3 -3
- package/dist/templates/spoken-summary/client.tsx +23 -30
- package/dist/templates/spoken-summary/workflows/summarize.ts +11 -11
- package/dist/templates/spoken-summary/workflows/transcribe.ts +14 -14
- package/dist/templates/support-line/agent.ts +8 -0
- package/dist/templates/support-line/client.tsx +4 -3
- package/dist/templates/support-line/prompts.ts +0 -2
- package/dist/templates/transcription-workflow/agent.test.ts +20 -12
- package/dist/templates/transcription-workflow/agent.ts +2 -2
- package/dist/templates/transcription-workflow/client.tsx +15 -14
- package/dist/templates/transcription-workflow/recover.ts +1 -1
- package/dist/templates/transcription-workflow/run-panel.tsx +34 -14
- package/dist/templates/transcription-workflow/total-latency.tsx +46 -20
- package/dist/templates/transcription-workflow/workflows/batch.ts +15 -15
- package/dist/templates/transcription-workflow/workflows/downsample.ts +1 -1
- package/dist/templates/transcription-workflow/workflows/normalize.ts +15 -19
- package/dist/templates/transcription-workflow/workflows/stream.ts +12 -12
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +3 -3
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +23 -34
- package/dist/templates/travel-concierge/agent.eval.test.ts +2 -2
- package/dist/templates/travel-concierge/agent.test.ts +15 -7
- package/dist/templates/travel-concierge/client.tsx +2 -2
- package/dist/templates/travel-concierge/routing.ts +64 -55
- package/dist/templates/travel-concierge/shared.ts +10 -14
- package/dist/templates/travel-concierge/tools/cancel_action.ts +3 -1
- package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +3 -1
- package/dist/templates/travel-concierge/tools/confirm_action.ts +3 -1
- package/dist/templates/travel-concierge/tools/lookup_booking.ts +4 -3
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_excursions.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_flights.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_hotels.ts +3 -2
- package/dist/templates/web-researcher/agent.test.ts +130 -0
- package/dist/{test-guenJUxi.mjs → test-D032V2cA.mjs} +4 -17
- package/dist/typecheck.mjs +1 -1
- package/dist/worker-bundler.d.ts +1 -1
- package/dist/worker-bundler.mjs +8 -8
- package/dist/{workflow-BZyKLkhV.mjs → workflow-CVioyO3V.mjs} +6 -5
- package/package.json +9 -4
- package/dist/_server-common-BV4_CrP3.mjs +0 -43
- package/dist/build-CWyFGe-W.mjs +0 -69
- package/dist/scaffold/server.mjs +0 -204
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { isToolFailure, type ToolFailure } from "@alexkroman1/aai";
|
|
13
|
+
import { formatMoney } from "@alexkroman1/aai/utils";
|
|
13
14
|
import { creditRefund, REFUND_DELAY_NOTE, REFUND_IMMEDIATE_NOTE } from "./refund.ts";
|
|
14
15
|
import { resolveOrder } from "./resolve.ts";
|
|
15
16
|
import type { RetailState } from "./shared.ts";
|
|
16
|
-
import {
|
|
17
|
+
import { money } from "./store.ts";
|
|
17
18
|
|
|
18
19
|
/** The only two reasons the store records. */
|
|
19
20
|
export const CANCEL_REASONS = ["no longer needed", "ordered by mistake"] as const;
|
|
@@ -42,9 +43,9 @@ export function planCancel(
|
|
|
42
43
|
spokenOrderId: string,
|
|
43
44
|
reason: CancelReason,
|
|
44
45
|
): CancelPlan | ToolFailure {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
// No `authenticatedUser` gate here: `resolveOrder` opens with the identical
|
|
47
|
+
// call and returns the identical failure, so a second one is a second store
|
|
48
|
+
// lookup for a value this planner never reads.
|
|
48
49
|
const order = resolveOrder(state, spokenOrderId);
|
|
49
50
|
if (isToolFailure(order)) return order;
|
|
50
51
|
|
|
@@ -75,7 +76,7 @@ export function planCancel(
|
|
|
75
76
|
return {
|
|
76
77
|
readBack:
|
|
77
78
|
`cancel order ${order.order_id} (${items}) as '${reason}' and refund ` +
|
|
78
|
-
|
|
79
|
+
`${formatMoney(total)} to ${destinations}`,
|
|
79
80
|
orderId: order.order_id,
|
|
80
81
|
reason,
|
|
81
82
|
refunds,
|
|
@@ -109,7 +110,7 @@ export function applyCancel(state: RetailState, plan: CancelPlan) {
|
|
|
109
110
|
cancel_reason: plan.reason,
|
|
110
111
|
refunded: plan.total,
|
|
111
112
|
refund_immediate: immediate,
|
|
112
|
-
message: `Order ${plan.orderId} is cancelled and
|
|
113
|
+
message: `Order ${plan.orderId} is cancelled and ${formatMoney(plan.total)} is being refunded. ${
|
|
113
114
|
immediate ? REFUND_IMMEDIATE_NOTE : REFUND_DELAY_NOTE
|
|
114
115
|
}`,
|
|
115
116
|
};
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { formatMoney, plural } from "@alexkroman1/aai/utils";
|
|
1
2
|
import "@alexkroman1/aai-ui/styles.css";
|
|
2
|
-
import type { AgentState, ConversationItem
|
|
3
|
+
import type { AgentState, ConversationItem } from "@alexkroman1/aai-ui";
|
|
3
4
|
import {
|
|
4
5
|
AutoScroll,
|
|
5
|
-
|
|
6
|
+
mountClient,
|
|
7
|
+
SessionErrorBanner,
|
|
6
8
|
useAgentState,
|
|
7
9
|
useConversation,
|
|
8
|
-
|
|
10
|
+
useSessionActions,
|
|
9
11
|
useSessionSelector,
|
|
12
|
+
useSessionStatus,
|
|
10
13
|
} from "@alexkroman1/aai-ui";
|
|
11
14
|
import type { ReactNode } from "react";
|
|
12
15
|
import type {
|
|
@@ -100,7 +103,7 @@ function MethodRow({ method }: { method: PaymentMethodView }) {
|
|
|
100
103
|
</span>
|
|
101
104
|
) : (
|
|
102
105
|
<span className="font-semibold tabular-nums" style={{ color: "#15803d" }}>
|
|
103
|
-
|
|
106
|
+
{formatMoney(method.balance)}
|
|
104
107
|
</span>
|
|
105
108
|
)}
|
|
106
109
|
</div>
|
|
@@ -132,8 +135,7 @@ function OrderCard({ order, focused }: { order: OrderView; focused: boolean }) {
|
|
|
132
135
|
</span>
|
|
133
136
|
</div>
|
|
134
137
|
<div className="text-[11px] mb-1 tabular-nums" style={{ color: "#71717a" }}>
|
|
135
|
-
|
|
136
|
-
{order.items.length === 1 ? "" : "s"}
|
|
138
|
+
{formatMoney(order.total)} · {order.items.length} {plural(order.items.length, "item")}
|
|
137
139
|
</div>
|
|
138
140
|
{order.items.slice(0, 3).map((item) => (
|
|
139
141
|
<div
|
|
@@ -246,7 +248,7 @@ function SwapOptions({ option }: { option: SwapOptionView }) {
|
|
|
246
248
|
>
|
|
247
249
|
<span>{Object.values(alternative.options).join(", ")}</span>
|
|
248
250
|
<span className="tabular-nums whitespace-nowrap" style={{ color: "#71717a" }}>
|
|
249
|
-
|
|
251
|
+
{formatMoney(alternative.price)}
|
|
250
252
|
</span>
|
|
251
253
|
</div>
|
|
252
254
|
))
|
|
@@ -344,8 +346,16 @@ function Conversation() {
|
|
|
344
346
|
{streaming}
|
|
345
347
|
</div>
|
|
346
348
|
)}
|
|
349
|
+
{/* Same contract as the shipped `MessageList`'s indicator: three pulsing
|
|
350
|
+
dots are the only sign the agent is working, and to a screen reader
|
|
351
|
+
they are punctuation. */}
|
|
347
352
|
{thinking && (
|
|
348
|
-
<div
|
|
353
|
+
<div
|
|
354
|
+
role="status"
|
|
355
|
+
aria-label="Agent is thinking"
|
|
356
|
+
className="self-start text-[11px] px-3.5"
|
|
357
|
+
style={{ color: "#a1a1aa" }}
|
|
358
|
+
>
|
|
349
359
|
<span style={{ animation: "rt-pulse 1.2s ease-in-out infinite" }}>· · ·</span>
|
|
350
360
|
</div>
|
|
351
361
|
)}
|
|
@@ -370,7 +380,7 @@ function Conversation() {
|
|
|
370
380
|
/** The live status dot, on its own subscription rather than a field off a
|
|
371
381
|
* whole-page read — the header is the only thing here that wants it. */
|
|
372
382
|
function StatusReadout() {
|
|
373
|
-
const state =
|
|
383
|
+
const state = useSessionStatus();
|
|
374
384
|
return (
|
|
375
385
|
<>
|
|
376
386
|
<span
|
|
@@ -393,58 +403,29 @@ function StatusReadout() {
|
|
|
393
403
|
);
|
|
394
404
|
}
|
|
395
405
|
|
|
396
|
-
|
|
397
|
-
* The
|
|
406
|
+
/*
|
|
407
|
+
* The call controls.
|
|
398
408
|
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
* `ConsoleShell` carries it, and this template's two-pane page cannot use that
|
|
404
|
-
* shell as its frame; the attribute is what it is here for.
|
|
409
|
+
* The methods come off `useSessionActions()`, which does not subscribe, and the
|
|
410
|
+
* two flags off one-field selectors. The whole-snapshot `useSession()` this
|
|
411
|
+
* replaced re-rendered these four buttons on every STT partial to read two
|
|
412
|
+
* booleans that change once a call.
|
|
405
413
|
*/
|
|
406
|
-
function ErrorBanner() {
|
|
407
|
-
const error = useSessionSelector((s) => s.error);
|
|
408
|
-
if (!error) return null;
|
|
409
|
-
return (
|
|
410
|
-
<div
|
|
411
|
-
role="alert"
|
|
412
|
-
className="px-4 py-2 text-xs"
|
|
413
|
-
style={{ background: "#fef2f2", color: "#b91c1c", borderTop: "1px solid #fecaca" }}
|
|
414
|
-
>
|
|
415
|
-
{error.message} ({error.code})
|
|
416
|
-
</div>
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Start a fresh conversation without leaving the console.
|
|
422
|
-
*
|
|
423
|
-
* Written out here rather than reached for on the session, because there is no
|
|
424
|
-
* one method that does it: `reset()` clears the CONVERSATION and keeps the
|
|
425
|
-
* session, which is wrong for any agent that also keeps session-scoped state —
|
|
426
|
-
* this one's store would come back with the next tool call.
|
|
427
|
-
*/
|
|
428
|
-
function newConversation(session: Session): void {
|
|
429
|
-
session.end();
|
|
430
|
-
session.start();
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/** The call controls: the one place a whole-session read is what is wanted —
|
|
434
|
-
* `started`, `running` and four methods, in four buttons. */
|
|
435
414
|
function CallControls({ productCount }: { productCount: number }) {
|
|
436
|
-
const
|
|
415
|
+
const { start, toggle, restart, end } = useSessionActions();
|
|
416
|
+
const started = useSessionSelector((s) => s.started);
|
|
417
|
+
const running = useSessionSelector((s) => s.running);
|
|
437
418
|
return (
|
|
438
419
|
<div
|
|
439
420
|
className="flex items-center gap-2 px-4 py-3"
|
|
440
421
|
style={{ background: "#ffffff", borderTop: "1px solid #e4e4e7" }}
|
|
441
422
|
>
|
|
442
|
-
{!
|
|
423
|
+
{!started ? (
|
|
443
424
|
<button
|
|
444
425
|
type="button"
|
|
445
426
|
className="px-4 py-2 border-none rounded-md text-xs font-semibold cursor-pointer text-white"
|
|
446
427
|
style={{ background: "#2563eb" }}
|
|
447
|
-
onClick={() =>
|
|
428
|
+
onClick={() => start()}
|
|
448
429
|
>
|
|
449
430
|
Start call
|
|
450
431
|
</button>
|
|
@@ -454,12 +435,12 @@ function CallControls({ productCount }: { productCount: number }) {
|
|
|
454
435
|
type="button"
|
|
455
436
|
className="px-4 py-2 border-none rounded-md text-xs font-semibold cursor-pointer"
|
|
456
437
|
style={{
|
|
457
|
-
background:
|
|
458
|
-
color:
|
|
438
|
+
background: running ? "#e4e4e7" : "#2563eb",
|
|
439
|
+
color: running ? "#18181b" : "#ffffff",
|
|
459
440
|
}}
|
|
460
|
-
onClick={() =>
|
|
441
|
+
onClick={() => toggle()}
|
|
461
442
|
>
|
|
462
|
-
{
|
|
443
|
+
{running ? "Hold" : "Resume"}
|
|
463
444
|
</button>
|
|
464
445
|
{/* The one-click new conversation the default shell's
|
|
465
446
|
`<Controls>` gives every other template — a custom
|
|
@@ -472,7 +453,7 @@ function CallControls({ productCount }: { productCount: number }) {
|
|
|
472
453
|
type="button"
|
|
473
454
|
className="px-4 py-2 rounded-md text-xs font-semibold cursor-pointer"
|
|
474
455
|
style={{ background: "#ffffff", color: "#18181b", border: "1px solid #e4e4e7" }}
|
|
475
|
-
onClick={
|
|
456
|
+
onClick={restart}
|
|
476
457
|
>
|
|
477
458
|
New Conversation
|
|
478
459
|
</button>
|
|
@@ -484,7 +465,7 @@ function CallControls({ productCount }: { productCount: number }) {
|
|
|
484
465
|
type="button"
|
|
485
466
|
className="px-4 py-2 border-none rounded-md text-xs font-semibold cursor-pointer text-white"
|
|
486
467
|
style={{ background: "#dc2626" }}
|
|
487
|
-
onClick={() =>
|
|
468
|
+
onClick={() => end()}
|
|
488
469
|
>
|
|
489
470
|
End
|
|
490
471
|
</button>
|
|
@@ -545,7 +526,7 @@ function App() {
|
|
|
545
526
|
style={{ borderRight: "1px solid #e4e4e7" }}
|
|
546
527
|
>
|
|
547
528
|
<Conversation />
|
|
548
|
-
<
|
|
529
|
+
<SessionErrorBanner className="rounded-none border-x-0 border-b-0" />
|
|
549
530
|
<CallControls productCount={view.productCount} />
|
|
550
531
|
</div>
|
|
551
532
|
|
|
@@ -642,7 +623,7 @@ function App() {
|
|
|
642
623
|
)}
|
|
643
624
|
</span>
|
|
644
625
|
<span className="tabular-nums whitespace-nowrap ml-3">
|
|
645
|
-
{view.callSeq}
|
|
626
|
+
{view.callSeq} {plural(view.callSeq, "call")}
|
|
646
627
|
</span>
|
|
647
628
|
</div>
|
|
648
629
|
</div>
|
|
@@ -650,7 +631,7 @@ function App() {
|
|
|
650
631
|
);
|
|
651
632
|
}
|
|
652
633
|
|
|
653
|
-
|
|
634
|
+
mountClient({
|
|
654
635
|
component: App,
|
|
655
636
|
theme: {
|
|
656
637
|
bg: "#f4f4f5",
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { isToolFailure, type ToolFailure } from "@alexkroman1/aai";
|
|
7
|
+
import { formatMoney } from "@alexkroman1/aai/utils";
|
|
7
8
|
import { resolveOrder } from "./resolve.ts";
|
|
8
9
|
import type { RetailState } from "./shared.ts";
|
|
9
10
|
import { authenticatedUser, findPaymentMethod, isGiftCard, money } from "./store.ts";
|
|
@@ -53,13 +54,13 @@ export function planPayment(
|
|
|
53
54
|
const amount = original.amount;
|
|
54
55
|
if (isGiftCard(newMethod) && newMethod.balance < amount) {
|
|
55
56
|
return {
|
|
56
|
-
error: `Gift card ${newMethodId}'s balance (
|
|
57
|
+
error: `Gift card ${newMethodId}'s balance (${formatMoney(newMethod.balance)}) does not cover the ${formatMoney(amount)} order total.`,
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
return {
|
|
61
62
|
readBack:
|
|
62
|
-
`charge order ${order.order_id} —
|
|
63
|
+
`charge order ${order.order_id} — ${formatMoney(amount)} — to ${newMethodId} instead, ` +
|
|
63
64
|
`refunding ${original.payment_method_id}`,
|
|
64
65
|
orderId: order.order_id,
|
|
65
66
|
newMethodId,
|
|
@@ -102,6 +103,6 @@ export function applyPayment(state: RetailState, plan: PaymentPlan) {
|
|
|
102
103
|
amount: plan.amount,
|
|
103
104
|
paid_with: plan.newMethodId,
|
|
104
105
|
refunded_to: plan.oldMethodId,
|
|
105
|
-
message: `Order ${plan.orderId} is now charged to ${plan.newMethodId}, and
|
|
106
|
+
message: `Order ${plan.orderId} is now charged to ${plan.newMethodId}, and ${formatMoney(plan.amount)} is going back to ${plan.oldMethodId}.`,
|
|
106
107
|
};
|
|
107
108
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ToolContext } from "@alexkroman1/aai";
|
|
2
2
|
import { isToolFailure } from "@alexkroman1/aai";
|
|
3
|
-
import { createToolContext,
|
|
3
|
+
import { createToolContext, expectDialogOk, expectToolOk } from "@alexkroman1/aai/testing";
|
|
4
4
|
import { describe, expect, test } from "vitest";
|
|
5
5
|
import type { StagedResult } from "./pending.ts";
|
|
6
6
|
import { retailSlot } from "./store.ts";
|
|
@@ -17,7 +17,7 @@ import transferToHumanAgents from "./tools/transfer_to_human_agents.ts";
|
|
|
17
17
|
* one delivered order (#W4316152), which is every shape these cases need. */
|
|
18
18
|
async function aaravCtx(): Promise<ToolContext> {
|
|
19
19
|
const ctx = createToolContext();
|
|
20
|
-
|
|
20
|
+
expectToolOk(await findUserIdByEmail.execute({ email: "aarav.anderson9752@example.com" }, ctx));
|
|
21
21
|
return ctx;
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -29,7 +29,7 @@ describe("staging", () => {
|
|
|
29
29
|
const ctx = await aaravCtx();
|
|
30
30
|
const before = structuredClone(retailSlot.get(ctx).store.orders["#W9300146"]);
|
|
31
31
|
|
|
32
|
-
const staged =
|
|
32
|
+
const staged = expectDialogOk<StagedResult>(await stageCancel(ctx));
|
|
33
33
|
|
|
34
34
|
expect(staged.state).toBe("serving.awaitingConfirmation");
|
|
35
35
|
expect(staged.result.staged).toBe("cancel_pending_order");
|
|
@@ -43,7 +43,7 @@ describe("staging", () => {
|
|
|
43
43
|
|
|
44
44
|
test("the readback names the order, the items, the amount and where the money goes", async () => {
|
|
45
45
|
const ctx = await aaravCtx();
|
|
46
|
-
const staged =
|
|
46
|
+
const staged = expectToolOk<StagedResult>(await stageCancel(ctx));
|
|
47
47
|
expect(staged.read_back).toContain("#W9300146");
|
|
48
48
|
expect(staged.read_back).toContain("153.23");
|
|
49
49
|
expect(staged.read_back).toContain("gift_card_7245904");
|
|
@@ -52,14 +52,14 @@ describe("staging", () => {
|
|
|
52
52
|
|
|
53
53
|
test("the result says out loud that nothing has happened", async () => {
|
|
54
54
|
const ctx = await aaravCtx();
|
|
55
|
-
const staged =
|
|
55
|
+
const staged = expectToolOk<StagedResult>(await stageCancel(ctx));
|
|
56
56
|
expect(staged.message).toContain("NOTHING HAS CHANGED YET");
|
|
57
57
|
expect(staged.message).toContain("confirm_change");
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
test("a second stage is refused, and the refusal names the one already waiting", async () => {
|
|
61
61
|
const ctx = await aaravCtx();
|
|
62
|
-
|
|
62
|
+
expectToolOk(await stageCancel(ctx));
|
|
63
63
|
|
|
64
64
|
// A DIFFERENT change, so this is not a repeat of the first call.
|
|
65
65
|
const second = await modifyUserAddress.execute(
|
|
@@ -89,9 +89,9 @@ describe("staging", () => {
|
|
|
89
89
|
describe("confirming", () => {
|
|
90
90
|
test("confirm_change applies the staged change and returns to helping", async () => {
|
|
91
91
|
const ctx = await aaravCtx();
|
|
92
|
-
|
|
92
|
+
expectToolOk(await stageCancel(ctx));
|
|
93
93
|
|
|
94
|
-
const done =
|
|
94
|
+
const done = expectDialogOk<{ confirmed: string; status: string }>(
|
|
95
95
|
await confirmChange.execute({}, ctx),
|
|
96
96
|
);
|
|
97
97
|
expect(done.state).toBe("serving.helping");
|
|
@@ -114,8 +114,8 @@ describe("confirming", () => {
|
|
|
114
114
|
|
|
115
115
|
test("confirming twice is refused — the second call has nothing staged", async () => {
|
|
116
116
|
const ctx = await aaravCtx();
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
expectToolOk(await stageCancel(ctx));
|
|
118
|
+
expectToolOk(await confirmChange.execute({}, ctx));
|
|
119
119
|
|
|
120
120
|
const again = await confirmChange.execute({}, ctx);
|
|
121
121
|
expect(isToolFailure(again)).toBe(true);
|
|
@@ -130,9 +130,9 @@ describe("confirming", () => {
|
|
|
130
130
|
describe("cancelling a staged change", () => {
|
|
131
131
|
test("cancel_change drops it, changes nothing, and frees the call", async () => {
|
|
132
132
|
const ctx = await aaravCtx();
|
|
133
|
-
|
|
133
|
+
expectToolOk(await stageCancel(ctx));
|
|
134
134
|
|
|
135
|
-
const dropped =
|
|
135
|
+
const dropped = expectDialogOk<{ dropped: string | null; message: string }>(
|
|
136
136
|
await cancelChange.execute({}, ctx),
|
|
137
137
|
);
|
|
138
138
|
expect(dropped.state).toBe("serving.helping");
|
|
@@ -144,12 +144,12 @@ describe("cancelling a staged change", () => {
|
|
|
144
144
|
|
|
145
145
|
test("a corrected change can be staged straight after", async () => {
|
|
146
146
|
const ctx = await aaravCtx();
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
expectToolOk(await stageCancel(ctx));
|
|
148
|
+
expectToolOk(await cancelChange.execute({}, ctx));
|
|
149
149
|
|
|
150
150
|
// The caller changed their mind about which order — the very thing
|
|
151
151
|
// `cancel_change` exists for.
|
|
152
|
-
const restaged =
|
|
152
|
+
const restaged = expectToolOk<StagedResult>(
|
|
153
153
|
await returnDeliveredOrderItems.execute(
|
|
154
154
|
{
|
|
155
155
|
order_id: "#W4316152",
|
|
@@ -166,9 +166,9 @@ describe("cancelling a staged change", () => {
|
|
|
166
166
|
describe("what stays legal while a change waits", () => {
|
|
167
167
|
test("a read is still answerable — 'what was the total again?'", async () => {
|
|
168
168
|
const ctx = await aaravCtx();
|
|
169
|
-
|
|
169
|
+
expectToolOk(await stageCancel(ctx));
|
|
170
170
|
// `when: "serving"` matches both children, which is what makes this work.
|
|
171
|
-
const read =
|
|
171
|
+
const read = expectDialogOk<{ order_id: string }>(
|
|
172
172
|
await getOrderDetails.execute({ order_id: "#W9300146" }, ctx),
|
|
173
173
|
);
|
|
174
174
|
expect(read.result.order_id).toBe("#W9300146");
|
|
@@ -178,9 +178,9 @@ describe("what stays legal while a change waits", () => {
|
|
|
178
178
|
|
|
179
179
|
test("a caller can still ask for a human, and everything refuses afterwards", async () => {
|
|
180
180
|
const ctx = await aaravCtx();
|
|
181
|
-
|
|
181
|
+
expectToolOk(await stageCancel(ctx));
|
|
182
182
|
|
|
183
|
-
const transferred =
|
|
183
|
+
const transferred = expectDialogOk(
|
|
184
184
|
await transferToHumanAgents.execute({ summary: "wants a human" }, ctx),
|
|
185
185
|
);
|
|
186
186
|
// `TRANSFERRED` is declared on the `serving` PARENT, which is what lets it
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isToolFailure, type ToolContext } from "@alexkroman1/aai";
|
|
2
|
-
import { createToolContext,
|
|
2
|
+
import { createToolContext, expectDialogOk, expectToolOk } from "@alexkroman1/aai/testing";
|
|
3
3
|
import { describe, expect, test } from "vitest";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import {
|
|
@@ -237,16 +237,16 @@ describe("retailTool", () => {
|
|
|
237
237
|
test("runs once the flow says serving", async () => {
|
|
238
238
|
const ctx = makeCtx();
|
|
239
239
|
serve(ctx);
|
|
240
|
-
expect(
|
|
240
|
+
expect(expectToolOk(await gated.execute({}, ctx))).toEqual({ ok: true });
|
|
241
241
|
});
|
|
242
242
|
|
|
243
243
|
test("the result carries the position the call landed in", async () => {
|
|
244
244
|
const ctx = makeCtx();
|
|
245
245
|
serve(ctx);
|
|
246
|
-
// `
|
|
246
|
+
// `expectDialogOk` rather than a cast to `{ instruction?: string }`: it keeps
|
|
247
247
|
// the envelope this test is about, and a refusal fails here naming what the
|
|
248
248
|
// flow said instead of reading `undefined` off a field nobody assigned.
|
|
249
|
-
const answered =
|
|
249
|
+
const answered = expectDialogOk<{ ok: boolean }>(await gated.execute({}, ctx));
|
|
250
250
|
expect(answered).toMatchObject({ state: "serving.helping", done: false });
|
|
251
251
|
expect(answered.instruction).toMatch(/one identified customer/);
|
|
252
252
|
});
|
|
@@ -307,7 +307,7 @@ describe("the call flow", () => {
|
|
|
307
307
|
summary: () => "ran",
|
|
308
308
|
execute: () => ({ ok: true }),
|
|
309
309
|
});
|
|
310
|
-
expect(
|
|
310
|
+
expect(expectToolOk(await anywhere.execute({}, ctx))).toEqual({ ok: true });
|
|
311
311
|
|
|
312
312
|
const at = callFlow.send(ctx, { type: "TRANSFERRED" });
|
|
313
313
|
expect(at.state).toBe("transferred");
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { isToolFailure, type ToolFailure } from "@alexkroman1/aai";
|
|
10
|
+
import { formatMoney } from "@alexkroman1/aai/utils";
|
|
10
11
|
import { resolveOrder } from "./resolve.ts";
|
|
11
12
|
import type { Order, OrderItem, RetailState, User, Variant } from "./shared.ts";
|
|
12
13
|
import {
|
|
@@ -48,7 +49,6 @@ export interface SwapLine {
|
|
|
48
49
|
fromOptions: Record<string, string>;
|
|
49
50
|
fromPrice: number;
|
|
50
51
|
toItemId: string;
|
|
51
|
-
toName: string;
|
|
52
52
|
toOptions: Record<string, string>;
|
|
53
53
|
toPrice: number;
|
|
54
54
|
}
|
|
@@ -146,9 +146,6 @@ export function toSwapLines(plan: SwapPlan): SwapLine[] {
|
|
|
146
146
|
fromOptions: { ...pair.item.options },
|
|
147
147
|
fromPrice: pair.item.price,
|
|
148
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
149
|
toOptions: { ...pair.newVariant.options },
|
|
153
150
|
toPrice: pair.newVariant.price,
|
|
154
151
|
}));
|
|
@@ -163,7 +160,7 @@ export function assertCanCoverDiff(user: User, methodId: string, diff: number):
|
|
|
163
160
|
if (isToolFailure(method)) return method;
|
|
164
161
|
if (isGiftCard(method) && method.balance < diff) {
|
|
165
162
|
return {
|
|
166
|
-
error: `Gift card ${methodId}'s balance (
|
|
163
|
+
error: `Gift card ${methodId}'s balance (${formatMoney(method.balance)}) does not cover the ${formatMoney(diff)} difference. Ask for another payment method.`,
|
|
167
164
|
};
|
|
168
165
|
}
|
|
169
166
|
return null;
|
|
@@ -190,8 +187,8 @@ function describeLine(line: SwapLine): string {
|
|
|
190
187
|
}
|
|
191
188
|
|
|
192
189
|
function describeDiff(diff: number, methodId: string): string {
|
|
193
|
-
if (diff > 0) return
|
|
194
|
-
if (diff < 0) return
|
|
190
|
+
if (diff > 0) return `${formatMoney(diff)} charged to ${methodId}`;
|
|
191
|
+
if (diff < 0) return `${formatMoney(Math.abs(diff))} refunded to ${methodId}`;
|
|
195
192
|
return "no price difference";
|
|
196
193
|
}
|
|
197
194
|
|
|
@@ -275,8 +272,8 @@ export function applyModifyItems(state: RetailState, plan: ModifyItemsPlan) {
|
|
|
275
272
|
})),
|
|
276
273
|
message:
|
|
277
274
|
plan.diff > 0
|
|
278
|
-
? `Done.
|
|
279
|
-
: `Done.
|
|
275
|
+
? `Done. ${formatMoney(plan.diff)} was charged to ${plan.paymentMethodId}. This order can no longer be modified or cancelled.`
|
|
276
|
+
: `Done. ${formatMoney(Math.abs(plan.diff))} is being refunded to ${plan.paymentMethodId}. This order can no longer be modified or cancelled.`,
|
|
280
277
|
};
|
|
281
278
|
}
|
|
282
279
|
|
|
@@ -358,7 +355,7 @@ export function applyExchange(state: RetailState, plan: ExchangePlan) {
|
|
|
358
355
|
})),
|
|
359
356
|
message:
|
|
360
357
|
plan.diff > 0
|
|
361
|
-
? `Exchange requested on ${plan.orderId}.
|
|
362
|
-
: `Exchange requested on ${plan.orderId}.
|
|
358
|
+
? `Exchange requested on ${plan.orderId}. ${formatMoney(plan.diff)} will be charged to ${plan.paymentMethodId}. An email with return instructions is on its way.`
|
|
359
|
+
: `Exchange requested on ${plan.orderId}. ${formatMoney(Math.abs(plan.diff))} will be refunded to ${plan.paymentMethodId}. An email with return instructions is on its way.`,
|
|
363
360
|
};
|
|
364
361
|
}
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* deployed.
|
|
34
34
|
*/
|
|
35
35
|
import agentDef from "virtual:aai/agent";
|
|
36
|
-
import { type EvalTurn, toolResultIn } from "@alexkroman1/aai-runtime/eval";
|
|
36
|
+
import { type EvalTurn, toolResultIn, toolResultsIn } from "@alexkroman1/aai-runtime/eval";
|
|
37
37
|
import { describeEval } from "@alexkroman1/aai-runtime/eval/vitest";
|
|
38
38
|
import { expect } from "vitest";
|
|
39
39
|
import { z } from "zod";
|
|
@@ -82,20 +82,6 @@ const Refusal = z.object({ error: z.string() });
|
|
|
82
82
|
const answerOf = <T>(turn: EvalTurn, name: string, schema: z.ZodType<T>): T =>
|
|
83
83
|
toolResultIn(turn.toolCalls, name, schema);
|
|
84
84
|
|
|
85
|
-
/**
|
|
86
|
-
* What EVERY call to `name` answered on this turn, in call order.
|
|
87
|
-
*
|
|
88
|
-
* The plural form is for a claim about calls that may legitimately not have
|
|
89
|
-
* happened — a refusal the narrator may have pre-empted by not calling at all.
|
|
90
|
-
* `toolResultIn` over a ONE-CALL list per call: the name is that call's own, so
|
|
91
|
-
* the reader's "no such call" and "two calls" throws are unreachable and what
|
|
92
|
-
* is left is the parse, the schema, and the "never completed" failure.
|
|
93
|
-
*/
|
|
94
|
-
const answersOf = <T>(turn: EvalTurn, name: string, schema: z.ZodType<T>): T[] =>
|
|
95
|
-
turn.toolCalls
|
|
96
|
-
.filter((c) => c.name === name)
|
|
97
|
-
.map((call) => toolResultIn([call], call.name, schema));
|
|
98
|
-
|
|
99
85
|
/**
|
|
100
86
|
* Every field `setup_character` requires, for the SCRIPTED runs.
|
|
101
87
|
*
|
|
@@ -267,7 +253,7 @@ describeEval(agentDef, (test) => {
|
|
|
267
253
|
expect(answerOf(down, "update_state", Settled).state).toBe("gameOver");
|
|
268
254
|
|
|
269
255
|
const after = await session.say("I refuse to die. Roll to fight on.");
|
|
270
|
-
const attempts =
|
|
256
|
+
const attempts = toolResultsIn(after.toolCalls, "action_roll", Refusal);
|
|
271
257
|
// The gate is only OBSERVABLE if something calls the gated tool, and the
|
|
272
258
|
// script is what guarantees that — hence the exact count. `gameOver` was
|
|
273
259
|
// once a flag nothing acted on, so a player with both tracks empty could
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ToolContext, ToolDef, ToolInputSchema } from "@alexkroman1/aai";
|
|
2
2
|
import { isToolFailure } from "@alexkroman1/aai";
|
|
3
|
-
import { createToolContext,
|
|
3
|
+
import { createToolContext, expectToolOk } from "@alexkroman1/aai/testing";
|
|
4
4
|
import { describe, expect, test, vi } from "vitest";
|
|
5
5
|
import {
|
|
6
6
|
applyConsequences,
|
|
@@ -24,11 +24,11 @@ import updateState from "./tools/update_state.ts";
|
|
|
24
24
|
|
|
25
25
|
// ── Test doubles ─────────────────────────────────────────────────────────────
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
/** Each call gets its own slot store, which is what makes two contexts two
|
|
28
|
+
* games. `send` is the recorder `createToolContext` installs — no test here
|
|
29
|
+
* asserts on it, and overriding it would only throw the recording away. */
|
|
30
30
|
function makeCtx(): ToolContext {
|
|
31
|
-
return createToolContext(
|
|
31
|
+
return createToolContext();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const SETUP_ARGS = {
|
|
@@ -288,7 +288,7 @@ describe("burn_momentum", () => {
|
|
|
288
288
|
const ctx = makeCtx();
|
|
289
289
|
seedRolledState(8, ctx); // 8 beats both dice (3, 5)
|
|
290
290
|
|
|
291
|
-
const result =
|
|
291
|
+
const result = expectToolOk<Record<string, unknown>>(await callNoArgs(burnMomentum, ctx));
|
|
292
292
|
expect(result.burned).toBe(true);
|
|
293
293
|
expect(result.newResultCode).toBe("STRONG_HIT");
|
|
294
294
|
|
|
@@ -302,7 +302,7 @@ describe("burn_momentum", () => {
|
|
|
302
302
|
test("momentum beating only one die upgrades a MISS to WEAK_HIT", async () => {
|
|
303
303
|
const ctx = makeCtx();
|
|
304
304
|
seedRolledState(4, ctx); // beats 3, not 5
|
|
305
|
-
const result =
|
|
305
|
+
const result = expectToolOk<{ newResultCode: string }>(await callNoArgs(burnMomentum, ctx));
|
|
306
306
|
expect(result.newResultCode).toBe("WEAK_HIT");
|
|
307
307
|
});
|
|
308
308
|
|
|
@@ -528,7 +528,7 @@ describe("update_state", () => {
|
|
|
528
528
|
state.clocks[0]!.filled = 3; // 3 of 4
|
|
529
529
|
seedPlaying(ctx, state);
|
|
530
530
|
|
|
531
|
-
const result =
|
|
531
|
+
const result = expectToolOk<{ clockEvents: { clock: string; trigger: string }[] }>(
|
|
532
532
|
await updateState.execute({ advanceClockName: "Doom" }, ctx),
|
|
533
533
|
);
|
|
534
534
|
expect(result.clockEvents).toEqual([{ clock: "Doom", trigger: "The doom arrives" }]);
|
|
@@ -542,7 +542,7 @@ describe("update_state", () => {
|
|
|
542
542
|
}
|
|
543
543
|
seedPlaying(ctx, state);
|
|
544
544
|
|
|
545
|
-
const result =
|
|
545
|
+
const result = expectToolOk<{ warnings?: string[] }>(
|
|
546
546
|
await updateState.execute({ addNpcName: "One Too Many" }, ctx),
|
|
547
547
|
);
|
|
548
548
|
expect(result.warnings?.[0]).toMatch(/NPC limit/);
|
|
@@ -609,7 +609,7 @@ describe("the story flow", () => {
|
|
|
609
609
|
expect(created.state).toBe("playing.awaitingRoll");
|
|
610
610
|
expect(created.instruction).toMatch(/action_roll/);
|
|
611
611
|
|
|
612
|
-
|
|
612
|
+
expectToolOk(
|
|
613
613
|
await actionRoll.execute(
|
|
614
614
|
{ move: "clash", stat: "iron", position: "risky", effect: "standard", purpose: "swing" },
|
|
615
615
|
ctx,
|
|
@@ -618,7 +618,7 @@ describe("the story flow", () => {
|
|
|
618
618
|
expect(storyFlow.position(ctx).state).toBe("playing.rollResolved");
|
|
619
619
|
|
|
620
620
|
// Moving the scene on SPENDS the roll: the burn window is closed.
|
|
621
|
-
|
|
621
|
+
expectToolOk(await updateState.execute({ location: "The Bridge" }, ctx));
|
|
622
622
|
expect(storyFlow.position(ctx).state).toBe("playing.awaitingRoll");
|
|
623
623
|
expect(isToolFailure(await callNoArgs(burnMomentum, ctx))).toBe(true);
|
|
624
624
|
});
|
|
@@ -644,7 +644,7 @@ describe("the story flow", () => {
|
|
|
644
644
|
// act on it, so a player could keep rolling after both tracks emptied. The
|
|
645
645
|
// WRITE is `gameSlot`'s `after` hook; this tool no longer calls it, which
|
|
646
646
|
// is the point of moving it there.
|
|
647
|
-
|
|
647
|
+
expectToolOk(await updateState.execute({ health: 0, spirit: 0 }, ctx));
|
|
648
648
|
const at = storyFlow.position(ctx);
|
|
649
649
|
expect(at.state).toBe("gameOver");
|
|
650
650
|
expect(at.done).toBe(true);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "@alexkroman1/aai-ui/styles.css";
|
|
2
2
|
import type { DeepReadonly } from "@alexkroman1/aai";
|
|
3
|
-
import {
|
|
3
|
+
import { mountClient, useAgentState } from "@alexkroman1/aai-ui";
|
|
4
4
|
import type { ReactNode } from "react";
|
|
5
5
|
import {
|
|
6
6
|
type Clock,
|
|
@@ -782,7 +782,7 @@ function Sidebar() {
|
|
|
782
782
|
|
|
783
783
|
// ── App ──────────────────────────────────────────────────────────────────────
|
|
784
784
|
|
|
785
|
-
|
|
785
|
+
mountClient({
|
|
786
786
|
name: "Solo RPG",
|
|
787
787
|
icon: (
|
|
788
788
|
<span style={{ fontSize: "28px", color: C.accent }}>
|
|
@@ -805,7 +805,7 @@ client({
|
|
|
805
805
|
});
|
|
806
806
|
|
|
807
807
|
// No `onSessionId`/`resumeSessionId` here any more, and their removal is a FIX
|
|
808
|
-
// rather than a simplification. `
|
|
808
|
+
// rather than a simplification. `createBrowserSession` remembers the id in
|
|
809
809
|
// `sessionStorage` by default now — see `session-resume-store.ts`, whose doc
|
|
810
810
|
// names this template as the one client of fourteen that had wired it by hand.
|
|
811
811
|
// What it had wired was `localStorage`, which is the wrong store for a pointer
|