@itpay/cli 2.1.40 → 2.1.41

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.
Files changed (148) hide show
  1. package/README.md +5 -12
  2. package/dist/src/client/backend.js +2 -38
  3. package/dist/src/client/http.js +0 -2
  4. package/dist/src/commands/buy.js +58 -17
  5. package/dist/src/commands/cart.js +15 -17
  6. package/dist/src/commands/catalog.js +14 -39
  7. package/dist/src/commands/checkout.js +5 -146
  8. package/dist/src/commands/checkout_handoff.js +61 -11
  9. package/dist/src/commands/docs.js +18 -7
  10. package/dist/src/commands/guidance.js +134 -1
  11. package/dist/src/commands/install.js +3 -6
  12. package/dist/src/commands/order.js +3 -34
  13. package/dist/src/commands/orders.js +9 -4
  14. package/dist/src/commands/pay.js +1 -1
  15. package/dist/src/commands/readyz.js +1 -1
  16. package/dist/src/commands/refund.js +57 -13
  17. package/dist/src/commands/services.js +1068 -14
  18. package/dist/src/commands/skill.js +5 -8
  19. package/dist/src/commands/vault.js +20 -27
  20. package/dist/src/commands/vault_handoff.js +0 -18
  21. package/dist/src/main.js +43 -330
  22. package/dist/src/render/markdown.js +3 -0
  23. package/dist/src/render/qr.js +1 -1
  24. package/dist/src/state/cart_session.js +1 -4
  25. package/dist/src/state/client_context.js +0 -72
  26. package/dist/src/state/config.js +14 -44
  27. package/dist/src/state/device_authority.js +41 -141
  28. package/docs/agent/buyer/cart-checkout.json +6 -8
  29. package/docs/agent/buyer/catalog-list.json +9 -29
  30. package/docs/agent/buyer/identity-and-sessions.json +9 -26
  31. package/docs/agent/buyer/install-and-setup.json +9 -10
  32. package/docs/agent/buyer/orders-refunds.json +9 -25
  33. package/docs/agent/buyer/payment-flow.json +4 -6
  34. package/docs/agent/buyer/purchased-content.json +4 -18
  35. package/docs/agent/buyer/quickstart.json +17 -26
  36. package/docs/agent/buyer/render-hosts.json +12 -23
  37. package/docs/agent/buyer/service-feedback.json +3 -4
  38. package/docs/cli-reference/agent-types.md +2 -7
  39. package/docs/cli-reference/commands/buy.md +1 -2
  40. package/docs/cli-reference/commands/cart/add.md +1 -5
  41. package/docs/cli-reference/commands/cart/clear.md +1 -1
  42. package/docs/cli-reference/commands/cart/index.md +1 -1
  43. package/docs/cli-reference/commands/cart/next.md +1 -5
  44. package/docs/cli-reference/commands/cart/remove.md +1 -1
  45. package/docs/cli-reference/commands/cart/show.md +1 -1
  46. package/docs/cli-reference/commands/catalog/index.md +1 -1
  47. package/docs/cli-reference/commands/catalog/list.md +2 -2
  48. package/docs/cli-reference/commands/checkout.md +2 -12
  49. package/docs/cli-reference/commands/device.md +11 -18
  50. package/docs/cli-reference/commands/docs/index.md +1 -1
  51. package/docs/cli-reference/commands/docs/list.md +1 -1
  52. package/docs/cli-reference/commands/docs/search.md +1 -1
  53. package/docs/cli-reference/commands/docs/show.md +1 -1
  54. package/docs/cli-reference/commands/install.md +6 -8
  55. package/docs/cli-reference/commands/next.md +1 -1
  56. package/docs/cli-reference/commands/order.md +1 -1
  57. package/docs/cli-reference/commands/pay.md +1 -1
  58. package/docs/cli-reference/commands/readyz.md +11 -11
  59. package/docs/cli-reference/commands/refund/cancel.md +1 -1
  60. package/docs/cli-reference/commands/refund/create.md +1 -1
  61. package/docs/cli-reference/commands/refund/get.md +2 -2
  62. package/docs/cli-reference/commands/refund/index.md +1 -1
  63. package/docs/cli-reference/commands/refund/list.md +1 -1
  64. package/docs/cli-reference/commands/refund/watch.md +1 -1
  65. package/docs/cli-reference/commands/services/action.md +3 -24
  66. package/docs/cli-reference/commands/services/checkout.md +2 -17
  67. package/docs/cli-reference/commands/services/events.md +1 -1
  68. package/docs/cli-reference/commands/services/get.md +2 -4
  69. package/docs/cli-reference/commands/services/index.md +3 -4
  70. package/docs/cli-reference/commands/services/invoke.md +4 -6
  71. package/docs/cli-reference/commands/services/list.md +1 -1
  72. package/docs/cli-reference/commands/services/next.md +12 -78
  73. package/docs/cli-reference/commands/services/quote.md +4 -4
  74. package/docs/cli-reference/commands/services/read-result.md +6 -17
  75. package/docs/cli-reference/commands/services/start.md +3 -5
  76. package/docs/cli-reference/commands/vault/access.md +1 -5
  77. package/docs/cli-reference/conventions.md +6 -4
  78. package/docs/cli-reference/index.md +1 -10
  79. package/docs/skill-bundle-rollout/01-mcp-authentication.md +134 -0
  80. package/docs/skill-bundle-rollout/02-platform-bundle-repositories.md +210 -0
  81. package/docs/skill-bundle-rollout/03-platform-publishing.md +161 -0
  82. package/docs/skill-bundle-rollout/04-first-wave-platforms.md +69 -0
  83. package/docs/skill-bundle-rollout/05-sync-operations.md +62 -0
  84. package/docs/skill-bundle-rollout/README.md +151 -0
  85. package/package.json +12 -20
  86. package/skills/itpay/SKILL.md +102 -110
  87. package/assets/sell-preview/index.html +0 -6
  88. package/assets/sell-preview/preview.js +0 -9
  89. package/bin/darwin-amd64/itpay-sell +0 -0
  90. package/bin/darwin-arm64/itpay-sell +0 -0
  91. package/bin/linux-amd64/itpay-sell +0 -0
  92. package/bin/linux-arm64/itpay-sell +0 -0
  93. package/bin/windows-amd64/itpay-sell.exe +0 -0
  94. package/dist/src/commands/aftercare.js +0 -12
  95. package/dist/src/commands/presentation.js +0 -153
  96. package/dist/src/commands/rail_catalog.js +0 -1
  97. package/dist/src/commands/services/actions.js +0 -133
  98. package/dist/src/commands/services/commerce.js +0 -197
  99. package/dist/src/commands/services/lifecycle.js +0 -418
  100. package/dist/src/commands/services/results.js +0 -130
  101. package/dist/src/render/browser.js +0 -65
  102. package/dist/src/render/checkout_handoff.js +0 -101
  103. package/dist/src/render/checkout_plan.js +0 -47
  104. package/dist/src/sell/commands.js +0 -80
  105. package/dist/src/sell/contract.js +0 -86
  106. package/dist/src/sell/local.js +0 -260
  107. package/dist/src/sell/mcp.js +0 -49
  108. package/dist/src/sell/preview.js +0 -70
  109. package/dist/src/sell/sync.js +0 -145
  110. package/dist/src/services/adapter.js +0 -1
  111. package/dist/src/services/presentation.js +0 -198
  112. package/dist/src/services/rail/actions.js +0 -51
  113. package/dist/src/services/rail/adapter.js +0 -27
  114. package/dist/src/services/rail/aftercare.js +0 -45
  115. package/dist/src/services/rail/catalog.js +0 -261
  116. package/dist/src/services/rail/checkout.js +0 -19
  117. package/dist/src/services/rail/fulfillment.js +0 -76
  118. package/dist/src/services/rail/guide.js +0 -110
  119. package/dist/src/services/rail/invocation.js +0 -54
  120. package/dist/src/services/rail/page.js +0 -76
  121. package/dist/src/services/rail/pagination.js +0 -6
  122. package/dist/src/services/rail/query.js +0 -71
  123. package/dist/src/services/rail/readers.js +0 -107
  124. package/dist/src/services/rail/reading.js +0 -125
  125. package/dist/src/services/rail/recovery.js +0 -51
  126. package/dist/src/services/rail/results.js +0 -378
  127. package/dist/src/services/rail/review.js +0 -268
  128. package/dist/src/services/rail/saved.js +0 -49
  129. package/dist/src/services/rail/wait.js +0 -11
  130. package/dist/src/services/registry.js +0 -31
  131. package/dist/src/services/schema.js +0 -21
  132. package/dist/src/services/shared/envelope.js +0 -184
  133. package/dist/src/services/shared/input.js +0 -39
  134. package/dist/src/services/shared/pagination.js +0 -28
  135. package/dist/src/services/shared/platform.js +0 -365
  136. package/dist/src/services/shared/refund.js +0 -40
  137. package/dist/src/services/shared/workflow.js +0 -28
  138. package/dist/src/state/account_auth.js +0 -347
  139. package/dist/src/state/task_journal.js +0 -90
  140. package/docs/agent/buyer/rail-booking.json +0 -176
  141. package/docs/agent/buyer/rail-fast-checkout.json +0 -48
  142. package/docs/cli-reference/commands/aftercare.md +0 -15
  143. package/docs/cli-reference/commands/auth.md +0 -96
  144. package/docs/cli-reference/commands/sell.md +0 -1364
  145. package/docs/cli-reference/commands/services/page.md +0 -71
  146. package/docs/cli-reference/commands/services/run.md +0 -152
  147. package/docs/sell.md +0 -84
  148. /package/bin/{itp.mjs → itp} +0 -0
@@ -1,378 +0,0 @@
1
- import { railAvailableActions } from "./actions.js";
2
- import { publicPurchaseTemplate, formatMoney } from "../shared/platform.js";
3
- // Public summaries and saved details use the same evidenced units and scopes.
4
- export function railJourneyFacts(card) {
5
- const offer = card.representative_offer;
6
- const metrics = card.representative_metrics;
7
- const minor = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
8
- const time = (value) => typeof value === "string" ? Date.parse(/(?:Z|[+-]\d{2}:\d{2})$/.test(value) ? value.replace(" ", "T") : `${value.replace(" ", "T")}+08:00`) : NaN;
9
- const start = time(card.rides?.[0]?.departure), end = time(card.rides?.at(-1)?.arrival);
10
- return {
11
- rail_fare_fen: minor(offer?.fare_minor), service_fee_fen: minor(offer?.service_fee_minor),
12
- rail_payable_fen: minor(offer?.rail_payable_minor), currency: offer?.currency ?? null,
13
- price_basis: "以上票价与服务费均为本次人数的整程合计(非每人);整程铁路应付=票价+各段服务费,不含未知地面费用,以新报价为准。",
14
- availability_basis: "本次库存观测可用不代表余票数量充足;只以明确的 remaining 数量判断。",
15
- rail_duration_minutes: Number.isFinite(start) && Number.isFinite(end) && end >= start ? Math.round((end - start) / 60000) : null,
16
- door_to_door_minutes: metrics?.duration_basis === "door_to_door" ? minor(metrics.door_to_door_min) : null,
17
- ...(metrics?.duration_basis !== "door_to_door" ? { ground_time_note: "接驳未完整核验,不能把铁路时长称为门到门时间。" } : {}),
18
- estimated_trip_total: metrics?.estimated_total_cost ?? null,
19
- estimate_basis: "仅是已核验费用范围的估算,不是含全部地面费用的实付额。",
20
- };
21
- }
22
- export function publicBookingSupport(card) {
23
- if (card.booking_support !== "separate_legs_only")
24
- return card.booking_support;
25
- return card.qualification_status === "eligible" && card.ticket_plans?.some(plan => plan.qualification_status === "eligible" && plan.booking_input) ? "journey" : "read_only";
26
- }
27
- export function compactRailJourney(card, serviceExecutionID, snapshotID) {
28
- if (!card.journey_id)
29
- throw new Error("saved journey has no stable ID");
30
- const rides = (card.rides ?? []).map((ride) => ({
31
- train_code: ride.train_code ?? (Array.isArray(ride.train_codes) ? ride.train_codes.join("/") : null),
32
- from_station: ride.from_station ?? null, from_station_code: ride.from_station_code ?? null,
33
- departure: ride.departure ?? null, to_station: ride.to_station ?? null,
34
- to_station_code: ride.to_station_code ?? null, arrival: ride.arrival ?? null,
35
- ...(typeof ride.wait_minutes === "number" ? { wait_minutes: ride.wait_minutes } : {}),
36
- ...(typeof ride.minimum_connection_minutes === "number" ? { required_buffer_minutes: ride.minimum_connection_minutes } : {}),
37
- ...(ride.same_run === true ? { same_run: true } : {}),
38
- }));
39
- const offer = card.representative_offer;
40
- const metrics = card.representative_metrics;
41
- const ticketSeats = (offer?.ticket_offers ?? []).map((raw) => ({
42
- leg_index: raw.leg_index, seat_type: raw.seat_type, seat_name: raw.seat_name,
43
- availability: raw.availability_text, remaining: raw.inventory_exact === true ? raw.remaining : null,
44
- }));
45
- return {
46
- journey_ref: card.journey_id,
47
- ...(card.profile_refs?.balanced ? { profile_ref: card.profile_refs.balanced } : {}),
48
- default_layer: card.default_layer ?? "main",
49
- ...(card.backup_reason ? { backup_reason: card.backup_reason } : {}),
50
- ...(card.backup_baseline_min != null ? { backup_baseline_min: card.backup_baseline_min } : {}),
51
- ...(card.backup_delta_min != null ? { backup_delta_min: card.backup_delta_min } : {}),
52
- route: card.route_text ?? (card.route_names ?? card.route ?? []).join("→"),
53
- rides,
54
- ...(card.booking_support === "single_leg" && card.qualification_status === "eligible" && card.booking_offer ? {
55
- booking_template: { command: `itpay services run ${card.booking_offer.service_id} --input-json <购票.json> --json`, executable: false,
56
- seat_choices: ticketSeats.map(seat => ({ seat_type: seat.seat_type, seat_name: seat.seat_name })),
57
- input_example: { selection: { token: card.booking_offer.selection_token, seat_type: "<返回席别代码>" }, passengers: card.passengers ?? offer?.ticket_offers?.[0]?.passengers ?? "<真实人数>" },
58
- required_input: ["file", "真实席别选择及人数"],
59
- ...(publicPurchaseTemplate(card.purchase_operation) ?? {}) },
60
- } : {}),
61
- ...(ticketSeats.length ? { seats: ticketSeats } : {}),
62
- booking_support: publicBookingSupport(card),
63
- ...(publicBookingSupport(card) === "read_only" ? { purchase_recovery: { command: `itpay services next ${serviceExecutionID} --json`, reason: "历史快照无整程凭据;读取同一执行合法准备路径,不拆单" } } : {}),
64
- ticket_plan_refs: (card.ticket_plans ?? []).map(plan => plan.ticket_plan_ref),
65
- transfer_wait_minutes: card.transfer_wait_minutes ?? [],
66
- ground_summary: card.ground_summary ?? null,
67
- passengers: card.passengers ?? offer?.ticket_offers?.[0]?.passengers ?? null,
68
- availability: card.availability ?? "unknown",
69
- qualification_status: card.qualification_status ?? "unknown",
70
- ...(card.qualification_reasons?.length ? { qualification_reasons: card.qualification_reasons } : {}),
71
- ...railJourneyFacts(card),
72
- cost_basis: metrics?.cost_basis ?? "unknown",
73
- duration_basis: metrics?.duration_basis ?? "unknown",
74
- timezone: "Asia/Shanghai",
75
- ...(card.risk_notes?.length ? { risk_notes: card.risk_notes } : {}),
76
- ...(card.observed_at ? { observed_at: card.observed_at } : {}),
77
- detail: { command: `itpay services read-result ${serviceExecutionID} --snapshot ${snapshotID} --journey ${card.journey_id} --json`,
78
- reason: card.qualification_status === "eligible" ? "读取此完整线路的席别、报价和购买条件" : "读取此线路的铁路事实和不可购买原因" },
79
- };
80
- }
81
- export function compactRailTicketPlan(raw, journeyEligible) {
82
- const legs = Array.isArray(raw.legs) ? raw.legs : [];
83
- const seats = Array.isArray(raw.seat_offers) ? raw.seat_offers : [];
84
- const tickets = Array.isArray(raw.ticket_offers) ? raw.ticket_offers : [];
85
- return {
86
- ticket_plan_ref: raw.ticket_plan_ref,
87
- passengers: raw.passenger_count ?? (Array.isArray(raw.ticket_offers) ? raw.ticket_offers[0]?.passengers : null) ?? null,
88
- purchase_support: raw.purchase_support === "separate_legs_only" ? "read_only" : raw.purchase_support ?? "none",
89
- ...(legs.length > 1 && !raw.booking_input ? { booking_unavailable: "这份快照只有历史铁路事实,没有完整整程购买凭据;不能拆腿或拼接其他快照凭据。读取同一执行当前状态,按其购买准备或过期恢复动作继续。" } : {}),
90
- qualification_status: raw.qualification_status ?? "unknown",
91
- qualification_reasons: raw.qualification_reasons ?? [],
92
- rail_amount_fen: raw.rail_amount_fen ?? null,
93
- service_fee_fen: raw.service_fee_fen ?? null,
94
- price_verified: raw.rail_amount_verified === true,
95
- ...(journeyEligible && raw.qualification_status === "eligible" && raw.booking_input && typeof raw.booking_input === "object" ? {
96
- booking_template: { command: "itpay services run itpay-rail-booking --input-json <journey.json> --json", executable: false, ticket_plan_ref: raw.ticket_plan_ref, input_example: raw.booking_input, required_input: ["file", "passengers"], ...(publicPurchaseTemplate(raw.purchase_operation) ?? {}) },
97
- } : {}),
98
- legs: legs.map((leg, index) => ({
99
- leg_index: index,
100
- travel_date: leg.travel_date ?? tickets.find(ticket => ticket.leg_index === index)?.travel_date ?? null,
101
- ...(() => {
102
- const ticket = tickets.find(ticket => ticket.leg_index === index) ?? (tickets[index]?.leg_index === undefined ? tickets[index] : undefined);
103
- const offer = ticket?.booking_offer;
104
- return legs.length === 1 && offer?.service_id && offer.selection_token && journeyEligible && raw.qualification_status === "eligible" ? {
105
- booking_template: { command: `itpay services run ${offer.service_id} --input-json <购票.json> --json`, executable: false,
106
- ticket_plan_ref: raw.ticket_plan_ref, leg_index: index,
107
- seat_choices: [{ seat_type: ticket?.seat_type, seat_name: ticket?.seat_name }],
108
- input_example: { selection: { token: offer.selection_token, seat_type: ticket?.seat_type ?? "<该方案席别代码>" },
109
- passengers: ticket?.passengers ?? raw.passenger_count ?? "<真实人数>" },
110
- required_input: ["file", "真实人数", "该方案席别代码"],
111
- },
112
- } : legs.length > 1 ? {} : { booking_unavailable: "本段没有当前可用的服务端凭据;不能猜代码或例行重查。保留快照,按当前执行的恢复动作处理;过期凭据需真实新验证。" };
113
- })(),
114
- train_code: leg.train_code ?? null,
115
- from_station: leg.from_station ?? null, from_station_code: leg.from_station_code ?? null,
116
- departure: leg.departure ?? null,
117
- to_station: leg.to_station ?? null, to_station_code: leg.to_station_code ?? null,
118
- arrival: leg.arrival ?? null,
119
- seats: (seats[index] ?? []).map((seat) => ({ seat_type: seat.seat_type, seat_name: seat.seat_name,
120
- availability: seat.availability_text, remaining: seat.inventory_exact === true ? seat.remaining : null,
121
- unit_fare_fen: seat.fare_minor, eligible: seat.eligible === true })),
122
- })),
123
- };
124
- }
125
- export function railJourneySummary(card, serviceExecutionID, snapshotID) {
126
- const rides = Array.isArray(card.rides) ? card.rides : [];
127
- const trains = rides.map((ride) => ride.train_code).filter(Boolean);
128
- const first = rides[0];
129
- const last = rides.at(-1);
130
- const offer = card.representative_offer;
131
- const metrics = card.representative_metrics;
132
- return {
133
- journey_id: card.journey_id,
134
- ...(snapshotID ? (() => { const detail = compactRailJourney(card, serviceExecutionID, snapshotID); return { rides: detail.rides, seats: detail.seats, default_layer: detail.default_layer, detail: detail.detail, ...(detail.booking_template ? { booking_template: detail.booking_template } : {}) }; })() : {}),
135
- route: (card.route_names?.length ? card.route_names : card.route ?? []).join("→"),
136
- ...(trains.length ? { trains } : {}),
137
- ...(first?.departure || last?.arrival ? { time: `${first?.departure ?? ""}–${last?.arrival ?? ""}` } : {}),
138
- ...(offer && typeof offer.rail_payable_minor === "number" ? { price: formatMoney(offer.rail_payable_minor, offer.currency) } : { price: "unknown" }),
139
- ...railJourneyFacts(card),
140
- ...(metrics ? { calculation_scope: {
141
- origin: metrics.origin_scope ?? "legacy_unknown", destination: metrics.destination_scope ?? "legacy_unknown",
142
- duration_basis: metrics.duration_basis ?? "legacy_unknown", arrival_basis: metrics.arrival_basis ?? "legacy_unknown",
143
- cost_basis: metrics.cost_basis ?? "legacy_unknown",
144
- duration_minutes: metrics.duration_basis === "door_to_door" ? metrics.door_to_door_min ?? null : railJourneyFacts(card).rail_duration_minutes,
145
- } } : {}),
146
- ...(card.decision_role ? { decision_role: card.decision_role } : {}),
147
- ...(card.explanation?.length ? { explanation: card.explanation } : {}),
148
- ...(card.reason_codes?.length ? { reason_codes: card.reason_codes } : {}),
149
- ...(card.tradeoff_codes?.length ? { tradeoff_codes: card.tradeoff_codes } : {}),
150
- ...(card.recommended_profile ? { recommended_profile: card.recommended_profile,
151
- profile_comparison: { scope: "within_journey", earliest_arrival_label: "本路线较早到达配置", arrival_basis: metrics?.arrival_basis ?? "unknown", rule: "profile角色不是全目录排序;不能据此宣称全局最早。" },
152
- recommended_price_basis: "推荐席别及已核验接驳估算以recommended_profile为准;按其ticket_plan_ref/offer_refs读取完整方案,新报价确认后下单。" } : {}),
153
- availability: card.availability,
154
- booking_support: publicBookingSupport(card),
155
- ...(publicBookingSupport(card) === "read_only" ? { purchase_recovery: { command: `itpay services next ${serviceExecutionID} --json`, reason: "历史快照无整程凭据;读取同一执行当前合法购买准备路径,不拆单" } } : {}),
156
- ...(card.observed_at ? { observed_at: card.observed_at } : {}),
157
- ...(card.risk_notes?.length ? { risk_notes: card.risk_notes } : {}),
158
- ...(card.booking_support === "single_leg"
159
- ? {
160
- select_user_chosen: `itpay services action ${serviceExecutionID} --action select_journey --actor-type human --status approved --input journey_id=${card.journey_id} --json`,
161
- // Delegated selection under explicit user rules is an agent action —
162
- // never record it as a human pick.
163
- select_delegated: `itpay services action ${serviceExecutionID} --action select_journey --actor-type agent --status approved --input journey_id=${card.journey_id} --input selection_mode=delegated --json`,
164
- }
165
- : {}),
166
- };
167
- }
168
- // rail.progressive.v2 read projection: committed snapshots only; polling never
169
- // triggers supplier calls. Recommendation is absent until the decision stage
170
- // commits — alternatives still render so users can compare early.
171
- export function railPlanningEnvelope(model) {
172
- const plan = model.rail_planning;
173
- const se = model.execution.service_execution_id;
174
- const expansion = plan.search?.expansion_status ?? "running";
175
- const counts = plan.search?.counts;
176
- const journeys = [plan.recommendation, ...(plan.alternatives ?? [])].filter(Boolean);
177
- const hasEligible = typeof counts?.journeys_eligible === "number"
178
- ? counts.journeys_eligible > 0 : journeys.length > 0;
179
- const changedScopeHint = plan.query_input && !/supplier|provider|dispatch|map|amap|permission/.test(plan.search?.reason ?? "")
180
- ? "若原条件锁定车站,可提议仅放宽一端到原站周边;有已核实地点证据才建议具体站,不能说肯定有票。先询问用户是否接受该端范围变化;同意后按new_query_template开新查询,未同意则保持原站。"
181
- : /supplier|provider|dispatch|map|amap|permission/.test(plan.search?.reason ?? "")
182
- ? "依赖故障按原恢复动作处理,不通过换站掩盖故障。"
183
- : "如需改变端点,先取得真实范围变更同意,再按正式铁路指南保留原条件发起新查询。";
184
- const cards = journeys.filter(card => card.journey_id !== plan.recommendation?.journey_id)
185
- .map((card) => railJourneySummary(card, se, plan.catalog_snapshot_id ?? plan.snapshot_id));
186
- // Journey counts come from the committed catalog — real combination counts,
187
- // never seat-row counts. Transfer buckets follow the verified transfer_count.
188
- const journeyMix = counts && (counts.journeys_total ?? 0) > 0
189
- ? `已保存${counts.journeys_total}个具体车次时刻组合,其中${counts.journeys_eligible ?? "未知"}条当前满足铁路资格(直达${counts.journeys_direct ?? 0}/1中转${counts.journeys_one_transfer ?? 0}/2中转${counts.journeys_multi_transfer ?? 0};这些不是不同站序的数量)`
190
- : "";
191
- const pairProgress = counts && (counts.pairs_total ?? 0) > 0
192
- ? `已检查${counts.pairs_checked ?? 0}/${counts.pairs_total}个附近站对`
193
- : "";
194
- // §S5: failed pairs are named honestly — never implied by "checked" and
195
- // never rephrased as "no direct exists".
196
- // §S5: a rules-only recommendation after a failed model attempt is honest —
197
- // "rules finished, AI didn't", never silently dressed as a model pick.
198
- const modelDegradedHint = plan.search?.model_outcome === "fallback"
199
- ? "规则推荐已完成,AI模型未完成;推荐有效,但置信度说明以规则结果为准。"
200
- : "";
201
- const failedPairsHint = counts && (counts.pairs_failed ?? 0) > 0
202
- ? `有${counts.pairs_failed}个站对暂未查明,不能确认没有直达;已有结果仍可查看。`
203
- : "";
204
- const result = {
205
- service_execution_id: se,
206
- rail_planning: {
207
- readiness: plan.readiness,
208
- query_revision: plan.query_revision,
209
- ...(plan.query_input ? { endpoints: plan.query_input.endpoints } : {}),
210
- snapshot_id: plan.snapshot_id,
211
- expansion_status: expansion,
212
- ...(plan.search?.phase ? { phase: plan.search.phase } : {}),
213
- ...(plan.search?.transfer_status ? { transfer_status: plan.search.transfer_status } : {}),
214
- ...(plan.search?.transition_reason ? { transition_reason: plan.search.transition_reason } : {}),
215
- ...(plan.search?.authorization ? { authorization: plan.search.authorization } : {}),
216
- ...(plan.search?.expansion_target ? { expansion_target: plan.search.expansion_target } : {}),
217
- ...(plan.search?.decision_source ? { decision_source: plan.search.decision_source } : {}),
218
- ...(plan.search?.model_outcome ? { model_outcome: plan.search.model_outcome } : {}),
219
- ...(plan.search?.reason ? { reason: plan.search.reason } : {}),
220
- ...(plan.search?.poll_after_ms ? { poll_after_ms: plan.search.poll_after_ms } : {}),
221
- ...(plan.result_not_updated ? { result_not_updated: true } : {}),
222
- ...(counts ? { counts } : {}),
223
- ...(plan.coverage ? { coverage: plan.coverage } : {}),
224
- },
225
- ...(plan.notices?.length ? { notices: plan.notices } : {}),
226
- ...(plan.recommendation ? { recommendation: railJourneySummary(plan.recommendation, se, plan.catalog_snapshot_id ?? plan.snapshot_id) } : {}),
227
- ...(cards.length ? { journeys: cards } : {}),
228
- ...((plan.catalog_snapshot_id ?? plan.snapshot_id) ? { full_result: { command: `itpay services read-result ${se} --snapshot ${plan.catalog_snapshot_id ?? plan.snapshot_id} --json`, meaning: "读取已保存的完整目录;推荐和journeys仅是摘要" } } : {}),
229
- ...(!hasEligible && ["paused", "complete", "expired"].includes(expansion) && plan.query_input && plan.catalog_snapshot_id ? { new_query_template: {
230
- command: "itpay services run itpay-rail-smart --input-json <file> --json",
231
- executable: false,
232
- input_example: { ...plan.query_input, reuse_from: { execution_id: se, snapshot_id: plan.catalog_snapshot_id } },
233
- meaning: "先取得真实范围变更同意,再复制完整input_example,仅改获准端。换具体站用station及匹配名称/站码;考虑原站周边用place及原站名,删除station_code,经正常地点解析或使用已核实POI/坐标;只有明确同意整个市才用area及完整行政名,删除POI/坐标/站码。日期、人数、时限及偏好保持;新执行不改变旧查询或订单。引用仅限同owner未过期事实,跨owner拒绝;日期变更不复用旧库存。",
234
- } } : {}),
235
- ...(plan.available_actions?.length ? { available_actions: railAvailableActions(plan, se) } : {}),
236
- };
237
- const nextPoll = {
238
- command: `itpay services next ${se} --timeout 120${plan.snapshot_id ? ` --since-snapshot ${plan.snapshot_id}` : ""} --json`,
239
- reason: "仍需更多结果时有界等待同一规划(不触发供应商调用)",
240
- };
241
- switch (expansion) {
242
- case "complete":
243
- if (plan.search?.reason === "ground_evidence_required") {
244
- return {
245
- status: "awaiting_input", result,
246
- instruction: `已查到铁路班次,但具体地址的接驳或出站证据不足,${plan.query_input?.arrive_before || plan.query_input?.arrive_by ? "无法验证用户要求的到达时限" : "尚不能核验相应地点条件或门到门时间"};剩余站对未继续查询,不能说没有车。说明已查范围和缺失事实,请补充准确起终点地址或可验证的接驳/出站信息;补齐后按原要求规划,不默认扩大搜索。`,
247
- next: null, recovery: [],
248
- };
249
- }
250
- if (!hasEligible && !plan.result_not_updated) {
251
- return {
252
- status: "no_result", result,
253
- instruction: `本次已查询范围内没有当前合格线路。原因以 notices、coverage 和 search.reason 为准;缺票、接续失败、依赖故障或未覆盖范围不能改说成没有车。已有铁路事实可从 full_result 读取。${changedScopeHint}后续使用 available_actions 中与用户意图一致的动作。`,
254
- next: null, recovery: [],
255
- };
256
- }
257
- return {
258
- status: "ready",
259
- result,
260
- // §7.3: the lead line carries real combination counts bucketed by
261
- // verified transfer count — seat rows never inflate the journey count.
262
- instruction: `${plan.result_not_updated ? "快照没有更新,空增量不表示目录为空。" : ""}${hasEligible && journeys.length === 0 ? `已保存${counts?.journeys_eligible ?? "若干"}条合格线路,执行 full_result.command 读取。` : ""}${journeyMix ? `本次共${journeyMix}(席别不重复计数)。` : ""}${plan.search?.reason === "budget_exhausted" ? "本次查询已达到上限,以下是已查到的方案,搜索范围尚未全部核验。" : "本轮规划已完成。"}推荐和journeys是摘要;追问其他车次先读取 full_result 的已保存完整目录。比较需求说明首选与备选取舍后结束;已有明确代选委托则沿合格线路详情继续,只补缺失的真实同意,身份信息仍在官方页面填写。`,
263
- next: null,
264
- recovery: [],
265
- };
266
- case "paused": {
267
- if (plan.search?.reason === "legacy_plan_requires_new_search") {
268
- return {
269
- status: "awaiting_input", result,
270
- instruction: "这次规划使用旧算法快照,当前版本已暂停后续查询。已保存的结果仍可读取;若需继续覆盖未查范围,请按原出行条件发起一项新的 Smart 查询。不要把旧快照解释为新版完整结果。",
271
- next: null, recovery: [],
272
- };
273
- }
274
- // §7.1 case 1: usable directs committed while ranked scope remains. The
275
- // message must carry real counts, and expansion is a user choice — the
276
- // Agent must not treat "expandable" as consent already given.
277
- const directHint = (plan.search?.reason === "direct_options_ready" || plan.search?.phase === "direct_ready") && journeyMix
278
- ? `本次找到${counts?.journeys_direct ?? 0}趟可选直达(${journeyMix}),${pairProgress || "部分站对已核验"},中转尚未展开。可以直接选一趟;时间或价格不合适时可继续搜索其它直达及中转,等待会更久。`
279
- : "";
280
- // §2.1: a delivered-pause at the authorized transfer depth names what a
281
- // consented expand buys next — verified-empty is "nothing at this
282
- // depth", never "no routes exist".
283
- const transferHint = (plan.search?.reason === "transfer_options_ready" || plan.search?.phase === "transfer_ready") && journeyMix
284
- ? `一次中转范围已核验完毕并交付(${journeyMix})。可以直接选用;也可以继续比较两次中转方案,查询会明显更久。`
285
- : plan.search?.reason === "no_options_verified"
286
- ? `当前授权深度内的范围已核验完、未找到可用方案——这不等于没有其它路线。可授权继续更深的两次中转搜索(耗时显著增加),或停止。`
287
- : "";
288
- const expandHint = plan.search?.expansion_target === "two_transfer"
289
- ? "expand_search 将授权一次更深的两次中转搜索(等待更久)"
290
- : plan.search?.expansion_target === "more_direct_and_one_transfer"
291
- ? "expand_search 将补齐剩余直达并展开一次中转"
292
- : "expand_search 是唯一会再消耗供应商配额的命令,其余均为本地读取";
293
- // §7.1 case 3 on a recoverable pause: unverified scope is named with real
294
- // counts, and the phrasing never implies the whole nearby range ran.
295
- const partialPause = !directHint && counts && (counts.pairs_total ?? 0) > (counts.pairs_checked ?? 0)
296
- ? `已保存${counts.journeys_total ?? 0}个具体车次时刻组合,其中${counts.journeys_eligible ?? "未知"}条当前满足铁路资格;还有${(counts.pairs_total ?? 0) - (counts.pairs_checked ?? 0)}个站对/部分中转路径未核验。以下是已确认结果,不能据此断定没有其它走法。`
297
- : "";
298
- // §S5: a dispatch whose outcome is unknown pauses for reconciliation —
299
- // the message names the state, never rephrases it as a normal pause or
300
- // a no-directs result, and never offers a blind retry (expand_search is
301
- // withheld by the projection too).
302
- if (plan.search?.reason === "dispatch_outcome_unknown") {
303
- return {
304
- status: "awaiting_input",
305
- result,
306
- instruction: `${counts?.journeys_total ? `已保存${counts.journeys_total}个已核验组合仍可查看。` : ""}上一次查询发送结果未确认(可能已部分查询),系统已暂停并等待对账,不会自动重复发送,也不能据此断定没有车次。展示已有卡片,稍后可重新读取最新状态。`,
307
- next: null,
308
- recovery: [{ command: `itpay services next ${se} --json`, reason: "稍后读取对账后的最新状态" }],
309
- };
310
- }
311
- return {
312
- status: "awaiting_input",
313
- result,
314
- instruction: `${modelDegradedHint}${failedPairsHint}${directHint}${transferHint}${partialPause}${hasEligible ? "规划已发布可用结果并暂停扩展:展示现有卡片与 available_actions" : `当前已查范围尚未找到合格线路,搜索已暂停;未查范围与故障以coverage/notices为准,不是完整无解。${changedScopeHint}展示 available_actions`},等待用户意图(${expandHint})。ready 后 next 为空表示向用户汇报并等待,不是永远没有更多。比较需求说明首选与备选取舍后结束;已有代选委托且选项合格时沿详情继续到必要确认与官方入口。`,
315
- next: null,
316
- recovery: [],
317
- };
318
- }
319
- case "failed":
320
- case "cancelled":
321
- case "expired": {
322
- if (expansion === "failed" && !plan.snapshot_id &&
323
- ["address_required", "administrative_area_unresolved", "station_location_evidence_required", "coordinate_identity_unverified", "coordinate_name_mismatch", "endpoint_adcode_name_conflict", "endpoint_station_name_code_conflict", "endpoint_poi_name_conflict"].includes(plan.search?.reason ?? "")) {
324
- return {
325
- status: "awaiting_input", result,
326
- instruction: plan.search?.reason === "station_location_evidence_required"
327
- ? "尚未查票。车站身份明确,但 Smart 缺少必要地理证据;这是系统证据缺口,不要求用户换站。若双锁站仅需直达,可使用 Exact。"
328
- : "尚未查票。受影响端点的名称、归属或坐标尚不能一致确认;只补正该端点的真实信息,保留其余条件,不把此次失败说成无车。",
329
- next: null, recovery: [],
330
- };
331
- }
332
- // §7.1 case 3: when scope was only partially verified the honest wording
333
- // is "N confirmed + X pairs unverified" — never the case-2 phrasing that
334
- // implies the whole nearby range was checked.
335
- const partial = counts && (counts.pairs_total ?? 0) > (counts.pairs_checked ?? 0)
336
- ? `已保存${counts.journeys_total ?? 0}个具体车次时刻组合,其中${counts.journeys_eligible ?? "未知"}条当前满足铁路资格;还有${(counts.pairs_total ?? 0) - (counts.pairs_checked ?? 0)}个站对/部分中转路径未核验。以下是已确认结果,不能据此断定没有其它走法。`
337
- : "";
338
- // §S5: a processing failure with a committed catalog is "saved N
339
- // verified combinations, later expansion/recommend incomplete" — the
340
- // existing catalog stays readable and nothing implies no other routes.
341
- const savedCatalog = expansion === "failed" && (counts?.journeys_total ?? 0) > 0
342
- ? `已保存${counts?.journeys_total}个已核验组合;后续扩展/推荐未完成,原因是本次处理异常。已有方案仍可查看,不代表没有其它路线,也不需要重复提交订单或付款。`
343
- : "";
344
- return {
345
- status: expansion,
346
- result,
347
- instruction: `${modelDegradedHint}${failedPairsHint}${savedCatalog}${partial}${expansion === "expired" ? "规划已过期,已保存目录仍可读取并回答历史结果追问;购买须重新核验实时余票与报价。" : "本次规划已结束,展示已有结果;需要新的供应商观测时再明确发起查询。"}`,
348
- next: null,
349
- recovery: plan.snapshot_id
350
- ? [{ command: `itpay services read-result ${se} --snapshot ${plan.snapshot_id} --json`, reason: "读取同一任务已保存的完整目录" }]
351
- : [{ command: `itpay services next ${se} --json`, reason: "读取同一任务当前状态" }],
352
- };
353
- }
354
- default: {
355
- // §7.1 case 2: the automatic phase is still running. When the verified
356
- // stage is transfer the user must hear "direct scope found nothing
357
- // usable, transfer search continues" — never re-submit.
358
- const transferSearching = plan.search?.phase === "transfer"
359
- || plan.search?.transition_reason === "no_usable_direct"
360
- || plan.search?.transfer_status === "in_progress" || plan.search?.transfer_status === "pending";
361
- // §S5: an expansion the user authorized keeps earlier directs and says
362
- // so — it must not reuse the automatic "no usable direct" wording.
363
- const manualExpansion = plan.search?.authorization === "manual"
364
- ? `已保留之前的直达结果,正在按你的要求比较更多直达/中转及分段购票方案,需要多一点时间。`
365
- : "";
366
- const transferHint = !manualExpansion && transferSearching
367
- ? `已检查本次附近站范围,暂未找到符合条件且有票的直达,正在继续搜索中转组合,需要多一点时间;不需要重新提交。`
368
- : "";
369
- return {
370
- status: "planning",
371
- result,
372
- instruction: `${modelDegradedHint}${manualExpansion}${transferHint}${failedPairsHint}规划进行中:已提交的卡片可先向用户展示比较;稍后按 next 增量轮询同一执行(since-snapshot 命中时负载会被压缩,但阶段与计数仍返回最新值)。不要重新发起查询。`,
373
- next: nextPoll,
374
- recovery: [],
375
- };
376
- }
377
- }
378
- }