@itpay/cli 2.1.33 → 2.1.34

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.
@@ -61,7 +61,7 @@ export function buildCheckoutHandoff(input) {
61
61
  handoff,
62
62
  instruction: input.plan?.localSimulation
63
63
  ? `本地实验:铁路与地图查询真实,授权、通知和交易模拟,不会扣款。在本机打开 handoff.url 模拟付款入口,金额 ${input.amount};仅本机桌面可达。${workBuddyAction ? "按 handoff.agent_action 原样打开一次。" : "展示原入口。"}等待用户操作后读取同一 Checkout;不创建替代订单,用户声明不证明模拟付款已确认。`
64
- : checkoutHandoffInstruction(input.agentType, input.platform, input.amount, Boolean(mobileUrl)),
64
+ : `先展示官方入口,再做非必要记忆或日志;保留必需安全检查和宿主权限。${checkoutHandoffInstruction(input.agentType, input.platform, input.amount, Boolean(mobileUrl))}`,
65
65
  };
66
66
  }
67
67
  function checkoutHandoffInstruction(agentType, platform, amount, hasMobileLink) {
@@ -12,6 +12,7 @@ export const INSTALL_AGENT_TYPES = [
12
12
  "kimi-code",
13
13
  "openclaw",
14
14
  ];
15
+ export const CODEX_WAIT_GUIDANCE = "Codex 宿主等待:保留完整工具返回,按实际返回的会话句柄及当前宿主续读工具收取最终输出,不要只取 stdout。若使用 functions.exec 包装,可用 text(await tools.exec_command(...));内层有 session_id 时用 write_stdin,外层返回运行 cell 时才用 wait。没有该包装时按宿主实际工具操作。先收取最终输出,再执行 CLI 业务 next;不要重复提交。";
15
16
  const INSTRUCTIONS = {
16
17
  "codex-desktop": "在 Codex Desktop 中始终传这个 Agent Type;付款时把返回的二维码和链接实际展示到当前对话。",
17
18
  "codex-cli": "在 Codex CLI 中始终传这个 Agent Type;付款交接只应显示在用户可见终端。",
@@ -48,7 +49,7 @@ export function runInstall(target, options = {}) {
48
49
  ...installDefinition(normalized),
49
50
  default_api: DEFAULT_BASE_URL,
50
51
  },
51
- instruction: INSTRUCTIONS[normalized],
52
+ instruction: INSTRUCTIONS[normalized] + (normalized.startsWith("codex-") ? ` ${CODEX_WAIT_GUIDANCE}` : ""),
52
53
  next: {
53
54
  command: `itpay --agent-type ${normalized} readyz --json`,
54
55
  reason: "验证当前官方 ItPay API 的可用性",
@@ -1381,6 +1381,29 @@ function railBookingEnvelope(model) {
1381
1381
  recovery: [],
1382
1382
  };
1383
1383
  }
1384
+ // Public summaries and saved details use the same evidenced units and scopes.
1385
+ function railJourneyFacts(card) {
1386
+ const offer = card.representative_offer;
1387
+ const metrics = card.representative_metrics;
1388
+ const minor = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
1389
+ const time = (value) => typeof value === "string" ? Date.parse(/(?:Z|[+-]\d{2}:\d{2})$/.test(value) ? value.replace(" ", "T") : `${value.replace(" ", "T")}+08:00`) : NaN;
1390
+ const start = time(card.rides?.[0]?.departure), end = time(card.rides?.at(-1)?.arrival);
1391
+ return {
1392
+ rail_fare_fen: minor(offer?.fare_minor), service_fee_fen: minor(offer?.service_fee_minor),
1393
+ rail_payable_fen: minor(offer?.rail_payable_minor), currency: offer?.currency ?? null,
1394
+ price_basis: "铁路应付参考额=铁路票价+各票段服务费;不含未知地面费用,以新报价为准。",
1395
+ rail_duration_minutes: Number.isFinite(start) && Number.isFinite(end) && end >= start ? Math.round((end - start) / 60000) : null,
1396
+ door_to_door_minutes: metrics?.duration_basis === "door_to_door" ? minor(metrics.door_to_door_min) : null,
1397
+ ...(metrics?.duration_basis !== "door_to_door" ? { ground_time_note: "接驳未完整核验,不能把铁路时长称为门到门时间。" } : {}),
1398
+ estimated_trip_total: metrics?.estimated_total_cost ?? null,
1399
+ estimate_basis: "仅是已核验费用范围的估算,不是含全部地面费用的实付额。",
1400
+ };
1401
+ }
1402
+ function publicBookingSupport(card) {
1403
+ if (card.booking_support !== "separate_legs_only")
1404
+ return card.booking_support;
1405
+ return card.qualification_status === "eligible" && card.ticket_plans?.some(plan => plan.qualification_status === "eligible" && plan.booking_input) ? "journey" : "read_only";
1406
+ }
1384
1407
  function compactRailJourney(card, serviceExecutionID, snapshotID) {
1385
1408
  if (!card.journey_id)
1386
1409
  throw new Error("saved journey has no stable ID");
@@ -1399,14 +1422,6 @@ function compactRailJourney(card, serviceExecutionID, snapshotID) {
1399
1422
  leg_index: raw.leg_index, seat_type: raw.seat_type, seat_name: raw.seat_name,
1400
1423
  availability: raw.availability_text, remaining: raw.inventory_exact === true ? raw.remaining : null,
1401
1424
  }));
1402
- const railwayTime = (value) => {
1403
- if (typeof value !== "string")
1404
- return NaN;
1405
- const time = value.replace(" ", "T");
1406
- return Date.parse(/(?:Z|[+-]\d{2}:\d{2})$/.test(time) ? time : `${time}Z`);
1407
- };
1408
- const start = railwayTime(rides[0]?.departure);
1409
- const end = railwayTime(rides.at(-1)?.arrival);
1410
1425
  return {
1411
1426
  journey_ref: card.journey_id,
1412
1427
  ...(card.profile_refs?.balanced ? { profile_ref: card.profile_refs.balanced } : {}),
@@ -1423,6 +1438,8 @@ function compactRailJourney(card, serviceExecutionID, snapshotID) {
1423
1438
  required_input: ["file", "真实席别选择及人数"] },
1424
1439
  } : {}),
1425
1440
  ...(ticketSeats.length ? { seats: ticketSeats } : {}),
1441
+ booking_support: publicBookingSupport(card),
1442
+ ...(publicBookingSupport(card) === "read_only" ? { purchase_recovery: { command: `itpay services next ${serviceExecutionID} --json`, reason: "历史快照无整程凭据;读取同一执行合法准备路径,不拆单" } } : {}),
1426
1443
  ticket_plan_refs: (card.ticket_plans ?? []).map(plan => plan.ticket_plan_ref),
1427
1444
  transfer_wait_minutes: card.transfer_wait_minutes ?? [],
1428
1445
  ground_summary: card.ground_summary ?? null,
@@ -1430,13 +1447,11 @@ function compactRailJourney(card, serviceExecutionID, snapshotID) {
1430
1447
  availability: card.availability ?? "unknown",
1431
1448
  qualification_status: card.qualification_status ?? "unknown",
1432
1449
  ...(card.qualification_reasons?.length ? { qualification_reasons: card.qualification_reasons } : {}),
1433
- ...(offer ? { rail_payable_fen: offer.rail_payable_minor, currency: offer.currency } : { rail_payable_fen: null }),
1434
- estimated_trip_total: metrics?.estimated_total_cost ?? null,
1450
+ ...railJourneyFacts(card),
1435
1451
  cost_basis: metrics?.cost_basis ?? "unknown",
1436
1452
  duration_basis: metrics?.duration_basis ?? "unknown",
1437
1453
  timezone: "Asia/Shanghai",
1438
1454
  ...(card.risk_notes?.length ? { risk_notes: card.risk_notes } : {}),
1439
- rail_duration_minutes: Number.isFinite(start) && Number.isFinite(end) && end >= start ? Math.round((end - start) / 60000) : null,
1440
1455
  ...(card.observed_at ? { observed_at: card.observed_at } : {}),
1441
1456
  detail: { command: `itpay services read-result ${serviceExecutionID} --snapshot ${snapshotID} --journey ${card.journey_id} --json`,
1442
1457
  reason: card.qualification_status === "eligible" ? "读取此完整线路的席别、报价和购买条件" : "读取此线路的铁路事实和不可购买原因" },
@@ -1449,7 +1464,8 @@ function compactRailTicketPlan(raw, journeyEligible) {
1449
1464
  return {
1450
1465
  ticket_plan_ref: raw.ticket_plan_ref,
1451
1466
  passengers: raw.passenger_count ?? (Array.isArray(raw.ticket_offers) ? raw.ticket_offers[0]?.passengers : null) ?? null,
1452
- purchase_support: raw.purchase_support ?? "none",
1467
+ purchase_support: raw.purchase_support === "separate_legs_only" ? "read_only" : raw.purchase_support ?? "none",
1468
+ ...(legs.length > 1 && !raw.booking_input ? { booking_unavailable: "这份快照只有历史铁路事实,没有完整整程购买凭据;不能拆腿或拼接其他快照凭据。读取同一执行当前状态,按其购买准备或过期恢复动作继续。" } : {}),
1453
1469
  qualification_status: raw.qualification_status ?? "unknown",
1454
1470
  qualification_reasons: raw.qualification_reasons ?? [],
1455
1471
  rail_amount_fen: raw.rail_amount_fen ?? null,
@@ -1498,21 +1514,23 @@ function railJourneySummary(card, serviceExecutionID, snapshotID) {
1498
1514
  route: (card.route_names?.length ? card.route_names : card.route ?? []).join("→"),
1499
1515
  ...(trains.length ? { trains } : {}),
1500
1516
  ...(first?.departure || last?.arrival ? { time: `${first?.departure ?? ""}–${last?.arrival ?? ""}` } : {}),
1501
- ...(offer ? { price: formatMoney(offer.rail_payable_minor, offer.currency) } : { price: "unknown" }),
1517
+ ...(offer && typeof offer.rail_payable_minor === "number" ? { price: formatMoney(offer.rail_payable_minor, offer.currency) } : { price: "unknown" }),
1518
+ ...railJourneyFacts(card),
1502
1519
  ...(metrics ? { calculation_scope: {
1503
1520
  origin: metrics.origin_scope ?? "legacy_unknown", destination: metrics.destination_scope ?? "legacy_unknown",
1504
1521
  duration_basis: metrics.duration_basis ?? "legacy_unknown", arrival_basis: metrics.arrival_basis ?? "legacy_unknown",
1505
1522
  cost_basis: metrics.cost_basis ?? "legacy_unknown",
1506
- duration_minutes: metrics.door_to_door_min ?? null,
1523
+ duration_minutes: metrics.duration_basis === "door_to_door" ? metrics.door_to_door_min ?? null : railJourneyFacts(card).rail_duration_minutes,
1507
1524
  } } : {}),
1508
1525
  ...(card.decision_role ? { decision_role: card.decision_role } : {}),
1509
1526
  ...(card.explanation?.length ? { explanation: card.explanation } : {}),
1510
1527
  ...(card.reason_codes?.length ? { reason_codes: card.reason_codes } : {}),
1511
1528
  ...(card.tradeoff_codes?.length ? { tradeoff_codes: card.tradeoff_codes } : {}),
1512
1529
  ...(card.recommended_profile ? { recommended_profile: card.recommended_profile,
1513
- price_basis: "price为默认购票方案参考价;推荐对应的席别、接驳及总价以recommended_profile为准,按其中ticket_plan_ref/offer_refs查看并确认后下单。" } : {}),
1530
+ recommended_price_basis: "推荐席别及已核验接驳估算以recommended_profile为准;按其ticket_plan_ref/offer_refs读取完整方案,新报价确认后下单。" } : {}),
1514
1531
  availability: card.availability,
1515
- booking_support: card.booking_support,
1532
+ booking_support: publicBookingSupport(card),
1533
+ ...(publicBookingSupport(card) === "read_only" ? { purchase_recovery: { command: `itpay services next ${serviceExecutionID} --json`, reason: "历史快照无整程凭据;读取同一执行当前合法购买准备路径,不拆单" } } : {}),
1516
1534
  ...(card.observed_at ? { observed_at: card.observed_at } : {}),
1517
1535
  ...(card.risk_notes?.length ? { risk_notes: card.risk_notes } : {}),
1518
1536
  ...(card.booking_support === "single_leg"
@@ -1585,7 +1603,7 @@ function railPlanningEnvelope(model) {
1585
1603
  ...(plan.notices?.length ? { notices: plan.notices } : {}),
1586
1604
  ...(plan.recommendation ? { recommendation: railJourneySummary(plan.recommendation, se, plan.catalog_snapshot_id ?? plan.snapshot_id) } : {}),
1587
1605
  ...(cards.length ? { journeys: cards } : {}),
1588
- ...(plan.snapshot_id ? { full_result: { command: `itpay services read-result ${se} --snapshot ${plan.snapshot_id} --json`, meaning: "读取已保存的完整目录;推荐和journeys仅是摘要" } } : {}),
1606
+ ...((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仅是摘要" } } : {}),
1589
1607
  ...(!hasEligible && ["paused", "complete", "expired"].includes(expansion) && plan.query_input && plan.catalog_snapshot_id ? { new_query_template: {
1590
1608
  command: "itpay services run itpay-rail-smart --input-json <file> --json",
1591
1609
  executable: false,
@@ -1603,7 +1621,7 @@ function railPlanningEnvelope(model) {
1603
1621
  if (plan.search?.reason === "ground_evidence_required") {
1604
1622
  return {
1605
1623
  status: "awaiting_input", result,
1606
- instruction: "已查到铁路班次,但具体地址的接驳或出站证据不足,无法验证到达时限;剩余站对未继续查询,不能说没有车。向用户说明已查范围,并请其补充准确起终点地址或可验证的接驳/出站信息;取得新事实后再按其要求重新规划,不要默认扩大铁路搜索。",
1624
+ instruction: `已查到铁路班次,但具体地址的接驳或出站证据不足,${plan.query_input?.arrive_before || plan.query_input?.arrive_by ? "无法验证用户要求的到达时限" : "尚不能核验相应地点条件或门到门时间"};剩余站对未继续查询,不能说没有车。说明已查范围和缺失事实,请补充准确起终点地址或可验证的接驳/出站信息;补齐后按原要求规划,不默认扩大搜索。`,
1607
1625
  next: null, recovery: [],
1608
1626
  };
1609
1627
  }
@@ -2566,8 +2584,19 @@ export async function runServicesRun(backend, config, serviceID, input, options
2566
2584
  : reason === "stored_selection_inconsistent" ? "服务端凭据与已存目录不一致,补参数无法修复。停止购买并提供诊断标识 stored_selection_inconsistent;不猜字段、不重新创建购买。"
2567
2585
  : "购买凭据未通过校验,原因尚未确定且没有安全自动恢复。停止,不把推测报成原因或追加隐藏字段。", inputInvalid ? [{ command: "itpay services list --json", reason: "定位原查询并读取当前完整购买模板" }, { command: "itpay docs show rail-booking --json", reason: "读取正式输入约束" }] : [], undefined, { service_id: serviceID, ...(id ? { service_execution_id: id } : {}), ...(reason ? { reason } : {}), execution_created: Boolean(id) });
2568
2586
  }
2569
- if (cause instanceof HttpError && ["rail_journey_selection_required", "rail_selection_expired"].includes(cause.code ?? "")) {
2570
- throw new CommandContractError(cause.code, cause.message, "整程必须使用原查询当前完整 ticket plan 的 selections 模板;手填 legs 只兼容单腿。选择过期时说明缺少当前有效凭据,按用户决定取得完整方案,不拆单、不拼腿、不擅自换站或日期。", [{ command: "itpay services list --json", reason: "找回原查询并读取其完整方案模板" }, { command: "itpay docs show rail-booking --json", reason: "读取正式整程输入合同" }], undefined, { service_id: serviceID, ...(id ? { service_execution_id: id } : {}) });
2587
+ if (cause instanceof HttpError && cause.code === "rail_selection_expired") {
2588
+ const expiry = cause.payload?.selection_expiry;
2589
+ const bookingID = expiry?.booking_execution_id ?? id;
2590
+ const refreshInput = !bookingID && expiry?.safe_new_query && ["itpay-rail-smart", "itpay-rail-exact"].includes(expiry.query_service_id) ? expiry.query_input : undefined;
2591
+ const refresh = refreshInput !== undefined;
2592
+ throw new CommandContractError(cause.code, cause.message, refresh ? "刚才余票信息已过时,按原条件刷新一次真实库存(可能消耗查询额度);不需要重复批准同条件查票。有价格、库存或条款变化再确认;不复用旧库存、不换站或日期。"
2593
+ : bookingID ? "保留原购买执行,先读取订单与付款事实;不要重新查票、创建替代购买或重复付款。"
2594
+ : "旧选择凭据已过期,历史详情不能续期;缺少完整原查询条件或安全购买状态证据,停止自动刷新。", bookingID ? [{ command: `itpay services next ${bookingID} --json`, reason: "核对原购买执行和付款状态" }] : [], refreshInput && expiry ? { schema_version: "itpay.interaction.v1", stage: "input_required", input_template: {
2595
+ command: `itpay services run ${expiry.query_service_id} --input-json <file> --json`, input: refreshInput, required_input: ["file"], executable: false,
2596
+ } } : undefined, { service_id: serviceID, ...(bookingID ? { service_execution_id: bookingID } : {}), ...(expiry ? { selection_expiry: expiry } : {}) });
2597
+ }
2598
+ if (cause instanceof HttpError && cause.code === "rail_journey_selection_required") {
2599
+ throw new CommandContractError(cause.code, cause.message, "整程必须使用原查询当前完整 ticket plan 的 selections 模板;手填 legs 只兼容单腿。缺少整程模板时读取原方案;不拆单、不拼腿、不擅自换站或日期。", [{ command: "itpay services list --json", reason: "找回原查询并读取其完整方案模板" }, { command: "itpay docs show rail-booking --json", reason: "读取正式整程输入合同" }], undefined, { service_id: serviceID, ...(id ? { service_execution_id: id } : {}) });
2571
2600
  }
2572
2601
  if (cause instanceof HttpError && cause.code === "rail_seat_code_invalid" && id) {
2573
2602
  throw new CommandContractError(cause.code, cause.message, "席别必须使用原查询 booking_template.seat_choices 中与名称对应的代码,不能填“二等座”等显示名。用原 selection token 与真实人数修正输入,继续同一booking execution;若只是代码写法修正且车次、席别名称、人数等购买条件未变,沿用用户已给的确认,不再重复询问。", [{ command: `itpay services next ${id} --json`, reason: "读取同一booking当前状态后修正输入" }]);
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { CODEX_WAIT_GUIDANCE } from "./install.js";
4
5
  import { declaredAgentType } from "../state/agent_type.js";
5
6
  import { CommandContractError, writeCommandEnvelope } from "./guidance.js";
6
7
  const commandDir = dirname(fileURLToPath(import.meta.url));
@@ -19,6 +20,8 @@ export function runSkillShow(name, options = {}) {
19
20
  }
20
21
  validateSkill(content);
21
22
  const agentType = options.agentType ?? declaredAgentType();
23
+ if (agentType?.startsWith("codex-"))
24
+ content += `\n## Codex command continuation\n\n${CODEX_WAIT_GUIDANCE}\n`;
22
25
  const envelope = {
23
26
  status: "shown",
24
27
  result: { skill: ITPAY_SKILL, content },
package/dist/src/main.js CHANGED
@@ -155,7 +155,7 @@ function reportCLIError(error, contract) {
155
155
  reason: "检查锁持有者;只回收已退出进程留下的锁,不改变设备身份",
156
156
  }] : [];
157
157
  const authorizationInstruction = stateError
158
- ? "当前运行环境无法写入 owner-only Device 状态;请保持同一 Node、CLI 和 Agent Type,在允许持久写入 ~/.itpay-v3 的执行环境中重试。不要手工创建 lock、删除 identity 或换运行时碰运气。"
158
+ ? `当前运行环境无法写入 owner-only Device 状态;受阻路径 ${stateError.blockedPath ?? "~/.itpay-v3"},操作 ${stateError.operation}(${stateError.causeCode})。保持同一 Node、CLI 和 Agent Type,修复该路径持久写权限后重试原命令;不要手工创建 lock、删除 identity、随意更换 HOME 或换运行时。`
159
159
  : lockError
160
160
  ? "另一个进程仍在更新本地身份。执行 recovery 检查;若持有者仍活跃,等待后继续原任务,不要删除锁或重建身份。"
161
161
  : error instanceof HttpError && error.code === "agent_device_session_required"
@@ -15,7 +15,7 @@ import { OperationJournal } from "./operation_journal.js";
15
15
  export const DEFAULT_BASE_URL = "https://app.itpay.ai";
16
16
  export const DEV_BASE_URL = "https://dev.itpay.ai";
17
17
  export const SANDBOX_BASE_URL = "https://sandbox.itpay.ai";
18
- export const CLI_VERSION = "2.1.33";
18
+ export const CLI_VERSION = "2.1.34";
19
19
  export const API_CONTRACT_REVISION = "sha256:80d69dbad36debbe4bdfbe468127a934c87b657e7795f369bb04de54863da476";
20
20
  const CART_SESSION_DEFAULT_DIR = ".itpay-v3";
21
21
  const CART_SESSION_FILENAME = "cart.json";
@@ -308,11 +308,13 @@ export class DeviceAuthorizationError extends Error {
308
308
  export class DeviceStateError extends Error {
309
309
  operation;
310
310
  causeCode;
311
+ blockedPath;
311
312
  code = "device_state_unwritable";
312
- constructor(operation, causeCode) {
313
+ constructor(operation, causeCode, blockedPath) {
313
314
  super(`ItPay device state operation failed: ${operation} (${causeCode})`);
314
315
  this.operation = operation;
315
316
  this.causeCode = causeCode;
317
+ this.blockedPath = blockedPath;
316
318
  this.name = "DeviceStateError";
317
319
  }
318
320
  }
@@ -462,11 +464,11 @@ function moveLockAside(path, suffix, operation) {
462
464
  function asDeviceStateError(error, operation) {
463
465
  const code = error.code;
464
466
  return code === "EACCES" || code === "EPERM" || code === "EROFS" || code === "ENOTDIR" || code === "EISDIR"
465
- ? new DeviceStateError(operation, code)
467
+ ? new DeviceStateError(operation, code, error.path)
466
468
  : undefined;
467
469
  }
468
470
  function asDeviceStatePathError(error, operation) {
469
471
  const code = error.code;
470
- return code === "EEXIST" ? new DeviceStateError(operation, code) : asDeviceStateError(error, operation);
472
+ return code === "EEXIST" ? new DeviceStateError(operation, code, error.path) : asDeviceStateError(error, operation);
471
473
  }
472
474
  import { stateDir } from "./config.js";
@@ -11,7 +11,7 @@
11
11
  "goal_and_overview": {
12
12
  "route": "Understand endpoints and constraints → 2–4 line route brief → Exact or Smart → read enough saved results for the goal → select under delegation → missing real consent only → official payment entry; show and stop.",
13
13
  "query_vs_booking": "Exact/Smart returns a query execution and saved evidence. Booking creates a separate execution from a server-issued selection; it is not a repeated search.",
14
- "comparison": "If the human only asked to compare, answer from the results and stop. A clear purchase delegation permits selection under their rules, subject to required human terms."
14
+ "comparison": "If the human only asked to compare, answer within the searched scope and stop without a buy-or-not question. Reuse already-read unchanged Skill/docs in this context; do not repeat install/readyz/docs at each step. A clear purchase delegation permits selection under their rules, subject to required human terms."
15
15
  },
16
16
  "flow_at_a_glance": [
17
17
  {
@@ -36,7 +36,7 @@
36
36
  "step": "read Smart result",
37
37
  "command": "itpay services read-result <query_execution_id> --snapshot <snapshot_id> --json",
38
38
  "returns": "same-snapshot journeys and detail.command. Single-leg detail returns a selection action: use human only for a real user pick, agent for delegated selection; its result supplies booking credentials. Transfer detail returns the complete ordered selections template for one ticket plan; do not turn it into separate purchases.",
39
- "next": "If current eligible options and full connections satisfy the request, answer or continue under delegation. Separate leg purchases have no joint guarantee."
39
+ "next": "If current eligible options and full connections satisfy the request, answer or continue under delegation. Selecting a journey does not lock inventory; preserve whole-journey purchase. No unsupported ground distance/time estimates."
40
40
  },
41
41
  {
42
42
  "step": "book",
@@ -54,7 +54,7 @@
54
54
  "step": "checkout",
55
55
  "command": "use the returned next or handoff for the same booking execution",
56
56
  "returns": "an official protected payment entry",
57
- "next": "do not infer paid or issued from the handoff"
57
+ "next": "show the official entry using the current host immediately before optional memory/log writing, then stop; retain necessary safety/permissions, do not infer paid or issued"
58
58
  },
59
59
  {
60
60
  "step": "aftercare",
@@ -64,7 +64,7 @@
64
64
  }
65
65
  ],
66
66
  "understand_before_search": [
67
- "Preserve full administrative names, original endpoint meaning, date, party size, deadlines and station locks. Ask the human only if a genuine ambiguity changes the destination or constraints.",
67
+ "Preserve full administrative names, original endpoint meaning, date, party size, deadlines and station locks. Ask only for a missing date or genuine ambiguity changing the destination/constraints; no preference means do not pre-ask station, seat or departure time.",
68
68
  "Before the first railway query, leave one 2–4 line conclusion brief in conversation or existing route-brief.txt (choose one, no Backend upload or private reasoning): Origin: full original meaning; station/area/place; evidence from user/context/reliable knowledge/actual lookup; unknown facts. Destination: the same. Journey: date, given party size/deadlines; credible station pair or transfer direction and evidence; Exact/Smart choice and one-sentence reason. Never replace this with “understood”, or write it after querying as pre-query evidence. Reuse it while waiting, reading, quoting or buying; update only changed endpoints/scope/date. With existing results, serve the facts rather than restart for a missing ceremony; missing prior evidence remains unobserved.",
69
69
  "Only clear reliable route knowledge can directly support a candidate. A merely possible/probable transfer or “a long trip should transfer” is unknown: first make one targeted lookup on 12306 or 同程, with 携程 only if needed. Stop once a credible direct station pair or transfer direction is established; do not collect complete external timetables, prices or inventory. Remembered trains still require ItPay to verify the actual date and stock.",
70
70
  "12306 https://kyfw.12306.cn/ is the official date/station/train reference; claim a date verified only after reading that date result. 同程 https://www.ly.com/mergeTrain/huochepiao helps identify station pairs and transfer directions. 携程 https://trains.ctrip.com/ is an optional supplement for transfer routes. Knowledge, search snippets and historical timetables do not prove current operation, inventory, price or purchase credentials.",
@@ -108,7 +108,7 @@
108
108
  },
109
109
  "result": "Read the resolved station pair or area scope, observed coverage, complete compact train rows, fare/inventory evidence and stable references. The recommendation card is not the full catalog. Unknown cost and unverified address ground facts remain unknown. Eligible means usable, not equally preferable. Prefer existing default_layer, roles, dominance and tradeoffs; show at most three meaningful representatives. A same-arrival, earlier-departing and more expensive dominated option stays in the saved catalog rather than taking the first screen.",
110
110
  "waiting": "Use the same execution and returned bounded wait command. next is a read; background work may still proceed. Do not resubmit, sleep/get/events repeatedly, or reread the whole guide.",
111
- "followup": "Read the saved result for the original execution. Smart: itpay services read-result <execution_id> --snapshot <snapshot_id> --json. Exact: use its returned saved-result reader. Read the relevant same-snapshot pages first; unread does not mean nonexistent. Do not automatically enumerate every theoretical combination. For a new Smart query, restore the original area/place/station intent, passengers, deadlines and preferences; an Exact pair is verified evidence, not proof of user station locks. Only a user-locked station change or scope expansion needs new consent. After a changed endpoint or date, supply the original intent and a same-owner reuse_from reference to an unexpired snapshot; unchanged resolved endpoints and matching fresh station-pair facts may be reused. Date change never reuses old inventory. Preference, party-size or deadline changes within the same scope use refine_preferences on the existing plan. Planning deadline does not erase saved facts; an old selection token is not refreshed by rereading. Change to a named station: kind station with matching text/code. Original-station vicinity: kind place with verified station text, remove station_code, resolve normally or use verified POI/coordinates (existing 30 km/5 candidates). Whole city only with explicit consent: kind area with full administrative name, remove POI/location/station_code. Preserve date, passengers, time limits and preferences; refuse cross-owner reuse. The public Smart input does not expose overnight layover. A home or specific address remains the destination; nearby railway stations are candidates, never implicit station locks. Report the rail arrival station separately from any unknown last-mile connection. Preserve the endpoint brief; update only affected facts. A terminal location failure follows its explicit complete new-query template only after new evidence."
111
+ "followup": "Read the saved result for the original execution. Smart: itpay services read-result <execution_id> --snapshot <snapshot_id> --json. Exact: use its returned saved-result reader. Read the relevant same-snapshot pages first; unread does not mean nonexistent. Do not automatically enumerate every theoretical combination. For a new Smart query, restore the original area/place/station intent, passengers, deadlines and preferences; an Exact pair is verified evidence, not proof of user station locks. Only a user-locked station change or scope expansion needs new consent. After a changed endpoint or date, supply the original intent and a same-owner reuse_from reference to an unexpired snapshot; unchanged resolved endpoints and matching fresh station-pair facts may be reused. Date change never reuses old inventory. Preference, party-size or deadline changes within the same scope use refine_preferences on the existing plan. Planning deadline does not erase saved facts; an old selection token is not refreshed by rereading. For expiry, use only the server safe fresh-query template once within the original authorization, preserving all conditions and omitting reuse_from; it may consume query quota. Existing or unknown purchases resume the same booking/order, never new queries. Refresh does not guarantee inventory or original price; confirm materially changed terms. Change to a named station: kind station with matching text/code. Original-station vicinity: kind place with verified station text, remove station_code, resolve normally or use verified POI/coordinates (existing 30 km/5 candidates). Whole city only with explicit consent: kind area with full administrative name, remove POI/location/station_code. Preserve date, passengers, time limits and preferences; refuse cross-owner reuse. The public Smart input does not expose overnight layover. A home or specific address remains the destination; nearby railway stations are candidates, never implicit station locks. Report the rail arrival station separately from any unknown last-mile connection. Preserve the endpoint brief; update only affected facts. A terminal location failure follows its explicit complete new-query template only after new evidence."
112
112
  },
113
113
  "select_and_book": {
114
114
  "delegation": "Apply existing seat/price/delegation rules. Exact fastest requires enough coverage to compare the station-pair candidates; without ordering/coverage evidence the first page is not proof. Smart can present up to three eligible representatives once full connections meet the goal, or continue under delegation; no routine full-catalog or all-seat paging. If an unapproved seat choice changes the price, combine that decision and missing terms into one question. Do not default to second class or require a three-tier menu. No match does not permit loosening conditions.",
@@ -73,7 +73,7 @@
73
73
  }
74
74
  ],
75
75
  "agent_rules": [
76
- "Agent Type is stable identity; Host is presentation and may be explicitly overridden.",
76
+ "Agent Type is stable identity; Host is presentation and may be explicitly overridden. Once the official handoff is available, show it on this host before optional memory/log work; retain necessary safety and permissions.",
77
77
  "A local QR path is not visible until a desktop Agent attaches or renders that file on the human-facing surface; WorkBuddy Checkout handoff uses its returned Card Link and mobile URL; auth login may separately return a local QR path.",
78
78
  "For workbuddy with plain-chat, execute handoff.agent_action exactly once; it calls present_files with handoff.url as the only files element and opens the rendered Card Link in the right-side browser, then send handoff.mobile_url so phone users can tap straight into Alipay or WeChat Pay.",
79
79
  "For zcode with plain-chat, open handoff.url in the built-in browser and also send handoff.mobile_url as the phone entry; do not only paste text, download or rebuild a QR, or invent a private browser-tool payload; when the built-in browser is unavailable show both clickable links.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itpay/cli",
3
- "version": "2.1.33",
3
+ "version": "2.1.34",
4
4
  "description": "The ItPay CLI for services, orders, and human-authorized purchased content.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,11 +33,26 @@ human the useful result or action, not the internal steps.
33
33
  - Selling: `itpay sell guide --json`, then `itpay sell status --json` and the
34
34
  packaged seller guide.
35
35
 
36
+ Reuse the Skill and business documents already read in this context while their
37
+ version is unchanged; do not repeat docs/install/readyz at every step. Keep initial
38
+ installation and upgrade checks. For a comparison, ask only for a missing date or
39
+ a genuine endpoint ambiguity, not optional station/seat/time preferences. Preserve
40
+ area/place intent and human station locks; answer the comparison without forcing
41
+ a purchase question. Fastest means within the searched scope; choosing a journey
42
+ is not locking stock. Do not invent ground distances or travel times.
43
+
36
44
  If an ambiguous request could mean an earlier purchase or a new query, ask
37
45
  which one the human means before spending quota or starting a purchase.
38
46
 
39
47
  ## Follow one envelope
40
48
 
49
+ If the host command is still running with temporarily empty stdout, preserve its
50
+ complete tool return and continue reading the same session until final output and
51
+ exit. Empty output is not a business failure. Do not submit again, background the
52
+ command with `&`, or use process checks instead of collecting its result. Report
53
+ host tool facts if continuation fails; do not guess a map failure. Collect the
54
+ current command before following the CLI business `next`.
55
+
41
56
  Read `result` and status first, then `instruction` and the applicable `next`,
42
57
  `interaction`, `handoff` or `recovery`. Commands are executable only when all required
43
58
  arguments are present. Fill an `input_template` with unresolved values before
@@ -54,7 +69,11 @@ a template has not been submitted.
54
69
 
55
70
  Use the current execution or order for waiting and recovery; do not replay the supplier query. A
56
71
  saved result remains readable after the planning window, while a new purchase
57
- may require fresh inventory and quote evidence. Use the documented recovery
72
+ may require fresh inventory and quote evidence. If the server returns a safe
73
+ fresh-query template for an expired selection, query once with unchanged conditions
74
+ within the original request; do not ask permission again or use reuse_from for old
75
+ inventory. Existing or unknown purchases resume only the same execution/order.
76
+ New prices, inventory or terms require necessary confirmation. Use the documented recovery
58
77
  for the actual error, preserving identity and existing orders. A terminal location query may start once from its complete new_query_template only after new evidence or necessary scope consent; preserve all unchanged conditions. If neither an applicable next, interaction, recovery nor complete new-query template is returned, stop: do not add undeclared fields, create another booking or report a guessed cause. A dependency
59
78
  wait preserves resolved endpoints; follow its recovery time and same-execution
60
79
  command. A partial search is not proof that no train exists. Distance alone does not prove a transfer is needed; an observed route through a station does not prove that station is mandatory.
@@ -69,9 +88,12 @@ may select under the human's delegation, but must not record itself as a human.
69
88
  ## Show the human
70
89
 
71
90
  Present the current result in ordinary language and make the returned official
72
- link or QR genuinely visible using the actual host's handoff. Keep internal
73
- IDs, tokens, command lines, raw envelopes and diagnostics out of human-facing
74
- messages. Traveler names, ID numbers, phones, verification codes and payment
91
+ link or QR genuinely visible using the actual host's handoff before optional
92
+ memory/log work; keep necessary safety checks and host permissions. Keep internal
93
+ IDs, command lines, raw envelopes and diagnostics out of routine human-facing
94
+ messages. When the human explicitly requests troubleshooting, provide commands,
95
+ non-secret execution correlation IDs, redacted inputs/outputs and status. Never
96
+ expose credentials, login/payment tokens or identity data. Traveler names, ID numbers, phones, verification codes and payment
75
97
  details belong only in the protected official page, never chat or local query
76
98
  input. A railway ticket plan uses one booking and one official payment entry for the whole journey, with separate issuance per leg. Raw legs support one-leg compatibility only; a multi-leg journey requires the returned complete selections template. A payment entry is not payment success; payment is not ticket issuance.
77
99
  Once the Order confirms payment, tell the human they must not pay again and