@itpay/cli 2.1.5 → 2.1.6
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.
|
@@ -12,6 +12,65 @@ import { renderTerminalQR } from "../render/qr.js";
|
|
|
12
12
|
import { buildCheckoutQRPlan } from "./buy.js";
|
|
13
13
|
import { appendFeedbackPostmortemInstruction, CommandContractError, isTerminalServiceExecutionStatus, writeCommandEnvelope, } from "./guidance.js";
|
|
14
14
|
const serviceActionStatuses = new Set(["pending", "approved", "rejected", "expired", "cancelled"]);
|
|
15
|
+
const RAIL_SERVICE_GUIDANCE = {
|
|
16
|
+
"itpay-rail-exact": {
|
|
17
|
+
when_to_use: "出发和到达都是明确的火车站名时使用本服务(直达查询,最快)。只有城市、模糊位置或想要方案推荐时改用 itpay-rail-smart。",
|
|
18
|
+
input_fields: [
|
|
19
|
+
{ name: "origin", required: true, description: "出发火车站名(如'古镇'),不能是城市或地址", example: "古镇" },
|
|
20
|
+
{ name: "destination", required: true, description: "到达火车站名(如'广州南')", example: "广州南" },
|
|
21
|
+
{ name: "travel_date", required: true, description: "出行日期 YYYY-MM-DD;字段名必须是 travel_date,'date' 等别名无效", example: "2026-09-19" },
|
|
22
|
+
],
|
|
23
|
+
input_example: { origin: "古镇", destination: "广州南", travel_date: "2026-09-19" },
|
|
24
|
+
notes: [
|
|
25
|
+
"本服务只接受这三个字段,多传字段会被供应商拒绝",
|
|
26
|
+
"站名不确定就改用 itpay-rail-smart 先解析位置,不要在两个服务之间反复试错",
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
"itpay-rail-smart": {
|
|
30
|
+
when_to_use: "只有城市名、模糊位置或需要中转规划与方案推荐时使用本服务;平台负责把位置解析到车站。",
|
|
31
|
+
input_fields: [
|
|
32
|
+
{ name: "origin", required: true, description: "出发地:已知最完整的位置名或区域(城市/区县/地址均可)", example: "中山古镇" },
|
|
33
|
+
{ name: "destination", required: true, description: "目的地:与 origin 同样的位置规则", example: "广州南" },
|
|
34
|
+
{ name: "travel_date", required: true, description: "出行日期 YYYY-MM-DD;字段名必须是 travel_date", example: "2026-09-19" },
|
|
35
|
+
],
|
|
36
|
+
optional_fields: [
|
|
37
|
+
{ name: "origin_city", description: "出发地城市提示,辅助位置解析" },
|
|
38
|
+
{ name: "destination_city", description: "目的地城市提示" },
|
|
39
|
+
{ name: "origin_location", description: "已知坐标对象(高德 GCJ-02)" },
|
|
40
|
+
{ name: "destination_location", description: "目的地坐标对象" },
|
|
41
|
+
{ name: "depart_after", description: "不早于该时间出发" },
|
|
42
|
+
{ name: "arrive_before", description: "不晚于该时间到达" },
|
|
43
|
+
{ name: "priority", enum: "balanced|fastest|cheapest|safest|flexible", description: "方案偏好" },
|
|
44
|
+
{ name: "max_transfers", description: "允许中转次数,0-2" },
|
|
45
|
+
],
|
|
46
|
+
input_example: { origin: "中山古镇", destination: "广州南", travel_date: "2026-09-19", priority: "fastest" },
|
|
47
|
+
notes: [
|
|
48
|
+
"位置有歧义时会进入位置确认步骤,请用户选定后继续同一执行",
|
|
49
|
+
"推荐结果含每趟车的席别与余票;购票在后续受保护流程完成",
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
function railServiceGuidance(serviceID) {
|
|
54
|
+
return RAIL_SERVICE_GUIDANCE[serviceID];
|
|
55
|
+
}
|
|
56
|
+
const WORKFLOW_STEP_GUIDANCE = {
|
|
57
|
+
input: { meaning: "输入校验", hint: "对照服务声明的输入契约补齐字段后重新发起" },
|
|
58
|
+
quota: { meaning: "额度检查", hint: "免费额度或限流未通过;登录或稍后重试" },
|
|
59
|
+
geo: { meaning: "位置解析", hint: "检查 origin/destination 是否为真实地名;可附 origin_city 或坐标对象提示" },
|
|
60
|
+
search: { meaning: "供应商车次检索", hint: "最常见是字段名错误(必须是 travel_date)或站名不存在;按 itpay docs show rail-booking 核对输入" },
|
|
61
|
+
catalog: { meaning: "可行车次计算", hint: "位置已解析但无可行车次;换日期或换站点重试" },
|
|
62
|
+
recommend: { meaning: "方案推荐", hint: "候选集无法产出推荐;放宽条件或换日期重试" },
|
|
63
|
+
delivery: { meaning: "交付", hint: "结果组装失败;稍后重试或联系运营" },
|
|
64
|
+
};
|
|
65
|
+
function failedWorkflowStep(steps) {
|
|
66
|
+
if (!steps)
|
|
67
|
+
return undefined;
|
|
68
|
+
for (const [step, status] of Object.entries(steps)) {
|
|
69
|
+
if (status === "failure" && step !== "failure")
|
|
70
|
+
return step;
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
15
74
|
export async function runServicesStart(backend, serviceID, options = {}) {
|
|
16
75
|
const host = options.host ?? "terminal";
|
|
17
76
|
const response = await backend.startServiceExecution({
|
|
@@ -23,7 +82,8 @@ export async function runServicesStart(backend, serviceID, options = {}) {
|
|
|
23
82
|
},
|
|
24
83
|
});
|
|
25
84
|
if (response.workflow_entry) {
|
|
26
|
-
|
|
85
|
+
const guidance = railServiceGuidance(serviceID);
|
|
86
|
+
writeCommandEnvelope({ status: "input_required", result: { service_execution_id: response.execution.service_execution_id, service_id: serviceID, input_schema: response.workflow_entry.input_schema, ...(guidance ? { guidance } : {}) }, instruction: guidance ? "按 result.guidance 逐项填写输入(when_to_use 说明本服务适用场景、input_fields 是必填契约、input_example 可直接照抄),然后继续同一服务执行。不要臆造字段名。" : "根据服务声明填写输入,然后继续同一服务执行。", next: { command: `itpay services run ${serviceID} --execution ${response.execution.service_execution_id} --input-json <file> --json`, reason: "提交买家输入" }, recovery: [] }, { ...options });
|
|
27
87
|
return;
|
|
28
88
|
}
|
|
29
89
|
const capability = response.capabilities.find((item) => item.phase === response.execution.phase && !item.requires_payment);
|
|
@@ -950,6 +1010,21 @@ function servicesNextEnvelope(model) {
|
|
|
950
1010
|
command = `itpay services checkout ${id} --json`;
|
|
951
1011
|
if (state === "input_required")
|
|
952
1012
|
command = `itpay services run ${execution.service_id} --execution ${id} --input-json <file> --json`;
|
|
1013
|
+
const guidance = railServiceGuidance(execution.service_id);
|
|
1014
|
+
const failedStep = recovery ? failedWorkflowStep(model.workflow?.steps) : undefined;
|
|
1015
|
+
const failedStepGuidance = failedStep ? WORKFLOW_STEP_GUIDANCE[failedStep] : undefined;
|
|
1016
|
+
const failedInvocation = recovery
|
|
1017
|
+
? [...model.provider_invocations].reverse().find((item) => typeof item.status === "string" && item.status.startsWith("failed"))
|
|
1018
|
+
: undefined;
|
|
1019
|
+
const errorDetail = typeof failedInvocation?.error_message === "string" && failedInvocation.error_message ? failedInvocation.error_message : undefined;
|
|
1020
|
+
const providerErrorCode = typeof failedInvocation?.error_code === "string" && failedInvocation.error_code ? failedInvocation.error_code : undefined;
|
|
1021
|
+
const failedInstruction = recovery
|
|
1022
|
+
? failedStepGuidance
|
|
1023
|
+
? `执行在「${failedStepGuidance.meaning}」步失败:${failedStepGuidance.hint}${errorDetail ? `。供应商返回:${errorDetail}` : ""}。本执行已终止不能续用;修正后用同一服务新建执行重试,不要盲目重放。`
|
|
1024
|
+
: state === "failed"
|
|
1025
|
+
? `执行已失败${failedStep ? `(失败步骤:${failedStep})` : ""}且不可续用;修正输入后用同一服务新建执行重试。`
|
|
1026
|
+
: "执行未完成,请按步骤错误处理;不要重建执行或重复调用。"
|
|
1027
|
+
: undefined;
|
|
953
1028
|
return {
|
|
954
1029
|
status: state,
|
|
955
1030
|
result: {
|
|
@@ -957,14 +1032,25 @@ function servicesNextEnvelope(model) {
|
|
|
957
1032
|
service_id: execution.service_id,
|
|
958
1033
|
workflow: model.workflow,
|
|
959
1034
|
...(state === "input_required" ? { input_schema: model.workflow_entry.input_schema } : {}),
|
|
1035
|
+
...(guidance && state === "input_required" ? { guidance } : {}),
|
|
1036
|
+
...(failedStep ? { failed_step: failedStep, ...(failedStepGuidance ? { failed_step_meaning: failedStepGuidance.meaning } : {}) } : {}),
|
|
1037
|
+
...(providerErrorCode ? { provider_error_code: providerErrorCode } : {}),
|
|
1038
|
+
...(errorDetail ? { error_detail: errorDetail } : {}),
|
|
1039
|
+
...(recovery ? { retryable: state === "failed" } : {}),
|
|
960
1040
|
},
|
|
961
|
-
instruction:
|
|
962
|
-
|
|
963
|
-
: state === "payment"
|
|
1041
|
+
instruction: failedInstruction
|
|
1042
|
+
?? (state === "payment"
|
|
964
1043
|
? "服务已到付款步骤,使用现有 Checkout 完成扫码付款。"
|
|
965
|
-
:
|
|
1044
|
+
: guidance
|
|
1045
|
+
? "按 result.guidance 的字段契约填写输入后继续同一服务执行;不要臆造字段名。"
|
|
1046
|
+
: "继续读取同一执行;缺少输入时按服务声明补齐。"),
|
|
966
1047
|
next: recovery ? null : { command, reason: "继续当前流程" },
|
|
967
|
-
recovery:
|
|
1048
|
+
recovery: recovery && state === "failed"
|
|
1049
|
+
? [
|
|
1050
|
+
{ command: `itpay services start ${execution.service_id} --json`, reason: "修正输入后重新发起(本执行已终止不能续用)" },
|
|
1051
|
+
...(guidance ? [{ command: "itpay docs show rail-booking --json", reason: "查看本服务输入字段契约与示例" }] : []),
|
|
1052
|
+
]
|
|
1053
|
+
: [],
|
|
968
1054
|
};
|
|
969
1055
|
}
|
|
970
1056
|
const terminalEnvelope = terminalExecutionEnvelope(model);
|
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.6";
|
|
19
19
|
export const API_CONTRACT_REVISION = "sha256:42659989d533d5a43c4ab92eb2455bcbbc6456e799fa4c2b6587de77f06b95f7";
|
|
20
20
|
const CART_SESSION_DEFAULT_DIR = ".itpay-v3";
|
|
21
21
|
const CART_SESSION_FILENAME = "cart.json";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "itp.agent_doc.v1",
|
|
3
|
+
"role": "buyer",
|
|
4
|
+
"product_scope": "itpay is the single public CLI entry point, and $itpay is its user-facing Skill invocation. Under that one product entry point, the two top-level commerce actions are buy and sell: Buyer workflows are available now; Seller workflows will use the same entry point and are not implemented yet.",
|
|
5
|
+
"topic": "rail-booking",
|
|
6
|
+
"title": "Railway Query, Planning, and Ticket Booking",
|
|
7
|
+
"purpose": "Choose the right railway service for the buyer's situation, submit the exact input contract each service expects, and follow the protected booking flow without inventing field names.",
|
|
8
|
+
"when_to_use": [
|
|
9
|
+
"The buyer wants train schedules, routes, or tickets.",
|
|
10
|
+
"A railway service execution fails at the geo, search, catalog, or recommend step.",
|
|
11
|
+
"The buyer only knows a city or fuzzy location rather than a station name.",
|
|
12
|
+
"The buyer asks about seat preferences, refunds, or ticket issuance status."
|
|
13
|
+
],
|
|
14
|
+
"service_selection": {
|
|
15
|
+
"itpay-rail-exact": "Use when the buyer knows BOTH the exact departure and arrival railway station names (e.g. 古镇 → 广州南). Direct train query, fastest path. Do NOT use it with only a city or fuzzy location.",
|
|
16
|
+
"itpay-rail-smart": "Use when the buyer has a city name, an area, an address, or wants transfer planning and recommendations. The platform resolves locations to stations itself; never silently convert a city into its namesake station.",
|
|
17
|
+
"svc_qizhidao_company_lookup": "Not a railway service; do not mix intents."
|
|
18
|
+
},
|
|
19
|
+
"input_contract": {
|
|
20
|
+
"itpay-rail-exact": {
|
|
21
|
+
"required": {
|
|
22
|
+
"origin": "Departure railway station name only, e.g. 古镇. Not a city or an address.",
|
|
23
|
+
"destination": "Arrival railway station name only, e.g. 广州南.",
|
|
24
|
+
"travel_date": "Travel date in YYYY-MM-DD. The field name must be exactly travel_date; aliases like date are rejected."
|
|
25
|
+
},
|
|
26
|
+
"optional": "None. additionalProperties is false — extra fields are rejected by the provider.",
|
|
27
|
+
"example": {"origin": "古镇", "destination": "广州南", "travel_date": "2026-09-19"}
|
|
28
|
+
},
|
|
29
|
+
"itpay-rail-smart": {
|
|
30
|
+
"required": {
|
|
31
|
+
"origin": "Fullest known departure location: city, district, area, or address is all acceptable.",
|
|
32
|
+
"destination": "Fullest known arrival location, same rule as origin.",
|
|
33
|
+
"travel_date": "Travel date in YYYY-MM-DD. The field name must be exactly travel_date."
|
|
34
|
+
},
|
|
35
|
+
"optional": {
|
|
36
|
+
"origin_city": "Departure city hint that helps location resolution.",
|
|
37
|
+
"destination_city": "Arrival city hint.",
|
|
38
|
+
"origin_location": "Known coordinate object (AMap GCJ-02).",
|
|
39
|
+
"destination_location": "Destination coordinate object.",
|
|
40
|
+
"depart_after": "Do not depart before this time.",
|
|
41
|
+
"arrive_before": "Do not arrive after this time.",
|
|
42
|
+
"priority": "One of balanced | fastest | cheapest | safest | flexible.",
|
|
43
|
+
"max_transfers": "Integer 0-2, allowed transfer count."
|
|
44
|
+
},
|
|
45
|
+
"example": {"origin": "中山古镇", "destination": "广州南", "travel_date": "2026-09-19", "priority": "fastest"}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"flow": [
|
|
49
|
+
"services start <service> --json returns input_required plus the guidance block for rail services.",
|
|
50
|
+
"Submit input with services run <service> --execution <id> --input-json <file> --json on the SAME execution.",
|
|
51
|
+
"itpay-rail-smart may stop at a location confirmation step; show the candidate places to the buyer, then continue the same execution after the human confirms.",
|
|
52
|
+
"After a train and seat are chosen, the booking flow moves to a protected checkout page: passenger identity data is entered by the human on that page, never through the CLI.",
|
|
53
|
+
"Payment success does not mean the ticket is issued. Issuance is asynchronous; keep reading the same execution with services next.",
|
|
54
|
+
"If seat details are still syncing after issuance, advise the buyer to verify the itinerary on 12306 instead of repeating the query."
|
|
55
|
+
],
|
|
56
|
+
"failure_handling": {
|
|
57
|
+
"failed_step": "The CLI reports failed_step and failed_step_meaning in the result when a workflow fails.",
|
|
58
|
+
"geo": "Location resolution failed: check origin/destination are real place names, or attach origin_city/coordinate hints, then start a NEW execution (a failed execution is terminal).",
|
|
59
|
+
"search": "Supplier query failed: most commonly a wrong field name (must be travel_date) or a nonexistent station name; recheck input against this doc.",
|
|
60
|
+
"catalog_or_recommend": "Location resolved but no feasible trains: change the date or stations.",
|
|
61
|
+
"general_rule": "Never replay a failed execution and never guess different field names; correct the input and start a new execution of the same service."
|
|
62
|
+
},
|
|
63
|
+
"seat_preferences": {
|
|
64
|
+
"meaning": "A buyer's seat-letter preference may be planned per leg before payment. The plan is submitted to the supplier when encodable; unsupported combinations fall back to automatic assignment.",
|
|
65
|
+
"letters": "Evidence-backed letters only: O=ABCDF (second class), M=ACDF (first class), 9=ACF (business class). Other layouts auto-assign.",
|
|
66
|
+
"boundary": "Never claim a preference was fulfilled before supplier confirmation; check the issued seat in the order/status output."
|
|
67
|
+
},
|
|
68
|
+
"agent_rules": [
|
|
69
|
+
"Pick the service by the buyer's input precision: exact station names → itpay-rail-exact; anything fuzzier → itpay-rail-smart.",
|
|
70
|
+
"Never invent input field names. If the guidance block or this doc does not list a field, do not send it.",
|
|
71
|
+
"Never ask the buyer for passenger names, ID numbers, or phone numbers in chat; those are collected only on the protected checkout page.",
|
|
72
|
+
"Do not repeat or replay a failed execution; correct the input and start a new one.",
|
|
73
|
+
"Do not claim payment equals ticket issuance; issuance is asynchronous and confirmed only by the issued seat status."
|
|
74
|
+
],
|
|
75
|
+
"human_language": {
|
|
76
|
+
"prefer": ["车次", "出发站", "到达站", "出行日期", "座位", "余票", "出票中", "已出票"],
|
|
77
|
+
"avoid": ["workflow", "execution", "provider", "seat_source", "candidate_id"]
|
|
78
|
+
},
|
|
79
|
+
"search_terms": ["rail", "railway", "train", "ticket", "火车", "火车票", "高铁", "车票", "车次", "seat", "booking", "rail-booking"]
|
|
80
|
+
}
|