@curless/hotel-storefront 0.47.8 → 0.48.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/card.generated.d.ts +1 -1
- package/dist/card.generated.js +2 -2
- package/dist/card.generated.js.map +1 -1
- package/dist/guests.d.ts +27 -0
- package/dist/guests.d.ts.map +1 -0
- package/dist/guests.js +51 -0
- package/dist/guests.js.map +1 -0
- package/dist/huazhu/backend.d.ts.map +1 -1
- package/dist/huazhu/backend.js +24 -6
- package/dist/huazhu/backend.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +7 -3
- package/dist/server.js.map +1 -1
- package/dist/surface.js +1 -1
- package/dist/surface.js.map +1 -1
- package/dist/tools.d.ts +50 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +162 -11
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_PAGE, FULL_CAPABILITIES, LASTING_REQUESTS, MAX_PAGE, REQUEST_KINDS, REQUEST_NOTE_MAX, guestTooLong, unitWords, } from './backend.js';
|
|
2
2
|
import { MAX_GUESTS, isDay, nights as nightsBetween } from './dates.js';
|
|
3
3
|
import { FACILITIES } from './facilities.js';
|
|
4
|
-
import { DESCRIPTION_MAX, POLICY_MAX,
|
|
4
|
+
import { DESCRIPTION_MAX, POLICY_MAX, hotelText } from './fence.js';
|
|
5
5
|
import { clockOf, minutesOf, startProblem, stayWords, windowWords } from './hourly.js';
|
|
6
6
|
import { amountsIn, formatMoney, keepingAmounts, money, multiplyMoney, toMinor } from './money.js';
|
|
7
7
|
import { namedExactly, roomNamesOf, roomTypeNamed, withTraditional } from './names.js';
|
|
@@ -431,7 +431,7 @@ const allTools = (n, v) => {
|
|
|
431
431
|
// the guest for one question (robin, 2026-09-29, Opus in Claude.ai). The
|
|
432
432
|
// reference apps do the same split: search without a card, present with
|
|
433
433
|
// one.
|
|
434
|
-
description: `Reads up to ${LOOK_UP_MAX} ${v.nounPlural} - a \`city\`'s (or a country's), or the ones named in \`${v.nounPlural}\` - and returns them as text, with nothing drawn and nothing shown to the guest: each one's id, area, rating, description and amenities, its add-ons with their ids and prices, and, given \`checkIn\` and \`checkOut\`, its rooms priced for those nights with taxes in. It is how the ${v.nounPlural} of a day-by-day plan are chosen - for each part of the trip, those near where its days are spent; a city with a span of dates and 行程 is such a plan, also right after a list of ${v.nounPlural} - and the ids are the ones a comparison and a plan take.`,
|
|
434
|
+
description: `Reads up to ${LOOK_UP_MAX} ${v.nounPlural} - a \`city\`'s (or a country's), or the ones named in \`${v.nounPlural}\` - and returns them as text, with nothing drawn and nothing shown to the guest: each one's id, area, rating, description and amenities, its add-ons with their ids and prices, and, given \`checkIn\` and \`checkOut\`, its rooms priced for those nights with taxes in. It is how the ${v.nounPlural} of a day-by-day plan are chosen - for each part of the trip, those near where its days are spent (a week in a big city has two or three parts, its far-out days side by side with a ${v.noun} near them); a city with a span of dates and 行程 is such a plan, also right after a list of ${v.nounPlural} - and the ids are the ones a comparison and a plan take.`,
|
|
435
435
|
annotations: reads(`Look up ${catalogue(v)}`, open),
|
|
436
436
|
inputSchema: {
|
|
437
437
|
type: 'object',
|
|
@@ -740,7 +740,7 @@ const allTools = (n, v) => {
|
|
|
740
740
|
},
|
|
741
741
|
{
|
|
742
742
|
name: n.itinerary,
|
|
743
|
-
description: `A day-by-day plan (行程) as one card - the card for a trip of several days, or for a guest who asks to have their days planned (a city with a span of dates and 行程 is one, also right after a list of ${v.nounPlural}): the days are laid out by where they are spent, and each part of the trip has a ${v.noun} near those days, chosen from the ${v.nounPlural} read up as text. A week in a big city is usually two or three stays:
|
|
743
|
+
description: `A day-by-day plan (行程) as one card - the card for a trip of several days, or for a guest who asks to have their days planned (a city with a span of dates and 行程 is one, also right after a list of ${v.nounPlural}): the days are laid out by where they are spent, and each part of the trip has a ${v.noun} near those days, chosen from the ${v.nounPlural} read up as text. A week in a big city is usually two or three stays: its far-out sights fall on neighbouring days, and those nights have a ${v.noun} near them, with the reason in that day's \`why\` (Beijing's Summer Palace, Yuanmingyuan, Fragrant Hills and the Peking and Tsinghua campuses - 颐和园, 圆明园, 香山, 北大清华 - are days side by side in the north-west); a day out of town, such as the Great Wall, is a day of its own, there and back. One ${v.noun} for the whole week is the plan when every day is within about 45 minutes of it, with the stay in two parts as the other way the reply names. Nothing here knows a sight's opening days: each day is drawn as it is given, with its date (many museums close on Mondays, the Palace Museum - 故宫 - among them). Each day carries its own label, an optional note, the moments of the day in order, and the ${v.nounPlural} and add-ons attached to it, with their details filled in from their own records - a ${v.noun} with its photograph and, given dates, the price of its nights. Each stay is marked with whether it is booked, in the trip or only in the plan, and the card says how many of each. Nothing is added to the trip.`,
|
|
744
744
|
annotations: reads(v.market ? `Lay out an itinerary on ${v.brand}` : `Lay out a ${v.brand} itinerary`, open),
|
|
745
745
|
inputSchema: {
|
|
746
746
|
type: 'object',
|
|
@@ -910,6 +910,20 @@ const allTools = (n, v) => {
|
|
|
910
910
|
type: 'object',
|
|
911
911
|
properties: {
|
|
912
912
|
...guestProps,
|
|
913
|
+
// The room the form shows, as pick_room returned it - see `pickChanged`.
|
|
914
|
+
hotel: { type: 'string', description: `The ${v.noun} id on the form` },
|
|
915
|
+
room: { type: 'string', description: 'The room type id on the form' },
|
|
916
|
+
rooms: {
|
|
917
|
+
type: 'integer',
|
|
918
|
+
minimum: 1,
|
|
919
|
+
maximum: MAX_ROOMS_PER_LINE,
|
|
920
|
+
description: 'How many of that room type the form shows',
|
|
921
|
+
},
|
|
922
|
+
...stayProps,
|
|
923
|
+
startTime: {
|
|
924
|
+
type: 'string',
|
|
925
|
+
description: 'For a room by the hour: the start the form shows, HH:MM',
|
|
926
|
+
},
|
|
913
927
|
},
|
|
914
928
|
required: ['name', 'email'],
|
|
915
929
|
},
|
|
@@ -1177,6 +1191,13 @@ const BOOKING_SCHEMA = {
|
|
|
1177
1191
|
* 2026-10-14).
|
|
1178
1192
|
*/
|
|
1179
1193
|
failed: ARR,
|
|
1194
|
+
/**
|
|
1195
|
+
* The same stays for the same guest asked for again within minutes: the
|
|
1196
|
+
* booking made then is the answer, and nothing new was booked.
|
|
1197
|
+
*/
|
|
1198
|
+
repeat: BOOL,
|
|
1199
|
+
/** A form whose room is no longer the one picked: the pick that would put it back (`pickChanged`). */
|
|
1200
|
+
again: OBJ,
|
|
1180
1201
|
},
|
|
1181
1202
|
required: ['view'],
|
|
1182
1203
|
};
|
|
@@ -1729,7 +1750,7 @@ const bookedLine = (deps, b) => {
|
|
|
1729
1750
|
return `${total} in ${h.name}'s test system. **This is a test booking** - it holds no room for anyone, nothing is paid, now or at any hotel, and nobody needs to go there.`;
|
|
1730
1751
|
return b.status === 'requested'
|
|
1731
1752
|
? `${total} all in, to be paid at the hotel. ${h.name} has not confirmed this booking yet - that takes up to eight minutes, and ${deps.tools.listBookings} shows when it has. Nothing has been charged.`
|
|
1732
|
-
: `${total} all in, paid at the hotel. **Nothing has been charged here** - this is ${h.name}'s own reservation, kept until it is cancelled, and
|
|
1753
|
+
: `${total} all in, paid at the hotel. **Nothing has been charged here** - this is ${h.name}'s own reservation, kept until it is cancelled, and the guest pays at check-in.`;
|
|
1733
1754
|
}
|
|
1734
1755
|
const lapses = b.holdUntilLocal
|
|
1735
1756
|
? `${b.holdUntilLocal} hotel time`
|
|
@@ -1738,7 +1759,7 @@ const bookedLine = (deps, b) => {
|
|
|
1738
1759
|
// booking has no check-in, and saying so sent a guest towards a real desk.
|
|
1739
1760
|
if (h.demo)
|
|
1740
1761
|
return `${total} all in. **${demoBookingWords(h)}** Nothing has been charged and no card is needed. The demonstration hold lapses on its own at ${lapses}.`;
|
|
1741
|
-
return `${total} all in. **Nothing has been charged** - the rooms are held until ${lapses}, and
|
|
1762
|
+
return `${total} all in. **Nothing has been charged** - the rooms are held until ${lapses}, and the guest presents a card at check-in.`;
|
|
1742
1763
|
};
|
|
1743
1764
|
/** What a demonstration booking is, in one sentence - see `demoBookingNote`. */
|
|
1744
1765
|
const demoBookingWords = (h) => `This is a demonstration booking: ${h.name} has not received it and is not holding any room.`;
|
|
@@ -1941,6 +1962,8 @@ const readRequests = (args) => {
|
|
|
1941
1962
|
dropped,
|
|
1942
1963
|
};
|
|
1943
1964
|
};
|
|
1965
|
+
/** Said of a booking that carries the guest's note - see `requestLines`. */
|
|
1966
|
+
export const NOTE_PASSED = 'A note from the guest was passed to the hotel.';
|
|
1944
1967
|
/**
|
|
1945
1968
|
* The sentences a booking's requests earn, in the order a guest reads them.
|
|
1946
1969
|
*
|
|
@@ -1962,8 +1985,12 @@ const requestLines = (b, dropped, checkInFrom) => {
|
|
|
1962
1985
|
: REQUEST_WORDS[k]);
|
|
1963
1986
|
out.push(`Asked for on the booking: ${words.join(', ')}. The desk meets what it can on the day; none of it is held.`);
|
|
1964
1987
|
}
|
|
1988
|
+
// ⚠️ THAT THERE IS A NOTE, NOT WHAT IT SAYS (2026-10-27 audit, F3): the
|
|
1989
|
+
// guest's own words are theirs, typed on the card's form - the card shows
|
|
1990
|
+
// them from `_meta` (server.ts, `guestsApart`), and the model is told only
|
|
1991
|
+
// that one went to the hotel.
|
|
1965
1992
|
if (r.note)
|
|
1966
|
-
out.push(
|
|
1993
|
+
out.push(NOTE_PASSED);
|
|
1967
1994
|
if (r.kinds.includes('accessible-room')) {
|
|
1968
1995
|
out.push(`An accessible room is a room type, and this booking does not hold one - ${b.hotelName} confirms accessible rooms directly, quoting ${b.confirmationCode}.`);
|
|
1969
1996
|
}
|
|
@@ -2955,6 +2982,41 @@ const fitsManyOf = (deps, dropped) => dropped.flatMap((d) => d.candidates?.lengt
|
|
|
2955
2982
|
const LONG_STAY_NIGHTS = 4;
|
|
2956
2983
|
/** The other areas a long stay's answer names - one hotel each. */
|
|
2957
2984
|
const OTHER_AREAS_MAX = 4;
|
|
2985
|
+
/**
|
|
2986
|
+
* Every plan's answer ends with this: the card is the plan, and a correction
|
|
2987
|
+
* found after drawing it is said, not drawn.
|
|
2988
|
+
*
|
|
2989
|
+
* ⚠️ ONE PLAN, ONE CARD - SAID WHERE THE MODEL READS IT LAST (the owner,
|
|
2990
|
+
* 2026-10-26, live 0.47.8): the plan put the Palace Museum on a Monday, the
|
|
2991
|
+
* model noticed after drawing it and drew the whole plan again - two plan
|
|
2992
|
+
* cards in one answer, "以第二张卡片为准". The instructions and the description
|
|
2993
|
+
* say to check closing days before drawing; this was said only in the answer
|
|
2994
|
+
* of a long stay at one hotel. It is in every plan's answer now.
|
|
2995
|
+
*/
|
|
2996
|
+
export const PLAN_DRAWN_ONCE = 'The plan on the card is this one. Something to change that you notice now - a sight closed on its day, a day too far from its hotel - goes in your reply, not in another plan: the plan is drawn again only when the guest asks for the change.';
|
|
2997
|
+
/** An area's names as a day might say them: "Haidian", "海淀区" and "海淀". */
|
|
2998
|
+
const areaWords = (h) => {
|
|
2999
|
+
const out = new Set();
|
|
3000
|
+
for (const name of [h.area, ...Object.values(h.names?.area ?? {})]) {
|
|
3001
|
+
const n = name?.trim();
|
|
3002
|
+
if (!n)
|
|
3003
|
+
continue;
|
|
3004
|
+
out.add(n);
|
|
3005
|
+
const short = n.replace(/\s+district$/i, '').replace(/区$/, '');
|
|
3006
|
+
if (short.length >= 2)
|
|
3007
|
+
out.add(short);
|
|
3008
|
+
}
|
|
3009
|
+
return [...out];
|
|
3010
|
+
};
|
|
3011
|
+
/** Whether a day's own words - label, note, moments - name one of these. */
|
|
3012
|
+
const dayNames = (day, words) => {
|
|
3013
|
+
const said = [day.label, day.note, ...(day.plan ?? []).flatMap((m) => [m.what, m.note])]
|
|
3014
|
+
.filter(Boolean)
|
|
3015
|
+
.join(' ');
|
|
3016
|
+
return words.some((w) => /^[\x20-\x7e]+$/.test(w)
|
|
3017
|
+
? new RegExp(`\\b${w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i').test(said)
|
|
3018
|
+
: said.includes(w));
|
|
3019
|
+
};
|
|
2958
3020
|
/**
|
|
2959
3021
|
* For each priced stay of four nights or more, the other areas of its city that
|
|
2960
3022
|
* have a hotel - for the model, in the tool's text, never on the card or in the
|
|
@@ -2968,13 +3030,21 @@ const OTHER_AREAS_MAX = 4;
|
|
|
2968
3030
|
* model can plan the stay in parts - or offer the move - without a second
|
|
2969
3031
|
* look-up. Nothing where the city has no other area, or the backend cannot say:
|
|
2970
3032
|
* a nudge that guesses is worse than none.
|
|
3033
|
+
*
|
|
3034
|
+
* ⚠️ AND A WHOLE TRIP AT ONE HOTEL SAYS WHEN THAT FITS (0.47.9, after live
|
|
3035
|
+
* 0.47.8: six nights in Dongcheng, "only one day is far, not worth a move").
|
|
3036
|
+
* One hotel throughout is the plan only when every day is within about 45
|
|
3037
|
+
* minutes of it; otherwise the reply names the split as the other way. A day
|
|
3038
|
+
* whose own words name another area's hotel is named here - also for a trip
|
|
3039
|
+
* shorter than four nights - so the model does not have to find it again.
|
|
2971
3040
|
*/
|
|
2972
|
-
const longStayNudges = async (deps, stays) => {
|
|
3041
|
+
const longStayNudges = async (deps, stays, days) => {
|
|
2973
3042
|
// The areas the plan already uses are not "other" for any of its stays.
|
|
2974
3043
|
const used = new Set(stays.map((x) => x.area?.toLowerCase()).filter(Boolean));
|
|
2975
3044
|
const out = [];
|
|
3045
|
+
const whole = stays.length === 1;
|
|
2976
3046
|
for (const x of stays) {
|
|
2977
|
-
if ((x.nights ?? 0) <
|
|
3047
|
+
if (!x.area || !x.city || (x.nights ?? 0) < (whole ? 2 : LONG_STAY_NIGHTS))
|
|
2978
3048
|
continue;
|
|
2979
3049
|
let hotels;
|
|
2980
3050
|
try {
|
|
@@ -2996,10 +3066,29 @@ const longStayNudges = async (deps, stays) => {
|
|
|
2996
3066
|
}
|
|
2997
3067
|
if (byArea.size === 0)
|
|
2998
3068
|
continue;
|
|
3069
|
+
// Only for a whole trip at one hotel: which of its days name another area.
|
|
3070
|
+
const elsewhere = whole
|
|
3071
|
+
? days.flatMap((day, i) => {
|
|
3072
|
+
const h = [...byArea.values()].find((o) => dayNames(day, areaWords(o)));
|
|
3073
|
+
return h
|
|
3074
|
+
? [
|
|
3075
|
+
`Day ${i + 1}${day.date ? ` (${day.date})` : ''} names ${h.area}, where ${h.name} (\`${h.id}\`) is.`,
|
|
3076
|
+
]
|
|
3077
|
+
: [];
|
|
3078
|
+
})
|
|
3079
|
+
: [];
|
|
3080
|
+
if ((x.nights ?? 0) < LONG_STAY_NIGHTS && elsewhere.length === 0)
|
|
3081
|
+
continue;
|
|
2999
3082
|
const others = [...byArea.values()]
|
|
3000
3083
|
.slice(0, OTHER_AREAS_MAX)
|
|
3001
3084
|
.map((h) => `${h.area} - ${h.name} (\`${h.id}\`)`);
|
|
3002
|
-
out.push(
|
|
3085
|
+
out.push(whole
|
|
3086
|
+
? [
|
|
3087
|
+
`All ${x.nights} nights are at ${x.name} (${x.area}). ${x.city} also has: ${others.join('; ')}.`,
|
|
3088
|
+
...elsewhere,
|
|
3089
|
+
`One hotel for the whole trip fits only when every day is within about 45 minutes of it. In your reply, name the days spent farther out than that and add one sentence on the other way - "it could also be two stays: ..." - those nights at the hotel here nearest them, and why; or say in one sentence why every day is within reach of ${x.area}.`,
|
|
3090
|
+
].join(' ')
|
|
3091
|
+
: `${x.nights} nights are at ${x.name} (${x.area}). ${x.city} also has: ${others.join('; ')}. In your reply, name the planned days that are far from ${x.area} and offer moving those nights to the hotel here nearest them, with the reason in one sentence - or say why every day suits ${x.area}.`);
|
|
3003
3092
|
}
|
|
3004
3093
|
return out;
|
|
3005
3094
|
};
|
|
@@ -3029,6 +3118,58 @@ const direct = (a) => ({
|
|
|
3029
3118
|
...a,
|
|
3030
3119
|
structured: { ...a.structured, direct: true },
|
|
3031
3120
|
});
|
|
3121
|
+
/**
|
|
3122
|
+
* The booking form's room, when it is NOT the room picked now: the refusal to
|
|
3123
|
+
* book it. Undefined when the form books what it shows.
|
|
3124
|
+
*
|
|
3125
|
+
* ⚠️ THE PICK IS THE CONVERSATION'S, THE FORM IS ONE CARD'S (2026-10-27 audit,
|
|
3126
|
+
* F1). Two cards of one conversation share the slot (`pickedKey`): Reserve on
|
|
3127
|
+
* the second replaced the first's room, and the first card's form then booked
|
|
3128
|
+
* the second's room under its own picture of another. The form says which room
|
|
3129
|
+
* it shows; one that is not the slot's - or a slot emptied since, by a booking
|
|
3130
|
+
* or "Don't book" on the other card - books nothing, and `again` is the pick
|
|
3131
|
+
* that would put the form's room back.
|
|
3132
|
+
*
|
|
3133
|
+
* A form that names no room is a card from before 0.48.0, booked as before.
|
|
3134
|
+
*/
|
|
3135
|
+
const pickChanged = (deps, key, args) => {
|
|
3136
|
+
const room = str(args.room);
|
|
3137
|
+
if (!room)
|
|
3138
|
+
return undefined;
|
|
3139
|
+
const l = deps.trips.read(pickedKey(key)).find((x) => x.kind === 'room');
|
|
3140
|
+
const said = (v) => v !== undefined && v !== null && v !== '';
|
|
3141
|
+
const same = (asked, held) => !said(asked) || String(asked) === String(held);
|
|
3142
|
+
if (l?.kind === 'room' &&
|
|
3143
|
+
str(args.hotel) === l.hotelId &&
|
|
3144
|
+
room === l.roomId &&
|
|
3145
|
+
same(args.rooms, l.rooms ?? 1) &&
|
|
3146
|
+
same(args.guests, l.stay.guests) &&
|
|
3147
|
+
(l.hourly
|
|
3148
|
+
? same(args.startTime, l.hourly.start)
|
|
3149
|
+
: same(args.checkIn, l.stay.checkIn) && same(args.checkOut, l.stay.checkOut)))
|
|
3150
|
+
return undefined;
|
|
3151
|
+
const words = l
|
|
3152
|
+
? 'Nothing was booked: the booking form was for another room than the one picked now - Reserve was pressed on another card of this conversation since. The guest presses Reserve on the room they want again.'
|
|
3153
|
+
: 'Nothing was booked: the room on the booking form is no longer picked - another card of this conversation booked a room or let it go since. The guest presses Reserve on the room they want again.';
|
|
3154
|
+
return {
|
|
3155
|
+
structured: {
|
|
3156
|
+
view: 'error',
|
|
3157
|
+
...common(deps, key),
|
|
3158
|
+
error: 'pick-changed',
|
|
3159
|
+
detail: words,
|
|
3160
|
+
again: {
|
|
3161
|
+
hotel: str(args.hotel),
|
|
3162
|
+
room,
|
|
3163
|
+
rooms: said(args.rooms) ? Number(args.rooms) : 1,
|
|
3164
|
+
...(said(args.guests) ? { guests: Number(args.guests) } : {}),
|
|
3165
|
+
...(said(args.startTime)
|
|
3166
|
+
? { startTime: str(args.startTime) }
|
|
3167
|
+
: { checkIn: str(args.checkIn), checkOut: str(args.checkOut) }),
|
|
3168
|
+
},
|
|
3169
|
+
},
|
|
3170
|
+
text: words,
|
|
3171
|
+
};
|
|
3172
|
+
};
|
|
3032
3173
|
/**
|
|
3033
3174
|
* A room line, validated and priced against the hotel's calendar and put into
|
|
3034
3175
|
* one slot of the trip store - THE ONE ROOM-LINE CODE, for `add_to_trip` (`store`
|
|
@@ -3297,7 +3438,7 @@ const bookTripLines = async (deps, t, ctx, key, store, args, nothingThere) => {
|
|
|
3297
3438
|
const { requests, dropped } = readRequests(args);
|
|
3298
3439
|
try {
|
|
3299
3440
|
const shown = deps.trips.shown(store);
|
|
3300
|
-
const { booked: made, failed } = await deps.backend.requestBooking({ lines, ...(shown ? { shown } : {}) }, {
|
|
3441
|
+
const { booked: made, failed, repeat, } = await deps.backend.requestBooking({ lines, ...(shown ? { shown } : {}) }, {
|
|
3301
3442
|
...guest,
|
|
3302
3443
|
...(str(args.phone) ? { phone: str(args.phone) } : {}),
|
|
3303
3444
|
...(requests?.note ? { notes: requests.note } : {}),
|
|
@@ -3473,8 +3614,14 @@ const bookTripLines = async (deps, t, ctx, key, store, args, nothingThere) => {
|
|
|
3473
3614
|
? { accessibility: true }
|
|
3474
3615
|
: {}),
|
|
3475
3616
|
...(notBooked.length ? { failed: notBooked } : {}),
|
|
3617
|
+
...(repeat ? { repeat: true } : {}),
|
|
3476
3618
|
},
|
|
3477
3619
|
text: [
|
|
3620
|
+
// ⚠️ THE SAME BOOKING ASKED FOR AGAIN IS SAID TO BE THAT, never a new
|
|
3621
|
+
// one (2026-10-27 audit, F2): it was answered as if just booked.
|
|
3622
|
+
repeat
|
|
3623
|
+
? `NOTHING NEW WAS BOOKED: the same ${booked.length > 1 ? 'stays' : 'stay'} for the same guest ${booked.length > 1 ? 'were' : 'was'} asked for a few minutes ago, and ${booked.length > 1 ? 'these are the bookings' : 'this is the booking'} made then. A booking for someone else is made under their name.`
|
|
3624
|
+
: '',
|
|
3478
3625
|
// ⚠️ BOOKED IS CONFIRMED: a stay still being confirmed is said as
|
|
3479
3626
|
// that, and not counted (总线程, 2026-10-14).
|
|
3480
3627
|
notBooked.length || (booked.length > 1 && waiting)
|
|
@@ -4432,6 +4579,9 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
4432
4579
|
// The card's own: the room the guest picked with Reserve, booked exactly as
|
|
4433
4580
|
// a trip is - and the trip left as it is.
|
|
4434
4581
|
if (name === t.bookPicked) {
|
|
4582
|
+
const changed = pickChanged(deps, key, args);
|
|
4583
|
+
if (changed)
|
|
4584
|
+
return direct(changed);
|
|
4435
4585
|
return direct(await bookTripLines(deps, t, ctx, key, pickedKey(key), args, 'Nothing is picked: the guest presses Reserve on a room first.'));
|
|
4436
4586
|
}
|
|
4437
4587
|
if (name === t.listBookings) {
|
|
@@ -5006,11 +5156,12 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5006
5156
|
`Priced stays (${plan.stay?.guests ?? 2} guests; ${t.addToTrip} takes each one's hotel id, room id and dates):`,
|
|
5007
5157
|
...stays,
|
|
5008
5158
|
// ⚠️ IN THE TEXT ONLY - not in `structured`, not in the schema.
|
|
5009
|
-
...(await longStayNudges(deps, stayItems)),
|
|
5159
|
+
...(await longStayNudges(deps, stayItems, plan.days)),
|
|
5010
5160
|
]
|
|
5011
5161
|
: []),
|
|
5012
5162
|
...plan.dropped.map((d) => `Left out: ${d.name} — ${d.why}`),
|
|
5013
5163
|
...fitsManyOf(deps, plan.dropped),
|
|
5164
|
+
PLAN_DRAWN_ONCE,
|
|
5014
5165
|
deps.vocab.demo ? noteFor(deps.vocab) : '',
|
|
5015
5166
|
]
|
|
5016
5167
|
.filter(Boolean)
|