@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,15 +1,19 @@
|
|
|
1
|
-
import type { Db, ToolContext } from "@alexkroman1/aai";
|
|
2
|
-
import {
|
|
1
|
+
import type { Db, ToolContext, ToolDef, ToolInputSchema } from "@alexkroman1/aai";
|
|
2
|
+
import { isToolFailure } from "@alexkroman1/aai";
|
|
3
|
+
import { createToolContext, ok } from "@alexkroman1/aai/testing";
|
|
3
4
|
import { describe, expect, test, vi } from "vitest";
|
|
4
5
|
import {
|
|
5
6
|
applyConsequences,
|
|
6
7
|
DEFAULT_STATE,
|
|
7
8
|
type GameState,
|
|
8
9
|
gameSlot,
|
|
10
|
+
inCrisis,
|
|
11
|
+
isGameOver,
|
|
9
12
|
MAX_NPCS,
|
|
10
13
|
MIN_MOMENTUM,
|
|
11
14
|
makeNpc,
|
|
12
15
|
rollAction,
|
|
16
|
+
storyFlow,
|
|
13
17
|
} from "./shared.ts";
|
|
14
18
|
import actionRoll from "./tools/action_roll.ts";
|
|
15
19
|
import burnMomentum from "./tools/burn_momentum.ts";
|
|
@@ -76,7 +80,6 @@ const SETUP_ARGS = {
|
|
|
76
80
|
function playingState(): GameState {
|
|
77
81
|
const state = structuredClone(DEFAULT_STATE);
|
|
78
82
|
state.initialized = true;
|
|
79
|
-
state.phase = "playing";
|
|
80
83
|
state.sceneCount = 3;
|
|
81
84
|
state.npcs.push(makeNpc({ id: "npc_1", name: "Mira", disposition: "neutral" }));
|
|
82
85
|
state.clocks.push({
|
|
@@ -90,6 +93,39 @@ function playingState(): GameState {
|
|
|
90
93
|
return state;
|
|
91
94
|
}
|
|
92
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Seed a context with a mid-game campaign AND the matching flow position.
|
|
98
|
+
*
|
|
99
|
+
* Writing the slot alone is no longer enough: `action_roll`, `update_state`,
|
|
100
|
+
* `burn_momentum` and `save_game` gate on `storyFlow`, so a campaign installed
|
|
101
|
+
* behind the machine's back leaves every one of them refusing. That is the point
|
|
102
|
+
* of the gate, and it is what this helper exists to satisfy honestly — through
|
|
103
|
+
* the flow's own event, not by writing its snapshot.
|
|
104
|
+
*/
|
|
105
|
+
function seedPlaying(ctx: ToolContext, state: GameState = playingState()): GameState {
|
|
106
|
+
gameSlot.set(ctx, state);
|
|
107
|
+
storyFlow.send(ctx, { type: "SETUP" });
|
|
108
|
+
return state;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Call a tool that declares no `inputSchema`.
|
|
113
|
+
*
|
|
114
|
+
* `ToolDef["execute"]`'s first parameter is derived from the schema, so a tool
|
|
115
|
+
* with none types it as a shape no object literal satisfies — and nine call
|
|
116
|
+
* sites here had each cast their way past it. This is the one narrowing they all
|
|
117
|
+
* wanted: `check_state` and `burn_momentum` really do take no arguments, and a
|
|
118
|
+
* helper says that once where a cast per call site said nothing.
|
|
119
|
+
*
|
|
120
|
+
* It is generic in `R` because the three tool builders thread their result type
|
|
121
|
+
* out now, so the ARGUMENT is the only thing here that needs laundering — where
|
|
122
|
+
* every call site used to follow this one with a second cast of its own to
|
|
123
|
+
* recover a return type the erasure had thrown away.
|
|
124
|
+
*/
|
|
125
|
+
function callNoArgs<R>(def: ToolDef<ToolInputSchema, R>, ctx: ToolContext): Promise<Awaited<R>> {
|
|
126
|
+
return Promise.resolve((def.execute as (args: unknown, c: ToolContext) => R)({}, ctx));
|
|
127
|
+
}
|
|
128
|
+
|
|
93
129
|
// ── setup_character ──────────────────────────────────────────────────────────
|
|
94
130
|
|
|
95
131
|
describe("setup_character", () => {
|
|
@@ -147,9 +183,7 @@ describe("setup_character", () => {
|
|
|
147
183
|
// session ids would prove nothing extra, and `sessionSlot` could stop
|
|
148
184
|
// keying by session with this still passing.
|
|
149
185
|
await setupCharacter.execute(SETUP_ARGS, makeCtx());
|
|
150
|
-
const other =
|
|
151
|
-
initialized: boolean;
|
|
152
|
-
};
|
|
186
|
+
const other = await callNoArgs(checkState, makeCtx());
|
|
153
187
|
expect(other.initialized).toBe(false);
|
|
154
188
|
});
|
|
155
189
|
});
|
|
@@ -200,15 +234,20 @@ describe("applyConsequences MISS matrix", () => {
|
|
|
200
234
|
expect(state.spirit).toBe(4);
|
|
201
235
|
});
|
|
202
236
|
|
|
203
|
-
test("filling the threat clock emits its trigger event and
|
|
237
|
+
test("filling the threat clock emits its trigger event, and 0 health is a crisis", () => {
|
|
204
238
|
const state = playingState();
|
|
205
239
|
state.clocks[0]!.filled = 3; // one tick from full
|
|
206
240
|
state.health = 2;
|
|
207
241
|
const { clockEvents } = applyConsequences(state, miss, "risky", "standard", null);
|
|
208
242
|
expect(clockEvents).toEqual([{ clock: "Doom", trigger: "The doom arrives" }]);
|
|
209
243
|
expect(state.health).toBe(0);
|
|
210
|
-
|
|
211
|
-
|
|
244
|
+
// The two flags are DERIVED now, not written here: `applyConsequences` used
|
|
245
|
+
// to end with `updateCrisisFlags(game)`, one of three hand-placed calls
|
|
246
|
+
// that are `gameSlot`'s `after` hook instead. So this asserts the rule
|
|
247
|
+
// rather than the write — the flags on a stored game are checked through
|
|
248
|
+
// the tools below, which is where a stale one would actually bite.
|
|
249
|
+
expect(inCrisis(state)).toBe(true);
|
|
250
|
+
expect(isGameOver(state)).toBe(false);
|
|
212
251
|
});
|
|
213
252
|
|
|
214
253
|
test("strong hit with great effect gains +3 momentum and shifts disposition on compel", () => {
|
|
@@ -264,14 +303,19 @@ describe("burn_momentum", () => {
|
|
|
264
303
|
clockTicks: 1,
|
|
265
304
|
},
|
|
266
305
|
};
|
|
267
|
-
|
|
306
|
+
seedPlaying(ctx, state);
|
|
307
|
+
// The campaign records a roll, so the FLOW has to say one is standing —
|
|
308
|
+
// `playing.rollResolved` is the state `burn_momentum` gates on. Sent as an
|
|
309
|
+
// event rather than written into the snapshot, so the seed goes through the
|
|
310
|
+
// same door `action_roll` does.
|
|
311
|
+
storyFlow.send(ctx, { type: "ROLLED" });
|
|
268
312
|
}
|
|
269
313
|
|
|
270
314
|
test("a legal burn reverts the miss's consequences, upgrades, and resets momentum", async () => {
|
|
271
315
|
const ctx = makeCtx();
|
|
272
316
|
seedRolledState(8, ctx); // 8 beats both dice (3, 5)
|
|
273
317
|
|
|
274
|
-
const result = (await burnMomentum
|
|
318
|
+
const result = ok<Record<string, unknown>>(await callNoArgs(burnMomentum, ctx));
|
|
275
319
|
expect(result.burned).toBe(true);
|
|
276
320
|
expect(result.newResultCode).toBe("STRONG_HIT");
|
|
277
321
|
|
|
@@ -285,34 +329,42 @@ describe("burn_momentum", () => {
|
|
|
285
329
|
test("momentum beating only one die upgrades a MISS to WEAK_HIT", async () => {
|
|
286
330
|
const ctx = makeCtx();
|
|
287
331
|
seedRolledState(4, ctx); // beats 3, not 5
|
|
288
|
-
const result = (await burnMomentum
|
|
332
|
+
const result = ok<{ newResultCode: string }>(await callNoArgs(burnMomentum, ctx));
|
|
289
333
|
expect(result.newResultCode).toBe("WEAK_HIT");
|
|
290
334
|
});
|
|
291
335
|
|
|
292
|
-
test("burn is refused with no
|
|
336
|
+
test("burn is refused with no roll standing, insufficient momentum, or a strong hit", async () => {
|
|
293
337
|
const ctx = makeCtx();
|
|
294
338
|
|
|
295
|
-
// No roll yet
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
339
|
+
// No roll yet — and this refusal is now the FLOW's rather than a null check
|
|
340
|
+
// inside the body: nothing has rolled, so the game is in
|
|
341
|
+
// `playing.awaitingRoll` and this tool is not available there. The message
|
|
342
|
+
// names the position and quotes what that state expects.
|
|
343
|
+
seedPlaying(ctx);
|
|
344
|
+
// The flow's refusal, so it arrives as a `ToolFailure` rather than under
|
|
345
|
+
// the position envelope — which the narrowing now says out loud.
|
|
346
|
+
let result = await callNoArgs(burnMomentum, ctx);
|
|
347
|
+
expect(isToolFailure(result) && result.error).toMatch(/awaitingRoll/);
|
|
348
|
+
expect(isToolFailure(result) && result.error).toMatch(/action_roll/);
|
|
349
|
+
|
|
350
|
+
// Momentum too low to beat either die — a DATA rule, so it stays in the
|
|
351
|
+
// body and the tool still runs.
|
|
300
352
|
seedRolledState(2, ctx);
|
|
301
|
-
result =
|
|
302
|
-
expect(result.error).toMatch(/not high enough/);
|
|
353
|
+
result = await callNoArgs(burnMomentum, ctx);
|
|
354
|
+
expect(isToolFailure(result) && result.error).toMatch(/not high enough/);
|
|
303
355
|
|
|
304
356
|
// Strong hits cannot be upgraded
|
|
305
357
|
seedRolledState(8, ctx);
|
|
306
358
|
gameSlot.update(ctx, (state) => {
|
|
307
359
|
state.lastRoll!.result = "STRONG_HIT";
|
|
308
360
|
});
|
|
309
|
-
result =
|
|
310
|
-
expect(result.error).toMatch(/already a Strong Hit/);
|
|
361
|
+
result = await callNoArgs(burnMomentum, ctx);
|
|
362
|
+
expect(isToolFailure(result) && result.error).toMatch(/already a Strong Hit/);
|
|
311
363
|
});
|
|
312
364
|
|
|
313
365
|
test("action_roll persists the roll so burn needs no dice arguments", async () => {
|
|
314
366
|
const ctx = makeCtx();
|
|
315
|
-
|
|
367
|
+
seedPlaying(ctx);
|
|
316
368
|
await actionRoll.execute(
|
|
317
369
|
{ move: "clash", stat: "iron", position: "risky", effect: "standard", purpose: "attack" },
|
|
318
370
|
ctx,
|
|
@@ -385,7 +437,7 @@ describe("oracle", () => {
|
|
|
385
437
|
const ctx = makeCtx();
|
|
386
438
|
const state = playingState();
|
|
387
439
|
state.chaosFactor = 9; // threshold 6 — a roll of 1 lands
|
|
388
|
-
|
|
440
|
+
seedPlaying(ctx, state);
|
|
389
441
|
mockRoll(1, 10);
|
|
390
442
|
|
|
391
443
|
const result = (await oracle.execute({ type: "chaos_check" }, ctx)) as {
|
|
@@ -405,7 +457,7 @@ describe("oracle", () => {
|
|
|
405
457
|
const ctx = makeCtx();
|
|
406
458
|
const state = playingState();
|
|
407
459
|
state.chaosFactor = 3; // threshold 0 — `checkChaosInterrupt` returns early
|
|
408
|
-
|
|
460
|
+
seedPlaying(ctx, state);
|
|
409
461
|
|
|
410
462
|
const result = (await oracle.execute({ type: "chaos_check" }, ctx)) as {
|
|
411
463
|
interrupted: boolean;
|
|
@@ -419,7 +471,7 @@ describe("oracle", () => {
|
|
|
419
471
|
const ctx = makeCtx();
|
|
420
472
|
const state = playingState();
|
|
421
473
|
state.chaosFactor = 5; // threshold 2
|
|
422
|
-
|
|
474
|
+
seedPlaying(ctx, state);
|
|
423
475
|
mockRoll(10, 10); // past the threshold
|
|
424
476
|
|
|
425
477
|
const result = (await oracle.execute({ type: "chaos_check" }, ctx)) as {
|
|
@@ -460,7 +512,7 @@ describe("oracle", () => {
|
|
|
460
512
|
|
|
461
513
|
test("the four inspiration branches answer without touching the game", async () => {
|
|
462
514
|
const ctx = makeCtx();
|
|
463
|
-
|
|
515
|
+
seedPlaying(ctx);
|
|
464
516
|
const before = structuredClone(gameSlot.get(ctx));
|
|
465
517
|
|
|
466
518
|
const reaction = (await oracle.execute({ type: "npc_reaction" }, ctx)) as { reaction: string };
|
|
@@ -485,7 +537,7 @@ describe("oracle", () => {
|
|
|
485
537
|
describe("update_state", () => {
|
|
486
538
|
test("clock ids never collide after a removal (max-scan, not length+1)", async () => {
|
|
487
539
|
const ctx = makeCtx();
|
|
488
|
-
|
|
540
|
+
seedPlaying(ctx); // has clock_1
|
|
489
541
|
|
|
490
542
|
await updateState.execute({ addClockName: "Second" }, ctx); // clock_2
|
|
491
543
|
await updateState.execute({ removeClockName: "Doom" }, ctx); // removes clock_1
|
|
@@ -501,11 +553,11 @@ describe("update_state", () => {
|
|
|
501
553
|
const ctx = makeCtx();
|
|
502
554
|
const state = playingState();
|
|
503
555
|
state.clocks[0]!.filled = 3; // 3 of 4
|
|
504
|
-
|
|
556
|
+
seedPlaying(ctx, state);
|
|
505
557
|
|
|
506
|
-
const result =
|
|
507
|
-
|
|
508
|
-
|
|
558
|
+
const result = ok<{ clockEvents: { clock: string; trigger: string }[] }>(
|
|
559
|
+
await updateState.execute({ advanceClockName: "Doom" }, ctx),
|
|
560
|
+
);
|
|
509
561
|
expect(result.clockEvents).toEqual([{ clock: "Doom", trigger: "The doom arrives" }]);
|
|
510
562
|
});
|
|
511
563
|
|
|
@@ -515,11 +567,11 @@ describe("update_state", () => {
|
|
|
515
567
|
while (state.npcs.length < MAX_NPCS) {
|
|
516
568
|
state.npcs.push(makeNpc({ id: `npc_${state.npcs.length + 1}`, name: "Extra" }));
|
|
517
569
|
}
|
|
518
|
-
|
|
570
|
+
seedPlaying(ctx, state);
|
|
519
571
|
|
|
520
|
-
const result =
|
|
521
|
-
|
|
522
|
-
|
|
572
|
+
const result = ok<{ warnings?: string[] }>(
|
|
573
|
+
await updateState.execute({ addNpcName: "One Too Many" }, ctx),
|
|
574
|
+
);
|
|
523
575
|
expect(result.warnings?.[0]).toMatch(/NPC limit/);
|
|
524
576
|
const after = gameSlot.get(ctx);
|
|
525
577
|
expect(after.npcs).toHaveLength(MAX_NPCS);
|
|
@@ -555,21 +607,21 @@ describe("save_game / load_game", () => {
|
|
|
555
607
|
const played = playingState();
|
|
556
608
|
played.playerName = "Kael";
|
|
557
609
|
played.sceneCount = 7;
|
|
558
|
-
|
|
559
|
-
const saved =
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
>;
|
|
610
|
+
seedPlaying(sessionA, played);
|
|
611
|
+
const saved = ok<{ saved: boolean; slot: string }>(
|
|
612
|
+
await saveGame.execute({ slot: "chapter-2" }, sessionA),
|
|
613
|
+
);
|
|
563
614
|
expect(saved.saved).toBe(true);
|
|
564
615
|
expect(saved.slot).toBe("chapter-2");
|
|
565
616
|
expect(rows.get("save:chapter-2")).toMatchObject({ playerName: "Kael", sceneCount: 7 });
|
|
566
617
|
|
|
567
618
|
// Session B (a fresh game slot, the same app db) resumes it.
|
|
568
619
|
const sessionB = makeCtx(db);
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
620
|
+
// `load_game` is a plain `tool()`, so its result is the union its body
|
|
621
|
+
// writes rather than a position envelope — the guard is what picks the
|
|
622
|
+
// arm, where the old `as Record<string, unknown>` picked neither.
|
|
623
|
+
const loaded = await loadGame.execute({ slot: "chapter-2" }, sessionB);
|
|
624
|
+
if (isToolFailure(loaded)) throw new Error(`load refused: ${loaded.error}`);
|
|
573
625
|
expect(loaded.loaded).toBe(true);
|
|
574
626
|
expect(loaded.playerName).toBe("Kael");
|
|
575
627
|
expect(loaded.sceneCount).toBe(7);
|
|
@@ -578,14 +630,14 @@ describe("save_game / load_game", () => {
|
|
|
578
630
|
|
|
579
631
|
test("loading a missing slot reports an error instead of resetting the game", async () => {
|
|
580
632
|
const ctx = makeCtx();
|
|
581
|
-
const result =
|
|
582
|
-
expect(result.error).toMatch(/No save found/);
|
|
633
|
+
const result = await loadGame.execute({ slot: "nope" }, ctx);
|
|
634
|
+
expect(isToolFailure(result) && result.error).toMatch(/No save found/);
|
|
583
635
|
});
|
|
584
636
|
|
|
585
637
|
test("saving twice to one slot upserts — the newer save wins", async () => {
|
|
586
638
|
const { db, rows } = makeDb();
|
|
587
639
|
const ctx = makeCtx(db);
|
|
588
|
-
|
|
640
|
+
seedPlaying(ctx);
|
|
589
641
|
await saveGame.execute({}, ctx); // autosave
|
|
590
642
|
gameSlot.update(ctx, (game) => {
|
|
591
643
|
game.sceneCount = 9;
|
|
@@ -595,3 +647,130 @@ describe("save_game / load_game", () => {
|
|
|
595
647
|
expect(rows.get("save:autosave")).toMatchObject({ sceneCount: 9 });
|
|
596
648
|
});
|
|
597
649
|
});
|
|
650
|
+
|
|
651
|
+
// ── the story flow ───────────────────────────────────────────────────────────
|
|
652
|
+
|
|
653
|
+
describe("the story flow", () => {
|
|
654
|
+
test("a fresh session is awaiting setup, and the play tools refuse there", async () => {
|
|
655
|
+
const ctx = makeCtx();
|
|
656
|
+
expect(storyFlow.position(ctx).state).toBe("awaitingSetup");
|
|
657
|
+
|
|
658
|
+
// All three of these used to RUN before a character existed: `action_roll`
|
|
659
|
+
// rolled 2d6 against the stats of nobody and applied consequences to a game
|
|
660
|
+
// that was not there, and `save_game` wrote an empty campaign to a slot
|
|
661
|
+
// `load_game` would later restore over a real one.
|
|
662
|
+
for (const call of [
|
|
663
|
+
actionRoll.execute(
|
|
664
|
+
{ move: "clash", stat: "iron", position: "risky", effect: "standard", purpose: "swing" },
|
|
665
|
+
ctx,
|
|
666
|
+
),
|
|
667
|
+
updateState.execute({ location: "Nowhere" }, ctx),
|
|
668
|
+
callNoArgs(burnMomentum, ctx),
|
|
669
|
+
saveGame.execute({}, ctx),
|
|
670
|
+
]) {
|
|
671
|
+
const refusal = await call;
|
|
672
|
+
expect(isToolFailure(refusal)).toBe(true);
|
|
673
|
+
expect(isToolFailure(refusal) && refusal.error).toMatch(/awaitingSetup/);
|
|
674
|
+
expect(isToolFailure(refusal) && refusal.error).toMatch(/setup_character/);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// And nothing ran.
|
|
678
|
+
expect(gameSlot.get(ctx).initialized).toBe(false);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test("setup opens play, a roll leaves one standing, and settling closes the window", async () => {
|
|
682
|
+
const ctx = makeCtx();
|
|
683
|
+
// `state`/`instruction`, not `at`/`next`: the ungated tools spread the
|
|
684
|
+
// `DialogPosition` verbatim now, so they report their position under the
|
|
685
|
+
// same keys every gated tool's result carries — which is what the system
|
|
686
|
+
// prompt already claimed.
|
|
687
|
+
const created = await setupCharacter.execute(SETUP_ARGS, ctx);
|
|
688
|
+
expect(created.state).toBe("playing.awaitingRoll");
|
|
689
|
+
expect(created.instruction).toMatch(/action_roll/);
|
|
690
|
+
|
|
691
|
+
ok(
|
|
692
|
+
await actionRoll.execute(
|
|
693
|
+
{ move: "clash", stat: "iron", position: "risky", effect: "standard", purpose: "swing" },
|
|
694
|
+
ctx,
|
|
695
|
+
),
|
|
696
|
+
);
|
|
697
|
+
expect(storyFlow.position(ctx).state).toBe("playing.rollResolved");
|
|
698
|
+
|
|
699
|
+
// Moving the scene on SPENDS the roll: the burn window is closed.
|
|
700
|
+
ok(await updateState.execute({ location: "The Bridge" }, ctx));
|
|
701
|
+
expect(storyFlow.position(ctx).state).toBe("playing.awaitingRoll");
|
|
702
|
+
expect(isToolFailure(await callNoArgs(burnMomentum, ctx))).toBe(true);
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
test("check_state reports the position and is legal before setup", async () => {
|
|
706
|
+
const ctx = makeCtx();
|
|
707
|
+
const before = await callNoArgs(checkState, ctx);
|
|
708
|
+
expect(before.state).toBe("awaitingSetup");
|
|
709
|
+
expect(before.instruction).toMatch(/setup_character/);
|
|
710
|
+
expect(before.done).toBe(false);
|
|
711
|
+
expect(before.initialized).toBe(false);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
test("a game over is terminal: nothing rolls, and setup starts a new story", async () => {
|
|
715
|
+
const ctx = makeCtx();
|
|
716
|
+
const dead = playingState();
|
|
717
|
+
dead.health = 0;
|
|
718
|
+
dead.spirit = 0;
|
|
719
|
+
seedPlaying(ctx, dead);
|
|
720
|
+
|
|
721
|
+
// `isGameOver` is what the result reports and the tool's `sendFrom` is what
|
|
722
|
+
// turns it into a position — the flag used to be read by nobody who could
|
|
723
|
+
// act on it, so a player could keep rolling after both tracks emptied. The
|
|
724
|
+
// WRITE is `gameSlot`'s `after` hook; this tool no longer calls it, which
|
|
725
|
+
// is the point of moving it there.
|
|
726
|
+
ok(await updateState.execute({ health: 0, spirit: 0 }, ctx));
|
|
727
|
+
const at = storyFlow.position(ctx);
|
|
728
|
+
expect(at.state).toBe("gameOver");
|
|
729
|
+
expect(at.done).toBe(true);
|
|
730
|
+
|
|
731
|
+
const refused = await actionRoll.execute(
|
|
732
|
+
{ move: "clash", stat: "iron", position: "risky", effect: "standard", purpose: "swing" },
|
|
733
|
+
ctx,
|
|
734
|
+
);
|
|
735
|
+
expect(isToolFailure(refused)).toBe(true);
|
|
736
|
+
expect(isToolFailure(refused) && refused.error).toMatch(/gameOver/);
|
|
737
|
+
|
|
738
|
+
// Starting over is legal from anywhere, the ending included.
|
|
739
|
+
const restarted = await setupCharacter.execute(SETUP_ARGS, ctx);
|
|
740
|
+
expect(restarted.state).toBe("playing.awaitingRoll");
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
test("a loaded game resumes in play rather than awaiting setup", async () => {
|
|
744
|
+
const { db } = makeDb();
|
|
745
|
+
const sessionA = makeCtx(db);
|
|
746
|
+
seedPlaying(sessionA);
|
|
747
|
+
ok(await saveGame.execute({ slot: "resume" }, sessionA));
|
|
748
|
+
|
|
749
|
+
const sessionB = makeCtx(db);
|
|
750
|
+
expect(storyFlow.position(sessionB).state).toBe("awaitingSetup");
|
|
751
|
+
const loaded = await loadGame.execute({ slot: "resume" }, sessionB);
|
|
752
|
+
if (isToolFailure(loaded)) throw new Error(`load refused: ${loaded.error}`);
|
|
753
|
+
expect(loaded.state).toBe("playing.awaitingRoll");
|
|
754
|
+
|
|
755
|
+
// And the play tools are available in the resumed session.
|
|
756
|
+
ok(await updateState.execute({ location: "Back at the Docks" }, sessionB));
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
test("a save whose game was over resumes as over", async () => {
|
|
760
|
+
const { db } = makeDb();
|
|
761
|
+
const sessionA = makeCtx(db);
|
|
762
|
+
const dead = playingState();
|
|
763
|
+
dead.health = 0;
|
|
764
|
+
dead.spirit = 0;
|
|
765
|
+
dead.gameOver = true;
|
|
766
|
+
dead.crisisMode = true;
|
|
767
|
+
seedPlaying(sessionA, dead);
|
|
768
|
+
ok(await saveGame.execute({ slot: "ended" }, sessionA));
|
|
769
|
+
|
|
770
|
+
const sessionB = makeCtx(db);
|
|
771
|
+
const loaded = await loadGame.execute({ slot: "ended" }, sessionB);
|
|
772
|
+
if (isToolFailure(loaded)) throw new Error(`load refused: ${loaded.error}`);
|
|
773
|
+
expect(loaded.state).toBe("gameOver");
|
|
774
|
+
expect(storyFlow.position(sessionB).done).toBe(true);
|
|
775
|
+
});
|
|
776
|
+
});
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import "@alexkroman1/aai-ui/styles.css";
|
|
2
2
|
import type { DeepReadonly } from "@alexkroman1/aai";
|
|
3
|
-
import {
|
|
3
|
+
import { client, useAgentState } from "@alexkroman1/aai-ui";
|
|
4
4
|
import type { ReactNode } from "react";
|
|
5
5
|
import {
|
|
6
6
|
type Clock,
|
|
7
7
|
type Disposition,
|
|
8
|
-
type GameState,
|
|
9
8
|
GENRES,
|
|
10
9
|
gameProjection,
|
|
11
10
|
MAX_RESOURCE,
|
|
@@ -444,7 +443,22 @@ function StoryArc({ story }: { story: DeepReadonly<StoryBlueprint> }) {
|
|
|
444
443
|
|
|
445
444
|
// ── Sidebar ──────────────────────────────────────────────────────────────────
|
|
446
445
|
|
|
447
|
-
|
|
446
|
+
/**
|
|
447
|
+
* The character sheet.
|
|
448
|
+
*
|
|
449
|
+
* It reads the projection ITSELF rather than taking it as a prop. That prop
|
|
450
|
+
* existed only because a 27-line `component:` wrapper above it was already
|
|
451
|
+
* calling `useAgentState` — and the wrapper existed only to say four things
|
|
452
|
+
* `ClientConfig` could not: `icon`, `subtitle`, `buttonText` and
|
|
453
|
+
* `sidebarPosition`. All four are config fields now, so the wrapper is gone and
|
|
454
|
+
* the subscription lives where the data is used.
|
|
455
|
+
*
|
|
456
|
+
* `gameProjection` carries both halves: the campaign's readonly type — which is
|
|
457
|
+
* what the four components below take — and the frame a session that has run no
|
|
458
|
+
* tool renders.
|
|
459
|
+
*/
|
|
460
|
+
function Sidebar() {
|
|
461
|
+
const game = useAgentState(gameProjection);
|
|
448
462
|
return (
|
|
449
463
|
<div
|
|
450
464
|
style={{
|
|
@@ -768,39 +782,19 @@ function Sidebar({ game }: { game: DeepReadonly<GameState> }) {
|
|
|
768
782
|
|
|
769
783
|
// ── App ──────────────────────────────────────────────────────────────────────
|
|
770
784
|
|
|
771
|
-
function SoloRPGApp() {
|
|
772
|
-
// The agent's own session state, projected by `syncState` and pushed after
|
|
773
|
-
// every tool call — no per-tool `ctx.send`, and nothing to keep in step
|
|
774
|
-
// when a new tool starts mutating the game.
|
|
775
|
-
// `gameProjection` carries both halves: the campaign's readonly type — which
|
|
776
|
-
// is what the four components below take — and the frame a session that has
|
|
777
|
-
// run no tool renders.
|
|
778
|
-
const game = useAgentState(gameProjection);
|
|
779
|
-
|
|
780
|
-
return (
|
|
781
|
-
<StartScreen
|
|
782
|
-
icon={
|
|
783
|
-
<span style={{ fontSize: "28px", color: C.accent }}>
|
|
784
|
-
{/* biome-ignore lint/style/useConsistentCurlyBraces: unicode escape */}
|
|
785
|
-
{"\u2726"}
|
|
786
|
-
</span>
|
|
787
|
-
}
|
|
788
|
-
title="Solo RPG"
|
|
789
|
-
subtitle="A Narrative Solo-RPG Engine"
|
|
790
|
-
buttonText="Begin Your Story"
|
|
791
|
-
>
|
|
792
|
-
<SidebarLayout sidebar={<Sidebar game={game} />} sidebarWidth="260px" sidebarPosition="right">
|
|
793
|
-
<ChatView />
|
|
794
|
-
</SidebarLayout>
|
|
795
|
-
</StartScreen>
|
|
796
|
-
);
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
const SESSION_KEY = "solo-rpg:sessionId";
|
|
800
|
-
const savedSessionId = localStorage.getItem(SESSION_KEY);
|
|
801
|
-
|
|
802
785
|
client({
|
|
803
|
-
|
|
786
|
+
name: "Solo RPG",
|
|
787
|
+
icon: (
|
|
788
|
+
<span style={{ fontSize: "28px", color: C.accent }}>
|
|
789
|
+
{/* biome-ignore lint/style/useConsistentCurlyBraces: unicode escape */}
|
|
790
|
+
{"\u2726"}
|
|
791
|
+
</span>
|
|
792
|
+
),
|
|
793
|
+
subtitle: "A Narrative Solo-RPG Engine",
|
|
794
|
+
buttonText: "Begin Your Story",
|
|
795
|
+
sidebar: Sidebar,
|
|
796
|
+
sidebarWidth: "260px",
|
|
797
|
+
sidebarPosition: "right",
|
|
804
798
|
theme: {
|
|
805
799
|
bg: C.bg,
|
|
806
800
|
primary: C.accent,
|
|
@@ -808,6 +802,13 @@ client({
|
|
|
808
802
|
surface: C.surface,
|
|
809
803
|
border: C.border,
|
|
810
804
|
},
|
|
811
|
-
onSessionId: (id: string) => localStorage.setItem(SESSION_KEY, id),
|
|
812
|
-
...(savedSessionId ? { resumeSessionId: savedSessionId } : {}),
|
|
813
805
|
});
|
|
806
|
+
|
|
807
|
+
// No `onSessionId`/`resumeSessionId` here any more, and their removal is a FIX
|
|
808
|
+
// rather than a simplification. `createSessionCore` remembers the id in
|
|
809
|
+
// `sessionStorage` by default now — see `session-resume-store.ts`, whose doc
|
|
810
|
+
// names this template as the one client of fourteen that had wired it by hand.
|
|
811
|
+
// What it had wired was `localStorage`, which is the wrong store for a pointer
|
|
812
|
+
// into a live call: it survives a new tab and a visit tomorrow, and presenting
|
|
813
|
+
// a day-old id suppresses the greeting and rejoins a conversation whose context
|
|
814
|
+
// is gone. This client was OVERRIDING the correct default with the wrong one.
|