@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.
- package/dist/src/commands/checkout_handoff.js +1 -1
- package/dist/src/commands/install.js +2 -1
- package/dist/src/commands/services.js +49 -20
- package/dist/src/commands/skill.js +3 -0
- package/dist/src/main.js +1 -1
- package/dist/src/state/config.js +1 -1
- package/dist/src/state/device_authority.js +5 -3
- package/docs/agent/buyer/rail-booking.json +5 -5
- package/docs/agent/buyer/render-hosts.json +1 -1
- package/package.json +1 -1
- package/skills/itpay/SKILL.md +26 -4
|
@@ -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
|
-
...(
|
|
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
|
-
|
|
1530
|
+
recommended_price_basis: "推荐席别及已核验接驳估算以recommended_profile为准;按其ticket_plan_ref/offer_refs读取完整方案,新报价确认后下单。" } : {}),
|
|
1514
1531
|
availability: card.availability,
|
|
1515
|
-
booking_support: card
|
|
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 &&
|
|
2570
|
-
|
|
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
|
-
?
|
|
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"
|
package/dist/src/state/config.js
CHANGED
|
@@ -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.
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
package/skills/itpay/SKILL.md
CHANGED
|
@@ -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.
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
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
|