@curless/hotel-storefront 0.55.1 → 0.55.2
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/backend.d.ts.map +1 -1
- package/dist/backend.js +2 -1
- package/dist/backend.js.map +1 -1
- 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/otaless/backend.js +2 -2
- package/dist/otaless/backend.js.map +1 -1
- package/dist/present.d.ts.map +1 -1
- package/dist/present.js +3 -1
- package/dist/present.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -3
- package/dist/server.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +62 -14
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -2312,10 +2312,15 @@ const couponRefusedLine = (q) => q.couponRefused
|
|
|
2312
2312
|
? `The coupon ${q.couponRefused.name ? couponName({ name: q.couponRefused.name, names: q.couponRefused.names }) : q.couponRefused.id} was NOT taken off: ${q.couponRefused.why} The total is without it.`
|
|
2313
2313
|
: '';
|
|
2314
2314
|
const tripText = (quote, noCards) => {
|
|
2315
|
+
// A part not priced because its group did not answer just now: said once, and
|
|
2316
|
+
// left to the guest to ask for again (`askLater`; the trip is drawn as a card).
|
|
2317
|
+
const later = quote.dropped?.some((d) => d.code === 'unreachable') ? askLater(!noCards) : '';
|
|
2315
2318
|
// ⚠️ NOT "EMPTY" WHEN SOMETHING WAS LEFT OUT: a trip whose only group did not
|
|
2316
2319
|
// answer is not an empty trip (2026-11-03 review, C5).
|
|
2317
2320
|
if (quote.lines.length === 0 && quote.dropped?.length)
|
|
2318
|
-
return quote.dropped.map((d) => `Left out: ${d.title} — ${d.why}`)
|
|
2321
|
+
return [...quote.dropped.map((d) => `Left out: ${d.title} — ${d.why}`), later]
|
|
2322
|
+
.filter(Boolean)
|
|
2323
|
+
.join('\n');
|
|
2319
2324
|
if (quote.lines.length === 0)
|
|
2320
2325
|
return 'The trip is empty.';
|
|
2321
2326
|
const lines = quote.lines.map((l) => l.kind === 'room'
|
|
@@ -2327,6 +2332,7 @@ const tripText = (quote, noCards) => {
|
|
|
2327
2332
|
couponRefusedLine(quote),
|
|
2328
2333
|
...memberTripLines(quote),
|
|
2329
2334
|
...(quote.dropped ?? []).map((d) => `Left out: ${d.title} — ${d.why}`),
|
|
2335
|
+
later,
|
|
2330
2336
|
]
|
|
2331
2337
|
.filter(Boolean)
|
|
2332
2338
|
.join('\n');
|
|
@@ -2994,6 +3000,20 @@ const remember = (deps, k, amount) => {
|
|
|
2994
3000
|
};
|
|
2995
3001
|
/** "a, b and c" - "a and b" - "a". */
|
|
2996
3002
|
const listAnd = (xs) => xs.length > 1 ? `${xs.slice(0, -1).join(', ')} and ${xs.at(-1)}` : (xs[0] ?? '');
|
|
3003
|
+
/**
|
|
3004
|
+
* What the model does when a hotel group did not answer: tells the guest, and
|
|
3005
|
+
* leaves asking again to them - said after the sentence that names the groups,
|
|
3006
|
+
* in the text and in `structuredContent` alike.
|
|
3007
|
+
*
|
|
3008
|
+
* ⚠️ NEVER "ASKING AGAIN IN A MOMENT MAY BRING THEM" (robin 11-04). Read as
|
|
3009
|
+
* "ask again now", it had the model call the list a second time in the same
|
|
3010
|
+
* turn: two of the same card under 「打开 Otaless」 on claude.ai, with every
|
|
3011
|
+
* group of the backend silent. The guest asks again: with the card's 「重试」
|
|
3012
|
+
* where a card was drawn, in words where none was.
|
|
3013
|
+
*/
|
|
3014
|
+
const askLater = (card) => card
|
|
3015
|
+
? 'Do not look again in this turn - no second call for it: tell the guest, who can press Try again (重试) on the card when they want to look again.'
|
|
3016
|
+
: 'Do not look again in this turn - no second call for it: tell the guest, and that they can ask you to look again later.';
|
|
2997
3017
|
/**
|
|
2998
3018
|
* The groups of a market that did not answer a search, as the sentence the
|
|
2999
3019
|
* model is told and the fields the card draws its own line from - the same
|
|
@@ -3013,12 +3033,14 @@ noun = deps.vocab.nounPlural,
|
|
|
3013
3033
|
* card called it 锦江之星, another group (11-03, text-only eval). A card host
|
|
3014
3034
|
* is told the English name alone; the card says the group in its own words.
|
|
3015
3035
|
*/
|
|
3016
|
-
noCards = false
|
|
3036
|
+
noCards = false,
|
|
3037
|
+
/** This answer draws a card, with its 「重试」 (`askLater`) - not so for `look_up`, which draws none. */
|
|
3038
|
+
card = !noCards) => {
|
|
3017
3039
|
const down = answer.degraded ?? [];
|
|
3018
3040
|
if (down.length === 0)
|
|
3019
3041
|
return { structured: {}, said: '' };
|
|
3020
3042
|
const one = down.length === 1;
|
|
3021
|
-
const said = `${listAnd(down.map((d) => withZh(d.name, d.names, noCards)))} did not answer in time, so none of ${one ? 'its' : 'their'} ${noun} is in this list - this does not mean ${one ? 'it has' : 'they have'} none here
|
|
3043
|
+
const said = `${listAnd(down.map((d) => withZh(d.name, d.names, noCards)))} did not answer in time, so none of ${one ? 'its' : 'their'} ${noun} is in this list - this does not mean ${one ? 'it has' : 'they have'} none here. ${askLater(card)}`;
|
|
3022
3044
|
return {
|
|
3023
3045
|
structured: {
|
|
3024
3046
|
degraded: down.map((d) => ({ supplier: d.supplier, name: d.name })),
|
|
@@ -3036,13 +3058,15 @@ noCards = false) => {
|
|
|
3036
3058
|
* time"), not the name of whichever was asked first; and in every case it
|
|
3037
3059
|
* denies the reading a guest would make of silence, that there is nothing.
|
|
3038
3060
|
*/
|
|
3039
|
-
const notAnswered = (deps, key, down
|
|
3061
|
+
const notAnswered = (deps, key, down,
|
|
3062
|
+
/** The answer draws a card, with its 「重试」 - see `askLater`. */
|
|
3063
|
+
card) => {
|
|
3040
3064
|
const groups = deps.backend.suppliers?.().length ?? 0;
|
|
3041
3065
|
const names = listAnd(down.map((d) => d.name));
|
|
3042
3066
|
const one = down.length === 1;
|
|
3043
3067
|
const said = groups > 1 && down.length >= groups
|
|
3044
|
-
? `No hotel group answered this time: ${names} did not answer in time - this does not mean there is nothing here
|
|
3045
|
-
: `${names} did not answer in time, so nothing from ${one ? 'it' : 'them'} could be read just now - this does not mean ${one ? 'it has' : 'they have'} none here
|
|
3068
|
+
? `No hotel group answered this time: ${names} did not answer in time - this does not mean there is nothing here. ${askLater(card)}`
|
|
3069
|
+
: `${names} did not answer in time, so nothing from ${one ? 'it' : 'them'} could be read just now - this does not mean ${one ? 'it has' : 'they have'} none here. ${askLater(card)}`;
|
|
3046
3070
|
return {
|
|
3047
3071
|
structured: {
|
|
3048
3072
|
view: 'error',
|
|
@@ -3107,7 +3131,9 @@ const countProblem = (t, name, args) => {
|
|
|
3107
3131
|
* properties of null" - were read back too (2026-10-05 audit); they go to the
|
|
3108
3132
|
* log now.
|
|
3109
3133
|
*/
|
|
3110
|
-
const guestWords = (err,
|
|
3134
|
+
const guestWords = (err,
|
|
3135
|
+
// Not "try again in a moment" (robin 11-04): the guest asks for it again.
|
|
3136
|
+
fault = 'That could not be done just now - nothing was changed. Tell the guest; they can ask for it again later.') => {
|
|
3111
3137
|
const e = err;
|
|
3112
3138
|
// ⚠️ A READ THAT TIMED OUT IS NOT A REFUSAL (2026-11-03 review, C5). After a
|
|
3113
3139
|
// booking was made, "Atour did not answer within 8000 ms" would be read
|
|
@@ -3666,7 +3692,9 @@ export const handleTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3666
3692
|
const down = unansweredBy(err);
|
|
3667
3693
|
if (!down)
|
|
3668
3694
|
throw err;
|
|
3669
|
-
|
|
3695
|
+
// `look_up` and the policies draw no card, so there is no 「重试」 to press.
|
|
3696
|
+
const card = !ctx.noCards && name !== deps.tools.lookUp && name !== deps.tools.policies;
|
|
3697
|
+
return notAnswered(deps, ctx.tripKey ?? tripKey(ctx.session, deps.vocab.brand), down, card);
|
|
3670
3698
|
});
|
|
3671
3699
|
const suggestions = await suggestionsFor(deps, answer.structured).catch(() => []);
|
|
3672
3700
|
return { answer, suggestions };
|
|
@@ -5386,7 +5414,13 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5386
5414
|
const description = hotelText('description', h.name,
|
|
5387
5415
|
// Its 【…】 parts a line each, the words as they came (`aboutLines`).
|
|
5388
5416
|
aboutLines(h.summary), DESCRIPTION_MAX, deps.vocab.demo);
|
|
5389
|
-
|
|
5417
|
+
// ⚠️ NO POLICIES WHERE THE POLICIES TOOL IS NOT OFFERED (总线程 11-04): Atour's
|
|
5418
|
+
// single store (`policies: false`) still listed its topics on every hotel, and
|
|
5419
|
+
// the card drew six rows of 「入住须知」 that each opened on "could not load
|
|
5420
|
+
// this policy". Not read, not in the answer - the model is not told of a
|
|
5421
|
+
// policy nobody can open either.
|
|
5422
|
+
const offersPolicies = (deps.backend.capabilities ?? FULL_CAPABILITIES).policies !== false;
|
|
5423
|
+
const policies = offersPolicies ? await deps.backend.policies(h.id) : [];
|
|
5390
5424
|
const extras = await deps.backend.extrasFor(h.id, stay);
|
|
5391
5425
|
const base = {
|
|
5392
5426
|
...common(deps, key),
|
|
@@ -5396,7 +5430,9 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5396
5430
|
fromNightly: h.fromNightly ? formatMoney(h.fromNightly) : undefined,
|
|
5397
5431
|
},
|
|
5398
5432
|
extras: extras.map((e) => ({ ...e, price: formatMoney(e.price) })),
|
|
5399
|
-
|
|
5433
|
+
...(offersPolicies
|
|
5434
|
+
? { policies: policies.map((p) => ({ topic: p.topic, title: p.title })) }
|
|
5435
|
+
: {}),
|
|
5400
5436
|
};
|
|
5401
5437
|
if (!stay) {
|
|
5402
5438
|
return {
|
|
@@ -5437,7 +5473,10 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5437
5473
|
// (it says it in the guest's language from `noRoomsCode`).
|
|
5438
5474
|
const said = detail.noRooms && detail.noRoomsCode === 'none-on-sale'
|
|
5439
5475
|
? notOnSaleWords(groupNameOf(deps, h.id), detail.noRooms)
|
|
5440
|
-
:
|
|
5476
|
+
: // Rooms that could not be read just now: asked for again by the guest.
|
|
5477
|
+
detail.noRooms && detail.noRoomsCode === 'unreachable'
|
|
5478
|
+
? `${detail.noRooms} ${askLater(!ctx.noCards)}`
|
|
5479
|
+
: detail.noRooms;
|
|
5441
5480
|
return {
|
|
5442
5481
|
structured: {
|
|
5443
5482
|
view: 'no-rooms',
|
|
@@ -5588,7 +5627,8 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
5588
5627
|
total = found.total;
|
|
5589
5628
|
degradedFound = found;
|
|
5590
5629
|
}
|
|
5591
|
-
|
|
5630
|
+
// `look_up` draws no card: the guest asks again in words (`askLater`).
|
|
5631
|
+
const degraded = degradedOf(deps, degradedFound, undefined, ctx?.noCards, false);
|
|
5592
5632
|
const unique = [...new Set(ids)];
|
|
5593
5633
|
// A name that fits more than one is said with the ones it fits; the rest are not found.
|
|
5594
5634
|
const notFound = dropped.filter((d) => !candidates.some((c) => c.for === d));
|
|
@@ -6398,7 +6438,10 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
6398
6438
|
: []),
|
|
6399
6439
|
...fitsMany,
|
|
6400
6440
|
...silent.map((d) => `Left out: ${d.name} — ${d.why}`),
|
|
6401
|
-
|
|
6441
|
+
silent.length ? askLater(!ctx.noCards) : '',
|
|
6442
|
+
]
|
|
6443
|
+
.filter(Boolean)
|
|
6444
|
+
.join('\n'),
|
|
6402
6445
|
};
|
|
6403
6446
|
}
|
|
6404
6447
|
const stayWords = comparison.stay
|
|
@@ -6440,6 +6483,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
6440
6483
|
: '',
|
|
6441
6484
|
...comparison.dropped.map((d) => `Left out: ${d.name} — ${d.why}`),
|
|
6442
6485
|
...fitsManyOf(deps, comparison.dropped),
|
|
6486
|
+
comparison.dropped.some((d) => d.code === 'supplier-timeout') ? askLater(!ctx.noCards) : '',
|
|
6443
6487
|
]
|
|
6444
6488
|
.filter(Boolean)
|
|
6445
6489
|
.join('\n'),
|
|
@@ -6464,7 +6508,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
6464
6508
|
unchecked = down;
|
|
6465
6509
|
}
|
|
6466
6510
|
const uncheckedSaid = unchecked.length
|
|
6467
|
-
? `${listAnd(unchecked.map((d) => d.name))} did not answer in time, so ${unchecked.length === 1 ? 'its' : 'their'} bookings were not checked against this plan - a stay there may already be booked without the plan saying so
|
|
6511
|
+
? `${listAnd(unchecked.map((d) => d.name))} did not answer in time, so ${unchecked.length === 1 ? 'its' : 'their'} bookings were not checked against this plan - a stay there may already be booked without the plan saying so. ${askLater(!ctx.noCards)}`
|
|
6468
6512
|
: '';
|
|
6469
6513
|
const plan = await buildItinerary(deps.backend, days.map((d) => ({
|
|
6470
6514
|
label: str(d.label),
|
|
@@ -6565,6 +6609,10 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
6565
6609
|
: []),
|
|
6566
6610
|
...plan.dropped.map((d) => `Left out: ${d.name} — ${d.why}`),
|
|
6567
6611
|
...fitsManyOf(deps, plan.dropped),
|
|
6612
|
+
// Once, for every group left silent - the bookings' sentence has its own.
|
|
6613
|
+
!uncheckedSaid && plan.dropped.some((d) => d.code === 'supplier-timeout')
|
|
6614
|
+
? askLater(!ctx.noCards)
|
|
6615
|
+
: '',
|
|
6568
6616
|
uncheckedSaid,
|
|
6569
6617
|
ctx.noCards ? PLAN_NOT_DRAWN : PLAN_DRAWN_ONCE,
|
|
6570
6618
|
]
|