@itpay/cli 2.1.34 → 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.
- package/README.md +5 -12
- package/dist/src/client/backend.js +2 -33
- package/dist/src/client/http.js +0 -2
- package/dist/src/commands/buy.js +4 -7
- package/dist/src/commands/cart.js +15 -15
- package/dist/src/commands/catalog.js +7 -17
- package/dist/src/commands/checkout.js +5 -146
- package/dist/src/commands/checkout_handoff.js +10 -48
- package/dist/src/commands/docs.js +18 -7
- package/dist/src/commands/guidance.js +19 -69
- package/dist/src/commands/install.js +3 -6
- package/dist/src/commands/order.js +1 -14
- package/dist/src/commands/orders.js +9 -4
- package/dist/src/commands/pay.js +1 -1
- package/dist/src/commands/readyz.js +2 -2
- package/dist/src/commands/refund.js +5 -5
- package/dist/src/commands/services.js +167 -1712
- package/dist/src/commands/skill.js +5 -8
- package/dist/src/commands/vault.js +20 -27
- package/dist/src/commands/vault_handoff.js +0 -18
- package/dist/src/main.js +58 -294
- package/dist/src/render/markdown.js +3 -0
- package/dist/src/render/qr.js +1 -1
- package/dist/src/state/cart_session.js +1 -4
- package/dist/src/state/client_context.js +0 -72
- package/dist/src/state/config.js +14 -44
- package/dist/src/state/device_authority.js +41 -141
- package/docs/agent/buyer/cart-checkout.json +5 -6
- package/docs/agent/buyer/catalog-list.json +6 -24
- package/docs/agent/buyer/identity-and-sessions.json +9 -25
- package/docs/agent/buyer/install-and-setup.json +15 -11
- package/docs/agent/buyer/orders-refunds.json +5 -12
- package/docs/agent/buyer/payment-flow.json +3 -4
- package/docs/agent/buyer/purchased-content.json +4 -18
- package/docs/agent/buyer/quickstart.json +16 -24
- package/docs/agent/buyer/render-hosts.json +12 -23
- package/docs/agent/buyer/service-feedback.json +1 -2
- package/docs/cli-reference/agent-types.md +2 -7
- package/docs/cli-reference/commands/buy.md +1 -2
- package/docs/cli-reference/commands/cart/add.md +1 -1
- package/docs/cli-reference/commands/cart/clear.md +1 -1
- package/docs/cli-reference/commands/cart/index.md +1 -1
- package/docs/cli-reference/commands/cart/next.md +1 -1
- package/docs/cli-reference/commands/cart/remove.md +1 -1
- package/docs/cli-reference/commands/cart/show.md +1 -1
- package/docs/cli-reference/commands/catalog/index.md +1 -1
- package/docs/cli-reference/commands/catalog/list.md +2 -2
- package/docs/cli-reference/commands/checkout.md +2 -12
- package/docs/cli-reference/commands/device.md +11 -18
- package/docs/cli-reference/commands/docs/index.md +1 -1
- package/docs/cli-reference/commands/docs/list.md +1 -1
- package/docs/cli-reference/commands/docs/search.md +1 -1
- package/docs/cli-reference/commands/docs/show.md +1 -1
- package/docs/cli-reference/commands/install.md +6 -8
- package/docs/cli-reference/commands/next.md +1 -1
- package/docs/cli-reference/commands/order.md +1 -1
- package/docs/cli-reference/commands/pay.md +1 -1
- package/docs/cli-reference/commands/readyz.md +10 -58
- package/docs/cli-reference/commands/refund/cancel.md +1 -1
- package/docs/cli-reference/commands/refund/create.md +1 -1
- package/docs/cli-reference/commands/refund/get.md +2 -2
- package/docs/cli-reference/commands/refund/index.md +1 -1
- package/docs/cli-reference/commands/refund/list.md +1 -1
- package/docs/cli-reference/commands/refund/watch.md +1 -1
- package/docs/cli-reference/commands/services/action.md +3 -24
- package/docs/cli-reference/commands/services/checkout.md +2 -13
- package/docs/cli-reference/commands/services/events.md +1 -1
- package/docs/cli-reference/commands/services/get.md +2 -4
- package/docs/cli-reference/commands/services/index.md +3 -4
- package/docs/cli-reference/commands/services/invoke.md +4 -6
- package/docs/cli-reference/commands/services/list.md +1 -1
- package/docs/cli-reference/commands/services/next.md +12 -105
- package/docs/cli-reference/commands/services/quote.md +3 -3
- package/docs/cli-reference/commands/services/read-result.md +6 -17
- package/docs/cli-reference/commands/services/start.md +2 -2
- package/docs/cli-reference/commands/vault/access.md +1 -5
- package/docs/cli-reference/conventions.md +6 -4
- package/docs/cli-reference/index.md +1 -8
- package/docs/skill-bundle-rollout/01-mcp-authentication.md +134 -0
- package/docs/skill-bundle-rollout/02-platform-bundle-repositories.md +210 -0
- package/docs/skill-bundle-rollout/03-platform-publishing.md +161 -0
- package/docs/skill-bundle-rollout/04-first-wave-platforms.md +69 -0
- package/docs/skill-bundle-rollout/05-sync-operations.md +62 -0
- package/docs/skill-bundle-rollout/README.md +151 -0
- package/package.json +13 -20
- package/skills/itpay/SKILL.md +105 -101
- package/assets/sell-preview/index.html +0 -6
- package/assets/sell-preview/preview.js +0 -9
- package/bin/darwin-amd64/itpay-sell +0 -0
- package/bin/darwin-arm64/itpay-sell +0 -0
- package/bin/linux-amd64/itpay-sell +0 -0
- package/bin/linux-arm64/itpay-sell +0 -0
- package/bin/windows-amd64/itpay-sell.exe +0 -0
- package/dist/src/commands/presentation.js +0 -153
- package/dist/src/commands/rail_catalog.js +0 -261
- package/dist/src/render/browser.js +0 -65
- package/dist/src/sell/commands.js +0 -80
- package/dist/src/sell/contract.js +0 -86
- package/dist/src/sell/local.js +0 -260
- package/dist/src/sell/mcp.js +0 -49
- package/dist/src/sell/preview.js +0 -70
- package/dist/src/sell/sync.js +0 -145
- package/dist/src/state/account_auth.js +0 -332
- package/dist/src/state/task_journal.js +0 -90
- package/docs/agent/buyer/rail-booking.json +0 -162
- package/docs/agent/buyer/rail-fast-checkout.json +0 -48
- package/docs/cli-reference/commands/auth.md +0 -96
- package/docs/cli-reference/commands/sell.md +0 -1364
- package/docs/cli-reference/commands/services/page.md +0 -71
- package/docs/cli-reference/commands/services/run.md +0 -156
- package/docs/sell.md +0 -84
- /package/bin/{itp.mjs → itp} +0 -0
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { dirname, resolve } from 'node:path';
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, rmSync } from 'node:fs';
|
|
4
|
-
import { writeLocalPNG } from '../render/qr.js';
|
|
5
|
-
import { TaskJournal } from './task_journal.js';
|
|
6
|
-
import { taskJournalPath } from './config.js';
|
|
7
|
-
export function sellerAuthPath(baseURL, env = process.env, purpose = "seller") {
|
|
8
|
-
return resolve(stateDir(env), `${purpose}-${createHash('sha256').update(baseURL).digest('hex').slice(0, 16)}.json`);
|
|
9
|
-
}
|
|
10
|
-
function read(baseURL, env = process.env, purpose = "seller") {
|
|
11
|
-
const path = sellerAuthPath(baseURL, env, purpose);
|
|
12
|
-
if (!existsSync(path))
|
|
13
|
-
return;
|
|
14
|
-
const state = JSON.parse(readFileSync(path, 'utf8'));
|
|
15
|
-
if (state.baseURL !== baseURL)
|
|
16
|
-
throw new Error('Seller session backend mismatch');
|
|
17
|
-
return state;
|
|
18
|
-
}
|
|
19
|
-
function save(state, env = process.env, purpose = "seller") {
|
|
20
|
-
const path = sellerAuthPath(state.baseURL, env, purpose);
|
|
21
|
-
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
22
|
-
const tmp = `${path}.${process.pid}.tmp`;
|
|
23
|
-
writeFileSync(tmp, JSON.stringify(state), { mode: 0o600 });
|
|
24
|
-
renameSync(tmp, path);
|
|
25
|
-
}
|
|
26
|
-
export function sellerSessionToken(baseURL, env = process.env) {
|
|
27
|
-
const state = read(baseURL, env);
|
|
28
|
-
return state?.expiresAt && Date.parse(state.expiresAt) > Date.now() ? state.sessionToken : undefined;
|
|
29
|
-
}
|
|
30
|
-
export function sellerAuth(action, baseURL, env = process.env, fetcher = fetch) {
|
|
31
|
-
return accountAuth(action, baseURL, env, fetcher);
|
|
32
|
-
}
|
|
33
|
-
export function agentAuth(action, baseURL, backend, env = process.env, fetcher = fetch) {
|
|
34
|
-
return accountAuth(action, baseURL, env, fetcher, backend);
|
|
35
|
-
}
|
|
36
|
-
// AUTH01: dashboard session stages stay distinct — the human must know exactly
|
|
37
|
-
// what the official page still needs instead of a generic "pending".
|
|
38
|
-
const ACTIVE_SESSION_STAGES = ['created', 'waiting_provider', 'email_verification_required', 'merge_confirmation_required'];
|
|
39
|
-
function stageInstruction(stage) {
|
|
40
|
-
if (stage === 'email_verification_required') {
|
|
41
|
-
return '钱包授权已完成,官方页还需要完成联系方式验证(邮箱或手机,以页面实际提供的方式为准)。完成后继续原查询,不用重说行程。';
|
|
42
|
-
}
|
|
43
|
-
if (stage === 'merge_confirmation_required') {
|
|
44
|
-
return '官方页需要用户确认账号合并决定后才能完成登录;不要替用户选择,也不要新建授权请求。';
|
|
45
|
-
}
|
|
46
|
-
return '用户仍在官方页面完成登录;保留当前 handoff,不要生成新二维码或新请求。';
|
|
47
|
-
}
|
|
48
|
-
// AUTH07: distinct outcomes stay distinct — a technical failure is never
|
|
49
|
-
// reported as a user denial, and a missing session is its own state.
|
|
50
|
-
const TERMINAL_SESSION_STATES = {
|
|
51
|
-
expired: {
|
|
52
|
-
status: 'auth_expired',
|
|
53
|
-
instruction: '授权请求已过期。重新运行 itpay auth login 生成新请求;之前的查询输入在服务端保留,可恢复。',
|
|
54
|
-
},
|
|
55
|
-
failed: {
|
|
56
|
-
status: 'auth_failed',
|
|
57
|
-
instruction: '官方授权因技术原因未完成(不是用户拒绝)。重新运行 itpay auth login 生成新请求。',
|
|
58
|
-
},
|
|
59
|
-
cancelled: {
|
|
60
|
-
status: 'auth_cancelled',
|
|
61
|
-
instruction: '用户在官方页取消了授权;停止登录。只有用户明确重新要求授权时才发起新的登录。',
|
|
62
|
-
},
|
|
63
|
-
denied: {
|
|
64
|
-
status: 'auth_denied',
|
|
65
|
-
instruction: '官方授权被拒绝;停止登录,不自动创建新请求。只有用户明确重新要求授权时才重新登录。',
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
class AuthRequestError extends Error {
|
|
69
|
-
httpStatus;
|
|
70
|
-
constructor(httpStatus, message) {
|
|
71
|
-
super(message);
|
|
72
|
-
this.httpStatus = httpStatus;
|
|
73
|
-
this.name = 'AuthRequestError';
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
// AUTH03: transport/read uncertainty never maps to "denied" or a new session —
|
|
77
|
-
// the stored session stays on disk and the caller retries the same poll.
|
|
78
|
-
function authStatusUnknown(sessionID) {
|
|
79
|
-
return {
|
|
80
|
-
status: 'auth_status_unknown',
|
|
81
|
-
result: sessionID ? { dashboard_auth_session_id: sessionID } : {},
|
|
82
|
-
instruction: '读取官方授权状态时遇到网络或服务端临时故障;原授权请求已保留。稍后重读同一状态,不要新建授权、不要清除本地登记。',
|
|
83
|
-
next: { command: 'itpay auth status --json', reason: '稍后重读同一授权状态', poll_after_ms: 5000 },
|
|
84
|
-
recovery: [],
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
function sessionGone(env, purpose, baseURL, sessionID) {
|
|
88
|
-
rmSync(sellerAuthPath(baseURL, env, purpose), { force: true });
|
|
89
|
-
return {
|
|
90
|
-
status: 'auth_session_missing',
|
|
91
|
-
result: sessionID ? { dashboard_auth_session_id: sessionID } : {},
|
|
92
|
-
instruction: '之前的授权请求在服务端已不存在。重新运行 itpay auth login 生成新请求。',
|
|
93
|
-
next: { command: 'itpay auth login --json', reason: '重新发起官方授权' },
|
|
94
|
-
recovery: [],
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
// AUTH04: a successful bind resumes the SAME task that paused for login —
|
|
98
|
-
// from the local task journal, never a placeholder and never a guess at the
|
|
99
|
-
// latest order.
|
|
100
|
-
function authenticatedEnvelope(env, phoneVerified) {
|
|
101
|
-
const paused = new TaskJournal(taskJournalPath(env)).pausedTasks()
|
|
102
|
-
.filter((task) => task.stage === 'login_required' || task.stage === 'quota_paused');
|
|
103
|
-
const resumable = paused.filter((task) => typeof task.resume_command === 'string');
|
|
104
|
-
const result = { bound: true, phone_verified: phoneVerified };
|
|
105
|
-
let instruction = '登录与绑定已完成,告知用户可继续之前的查询;已注册账号查询不消耗试用次数,仍受正常限流。';
|
|
106
|
-
let next = null;
|
|
107
|
-
if (resumable.length === 1) {
|
|
108
|
-
next = { command: resumable[0].resume_command, reason: '恢复登录前暂停的同一查询' };
|
|
109
|
-
}
|
|
110
|
-
else if (resumable.length > 1) {
|
|
111
|
-
result.paused_executions = resumable.map((task) => ({
|
|
112
|
-
service_execution_id: task.service_execution_id,
|
|
113
|
-
...(task.service_id ? { service_id: task.service_id } : {}),
|
|
114
|
-
resume_command: task.resume_command,
|
|
115
|
-
}));
|
|
116
|
-
instruction += ' 有多个暂停的查询:只恢复与当前用户目标对应的那一个,不要一次恢复全部。';
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
instruction += ' 当前没有待恢复的暂停查询;不构造恢复命令。';
|
|
120
|
-
}
|
|
121
|
-
return { status: 'authenticated', result, instruction, next, recovery: [] };
|
|
122
|
-
}
|
|
123
|
-
async function accountAuth(action, baseURL, env, fetcher, backend) {
|
|
124
|
-
const purpose = backend ? 'agent-login' : 'seller';
|
|
125
|
-
const command = backend ? 'itpay auth status' : 'itpay sell auth status';
|
|
126
|
-
if (backend) {
|
|
127
|
-
try {
|
|
128
|
-
const current = await backend.agentAccountStatus();
|
|
129
|
-
if (current.status === 'authenticated')
|
|
130
|
-
return authenticatedEnvelope(env, current.phone_verified === true);
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
if (error instanceof AuthRequestError)
|
|
134
|
-
throw error;
|
|
135
|
-
// AUTH03: even the binding-status probe can hit transport failure; the
|
|
136
|
-
// truthful state is "unknown", never an implicit unbound or a fresh login.
|
|
137
|
-
return authStatusUnknown(undefined);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
async function request(path, init = {}) {
|
|
141
|
-
const response = await fetcher(baseURL + path, { ...init, redirect: 'error', signal: AbortSignal.timeout(15000) });
|
|
142
|
-
if (!response.ok)
|
|
143
|
-
throw new AuthRequestError(response.status, `ItPay authorization failed (${response.status}); retry login if expired`);
|
|
144
|
-
return response;
|
|
145
|
-
}
|
|
146
|
-
function pollOpenSession(state) {
|
|
147
|
-
return request(`/v1/dashboard/auth-sessions/${encodeURIComponent(state.sessionID)}?poll_token=${encodeURIComponent(state.pollToken)}`)
|
|
148
|
-
.then((response) => response.json());
|
|
149
|
-
}
|
|
150
|
-
async function bindSavedSession(state) {
|
|
151
|
-
const bound = await backend.bindAgentAccount({ dashboard_auth_session_id: state.sessionID, start_token: state.startToken });
|
|
152
|
-
if (bound.status !== 'authenticated')
|
|
153
|
-
throw new Error('Agent binding did not complete');
|
|
154
|
-
rmSync(sellerAuthPath(baseURL, env, purpose), { force: true });
|
|
155
|
-
return authenticatedEnvelope(env, bound.phone_verified === true);
|
|
156
|
-
}
|
|
157
|
-
if (action === 'login') {
|
|
158
|
-
if (backend) {
|
|
159
|
-
const open = read(baseURL, env, purpose);
|
|
160
|
-
if (open?.sessionID && open.pollToken && open.startToken) {
|
|
161
|
-
try {
|
|
162
|
-
const current = await pollOpenSession(open);
|
|
163
|
-
// AUTH02: a completed-but-unbound session binds in place; never
|
|
164
|
-
// create a second auth session for the same login.
|
|
165
|
-
if (current.status === 'completed')
|
|
166
|
-
return await bindSavedSession(open);
|
|
167
|
-
if (ACTIVE_SESSION_STAGES.includes(current.status)) {
|
|
168
|
-
const url = open.authURL ?? '';
|
|
169
|
-
const qr = url ? await writeLocalPNG(url).catch(() => undefined) : undefined;
|
|
170
|
-
return {
|
|
171
|
-
status: 'auth_pending',
|
|
172
|
-
result: {
|
|
173
|
-
dashboard_auth_session_id: open.sessionID,
|
|
174
|
-
stage: current.status,
|
|
175
|
-
expires_at: current.expires_at,
|
|
176
|
-
methods: ['phone', 'email', 'alipay', 'wechat'],
|
|
177
|
-
},
|
|
178
|
-
// SEC01: the handoff URL carries one-time credentials — it goes
|
|
179
|
-
// to the human only, never into logs, prompts, or PR material.
|
|
180
|
-
handoff: {
|
|
181
|
-
url,
|
|
182
|
-
contains_credentials: true,
|
|
183
|
-
...(qr ? { qr_local_path: qr.filePath, markdown: `` } : {}),
|
|
184
|
-
},
|
|
185
|
-
instruction: `${stageInstruction(current.status)} 沿用同一链接或二维码;不要生成新请求。`,
|
|
186
|
-
next: { command: 'itpay auth status --json', reason: '用户完成页面登录后确认绑定', poll_after_ms: 5000 },
|
|
187
|
-
recovery: [],
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
const terminal = TERMINAL_SESSION_STATES[current.status];
|
|
191
|
-
if (terminal) {
|
|
192
|
-
rmSync(sellerAuthPath(baseURL, env, purpose), { force: true });
|
|
193
|
-
// Fall through to a fresh session only after a definitive
|
|
194
|
-
// terminal state; anything else keeps the stored session.
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
return authStatusUnknown(open.sessionID);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
if (error instanceof AuthRequestError && (error.httpStatus === 404 || error.httpStatus === 410)) {
|
|
202
|
-
rmSync(sellerAuthPath(baseURL, env, purpose), { force: true });
|
|
203
|
-
// The explicit login call may open one fresh session now. Status
|
|
204
|
-
// remains read-only and still reports the missing old session.
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
return authStatusUnknown(open.sessionID);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
const response = await request('/v1/dashboard/auth-sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ return_to: backend ? '/' : '/seller' }) });
|
|
213
|
-
const result = await response.json();
|
|
214
|
-
const url = new URL(result.start_url, baseURL);
|
|
215
|
-
const sameOrigin = url.origin === new URL(baseURL).origin;
|
|
216
|
-
const alipay = url.origin === 'https://openauth.alipay.com' && url.pathname === '/oauth2/publicAppAuthorize.htm' && !url.username && !url.password;
|
|
217
|
-
const local = response.headers.get('X-ItPay-Environment') === 'local_simulation'
|
|
218
|
-
&& new URL(baseURL).protocol === 'http:' && new URL(baseURL).hostname === '127.0.0.1'
|
|
219
|
-
&& url.protocol === 'http:' && url.hostname === '127.0.0.1' && !url.username && !url.password;
|
|
220
|
-
if (!sameOrigin && !alipay && !local)
|
|
221
|
-
throw new Error('Unexpected authorization origin');
|
|
222
|
-
const fragment = new URLSearchParams(url.hash.replace(/^#dashboard-auth\?/, ''));
|
|
223
|
-
const state = url.searchParams.get('state')?.split('.');
|
|
224
|
-
const startToken = alipay
|
|
225
|
-
? (state?.length === 2 && state[0] === result.dashboard_auth_session_id ? state[1] : undefined)
|
|
226
|
-
: url.searchParams.get('start_token') || fragment.get('start_token');
|
|
227
|
-
if (!startToken || !result.poll_token || !result.dashboard_auth_session_id)
|
|
228
|
-
throw new Error('Incomplete authorization response');
|
|
229
|
-
save({ baseURL, sessionID: result.dashboard_auth_session_id, pollToken: result.poll_token, startToken, authURL: url.href }, env, purpose);
|
|
230
|
-
if (backend) {
|
|
231
|
-
const qr = await writeLocalPNG(url.href).catch(() => undefined);
|
|
232
|
-
return {
|
|
233
|
-
status: 'auth_pending',
|
|
234
|
-
result: {
|
|
235
|
-
dashboard_auth_session_id: result.dashboard_auth_session_id,
|
|
236
|
-
stage: 'created',
|
|
237
|
-
expires_at: result.expires_at,
|
|
238
|
-
methods: ['phone', 'email', 'alipay', 'wechat'],
|
|
239
|
-
},
|
|
240
|
-
handoff: {
|
|
241
|
-
url: url.href,
|
|
242
|
-
contains_credentials: true,
|
|
243
|
-
...(qr ? { qr_local_path: qr.filePath, markdown: `` } : {}),
|
|
244
|
-
},
|
|
245
|
-
instruction: '把官方授权页或二维码交给用户。用户在页面内选择手机号验证码、邮箱或钱包完成登录;不要替用户输入手机号或验证码,start_token 不要写入聊天记录或日志。',
|
|
246
|
-
next: { command: 'itpay auth status --json', reason: '用户完成页面登录后确认绑定', poll_after_ms: 5000 },
|
|
247
|
-
recovery: [],
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
return { status: 'authorization_required', authorization_url: url.href, instruction: `Complete ItPay login in the browser, then run ${command}.` };
|
|
251
|
-
}
|
|
252
|
-
const state = read(baseURL, env, purpose);
|
|
253
|
-
if (action === 'logout') {
|
|
254
|
-
const token = sellerSessionToken(baseURL, env);
|
|
255
|
-
if (token)
|
|
256
|
-
await request('/v1/me/logout', { method: 'POST', headers: { Authorization: `Bearer ${token}` } });
|
|
257
|
-
rmSync(sellerAuthPath(baseURL, env), { force: true });
|
|
258
|
-
return { status: 'logged_out' };
|
|
259
|
-
}
|
|
260
|
-
if (!backend && sellerSessionToken(baseURL, env))
|
|
261
|
-
return { status: 'authenticated', base_url: baseURL, expires_at: state?.expiresAt };
|
|
262
|
-
if (!state?.sessionID || !state.pollToken || !state.startToken) {
|
|
263
|
-
// AUTH07: a bare `status` with no session is its own truthful state, not a
|
|
264
|
-
// failure and not an implicit login.
|
|
265
|
-
if (!backend)
|
|
266
|
-
return { status: 'login_required' };
|
|
267
|
-
return {
|
|
268
|
-
status: 'login_required',
|
|
269
|
-
result: {},
|
|
270
|
-
instruction: '当前设备尚未绑定账号,也没有进行中的授权请求。需要时运行 itpay auth login 发起官方登录。',
|
|
271
|
-
next: { command: 'itpay auth login --json', reason: '需要账号绑定时发起官方授权' },
|
|
272
|
-
recovery: [],
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
let progress;
|
|
276
|
-
try {
|
|
277
|
-
progress = await pollOpenSession(state);
|
|
278
|
-
}
|
|
279
|
-
catch (error) {
|
|
280
|
-
if (error instanceof AuthRequestError && (error.httpStatus === 404 || error.httpStatus === 410)) {
|
|
281
|
-
return sessionGone(env, purpose, baseURL, state.sessionID);
|
|
282
|
-
}
|
|
283
|
-
return authStatusUnknown(state.sessionID);
|
|
284
|
-
}
|
|
285
|
-
if (progress.status !== 'completed') {
|
|
286
|
-
if (backend) {
|
|
287
|
-
const terminal = TERMINAL_SESSION_STATES[progress.status];
|
|
288
|
-
if (terminal) {
|
|
289
|
-
rmSync(sellerAuthPath(baseURL, env, purpose), { force: true });
|
|
290
|
-
return {
|
|
291
|
-
status: terminal.status,
|
|
292
|
-
result: { dashboard_auth_session_id: state.sessionID, stage: progress.status },
|
|
293
|
-
instruction: terminal.instruction,
|
|
294
|
-
next: ['denied', 'cancelled'].includes(progress.status) ? null : { command: 'itpay auth login --json', reason: '需要登录时重新发起官方授权' },
|
|
295
|
-
recovery: [],
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
return {
|
|
299
|
-
status: 'auth_pending',
|
|
300
|
-
result: {
|
|
301
|
-
dashboard_auth_session_id: state.sessionID,
|
|
302
|
-
stage: progress.status,
|
|
303
|
-
...(progress.expires_at ? { expires_at: progress.expires_at } : {}),
|
|
304
|
-
},
|
|
305
|
-
instruction: stageInstruction(progress.status),
|
|
306
|
-
next: { command: 'itpay auth status --json', reason: '轮询同一授权请求', poll_after_ms: 5000 },
|
|
307
|
-
recovery: [],
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
return { status: progress.status, instruction: 'Finish login and email verification in the browser.' };
|
|
311
|
-
}
|
|
312
|
-
if (backend) {
|
|
313
|
-
try {
|
|
314
|
-
return await bindSavedSession(state);
|
|
315
|
-
}
|
|
316
|
-
catch (error) {
|
|
317
|
-
if (error instanceof AuthRequestError)
|
|
318
|
-
throw error;
|
|
319
|
-
// Binding outcome unknown: keep the session so a retry can still bind
|
|
320
|
-
// the same completed login instead of creating a new one.
|
|
321
|
-
return authStatusUnknown(state.sessionID);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
const claimed = await request(`/v1/dashboard/auth-sessions/${encodeURIComponent(state.sessionID)}/claim?start_token=${encodeURIComponent(state.startToken)}`, { method: 'POST' });
|
|
325
|
-
const token = /(?:^|[, ]+)itpay_buyer_session=([^;]+)/.exec(claimed.headers.get('set-cookie') ?? '')?.[1];
|
|
326
|
-
const session = await claimed.json();
|
|
327
|
-
if (!token || !session.expires_at || !(Date.parse(session.expires_at) > Date.now()))
|
|
328
|
-
throw new Error('Incomplete Seller session');
|
|
329
|
-
save({ baseURL, sessionToken: token, expiresAt: session.expires_at }, env);
|
|
330
|
-
return { status: 'authenticated', base_url: baseURL, expires_at: session.expires_at };
|
|
331
|
-
}
|
|
332
|
-
import { stateDir } from "./config.js";
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
// Minimal local task journal: which Service Executions are paused at a human
|
|
2
|
-
// gate (login, rate limit) and how to resume the SAME execution. It stores
|
|
3
|
-
// no chat history, PII, credentials or tokens — only ids, stage and the
|
|
4
|
-
// resume command the owner flow already emitted. Business truth stays
|
|
5
|
-
// server-side; this file only makes "continue" point at the right task.
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { dirname } from "node:path";
|
|
8
|
-
export const PAUSED_STAGES = new Set([
|
|
9
|
-
"login_required",
|
|
10
|
-
"rate_limited",
|
|
11
|
-
"quota_paused",
|
|
12
|
-
"auth_pending",
|
|
13
|
-
"awaiting_input",
|
|
14
|
-
]);
|
|
15
|
-
const TERMINAL_STAGES = new Set([
|
|
16
|
-
"completed",
|
|
17
|
-
"delivered",
|
|
18
|
-
"issued",
|
|
19
|
-
"failed",
|
|
20
|
-
"cancelled",
|
|
21
|
-
"refunded",
|
|
22
|
-
]);
|
|
23
|
-
export class TaskJournal {
|
|
24
|
-
path;
|
|
25
|
-
constructor(path) {
|
|
26
|
-
this.path = path;
|
|
27
|
-
}
|
|
28
|
-
record(task) {
|
|
29
|
-
const file = this.readFile();
|
|
30
|
-
file.tasks[task.service_execution_id] = {
|
|
31
|
-
...task,
|
|
32
|
-
updated_at: new Date().toISOString(),
|
|
33
|
-
};
|
|
34
|
-
this.writeFile(file);
|
|
35
|
-
}
|
|
36
|
-
clear(serviceExecutionID) {
|
|
37
|
-
const file = this.readFile();
|
|
38
|
-
if (!file.tasks[serviceExecutionID])
|
|
39
|
-
return;
|
|
40
|
-
delete file.tasks[serviceExecutionID];
|
|
41
|
-
this.writeFile(file);
|
|
42
|
-
}
|
|
43
|
-
// Observe the latest stage without deleting history; terminal stages stop
|
|
44
|
-
// the task from counting as resumable.
|
|
45
|
-
observe(serviceExecutionID, stage, resumeCommand) {
|
|
46
|
-
const file = this.readFile();
|
|
47
|
-
const existing = file.tasks[serviceExecutionID];
|
|
48
|
-
if (existing && existing.stage === stage && (resumeCommand === undefined || existing.resume_command === resumeCommand))
|
|
49
|
-
return;
|
|
50
|
-
file.tasks[serviceExecutionID] = {
|
|
51
|
-
service_execution_id: serviceExecutionID,
|
|
52
|
-
...(existing?.service_id ? { service_id: existing.service_id } : {}),
|
|
53
|
-
stage,
|
|
54
|
-
...(resumeCommand ?? existing?.resume_command ? { resume_command: resumeCommand ?? existing.resume_command } : {}),
|
|
55
|
-
updated_at: new Date().toISOString(),
|
|
56
|
-
};
|
|
57
|
-
this.writeFile(file);
|
|
58
|
-
}
|
|
59
|
-
// Tasks paused behind login/quota gates, newest first. Terminal or
|
|
60
|
-
// completed tasks never count as "still waiting for login".
|
|
61
|
-
pausedTasks() {
|
|
62
|
-
const file = this.readFile();
|
|
63
|
-
return Object.values(file.tasks)
|
|
64
|
-
.filter((task) => PAUSED_STAGES.has(task.stage) && !TERMINAL_STAGES.has(task.stage))
|
|
65
|
-
.sort((left, right) => right.updated_at.localeCompare(left.updated_at));
|
|
66
|
-
}
|
|
67
|
-
readFile() {
|
|
68
|
-
if (!existsSync(this.path))
|
|
69
|
-
return { schema_version: "itpay.task_journal.v1", tasks: {} };
|
|
70
|
-
try {
|
|
71
|
-
const parsed = JSON.parse(readFileSync(this.path, "utf8"));
|
|
72
|
-
if (parsed.schema_version !== "itpay.task_journal.v1" || !parsed.tasks || typeof parsed.tasks !== "object") {
|
|
73
|
-
return { schema_version: "itpay.task_journal.v1", tasks: {} };
|
|
74
|
-
}
|
|
75
|
-
return parsed;
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
return { schema_version: "itpay.task_journal.v1", tasks: {} };
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
writeFile(file) {
|
|
82
|
-
// Bound the journal: keep only the 50 most recent task records.
|
|
83
|
-
const entries = Object.entries(file.tasks).sort((left, right) => right[1].updated_at.localeCompare(left[1].updated_at));
|
|
84
|
-
file.tasks = Object.fromEntries(entries.slice(0, 50));
|
|
85
|
-
mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
86
|
-
const tmp = `${this.path}.${process.pid}.tmp`;
|
|
87
|
-
writeFileSync(tmp, JSON.stringify(file), { mode: 0o600 });
|
|
88
|
-
renameSync(tmp, this.path);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
@@ -1,162 +0,0 @@
|
|
|
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 use the same entry point via itpay sell.",
|
|
5
|
-
"topic": "rail-booking",
|
|
6
|
-
"title": "Railway Query, Planning, and Ticket Booking",
|
|
7
|
-
"purpose": "One railway route from understanding the request to saved results, booking, protected checkout, order status and refunds.",
|
|
8
|
-
"when_to_use": [
|
|
9
|
-
"Any railway schedule, planning or ticket request; read this topic once before the first railway action."
|
|
10
|
-
],
|
|
11
|
-
"goal_and_overview": {
|
|
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
|
-
"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 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
|
-
},
|
|
16
|
-
"flow_at_a_glance": [
|
|
17
|
-
{
|
|
18
|
-
"step": "search Exact",
|
|
19
|
-
"command": "itpay services run itpay-rail-exact --input-json <query-file> --json",
|
|
20
|
-
"returns": "one station-pair query execution, resolved scope and either useful results or a same-execution wait action",
|
|
21
|
-
"next": "follow the current next command when still working; compare results and stop if the request is satisfied"
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"step": "search Smart",
|
|
25
|
-
"command": "itpay services run itpay-rail-smart --input-json <query-file> --json",
|
|
26
|
-
"returns": "one broader planning execution, observed coverage and either useful results or a same-execution wait action",
|
|
27
|
-
"next": "follow the current next command when still working; compare results and stop if the request is satisfied"
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"step": "read Exact result",
|
|
31
|
-
"command": "itpay services read-result <query_execution_id> --json",
|
|
32
|
-
"returns": "saved station-pair rows with detail.command; detail returns booking_template",
|
|
33
|
-
"next": "Read enough candidates for the user objective. Only a purchase delegation permits using the chosen template."
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"step": "read Smart result",
|
|
37
|
-
"command": "itpay services read-result <query_execution_id> --snapshot <snapshot_id> --json",
|
|
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. Selecting a journey does not lock inventory; preserve whole-journey purchase. No unsupported ground distance/time estimates."
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"step": "book",
|
|
43
|
-
"command": "itpay services run itpay-rail-booking --input-json <booking-file> --json",
|
|
44
|
-
"returns": "a separate booking execution using the returned selection token, seat code and party size",
|
|
45
|
-
"next": "follow its review or waiting action on that same booking execution"
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"step": "review",
|
|
49
|
-
"command": "itpay services next <booking_execution_id> --json",
|
|
50
|
-
"returns": "current review revision, material terms and a non-executable confirm_booking input_template",
|
|
51
|
-
"next": "submit the returned actor-type human template only after genuine consent; reuse an unchanged explicit consent for code-only correction"
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
"step": "checkout",
|
|
55
|
-
"command": "use the returned next or handoff for the same booking execution",
|
|
56
|
-
"returns": "an official protected payment entry",
|
|
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
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"step": "aftercare",
|
|
61
|
-
"command": "itpay order <order_id> --json",
|
|
62
|
-
"returns": "authoritative payment and ticket state",
|
|
63
|
-
"next": "use orders-refunds for the same order when needed"
|
|
64
|
-
}
|
|
65
|
-
],
|
|
66
|
-
"understand_before_search": [
|
|
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
|
-
"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
|
-
"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
|
-
"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.",
|
|
71
|
-
"If login, captcha or network blocks the short lookup, stop without repeated troubleshooting or browser installation and state the facts still unknown. Clear double-station endpoints can use Exact to verify direct trains; do not choose Smart merely because a transfer seems probable. A known transfer requirement or supported multi-station/place comparison can use Smart; genuinely unclear place meaning needs human clarification.",
|
|
72
|
-
"Date verification is required; the ItPay query for the actual date can perform it. A 12306 visit is not a mandatory extra call for every order. Valid Smart journeys and credentials do not require routine Exact rechecks of each leg.",
|
|
73
|
-
"Each side independently means area, place or station, giving all nine combinations. An area is not a same-name station; a specific station stays locked until the human consents to changing it. Smart also supports two locked stations when transfers are needed.",
|
|
74
|
-
"Distance alone does not establish a transfer requirement. One observed route through a station does not make it mandatory; a failed short lookup or incomplete search cannot establish that all direct trains are absent.",
|
|
75
|
-
"Unclear meaning that changes region/scope needs one key question, using known context first. Clear meaning with unknown coordinates can use the complete name and mark location verification pending. Use maps/browser for place identity; 12306/同程 for rail directions, 携程 as needed. Do not require a residential location to be researched on 12306. No fabricated coordinates or passenger/payment data in notes."
|
|
76
|
-
],
|
|
77
|
-
"choose_search": {
|
|
78
|
-
"exact": "Known or credible resolved station pair, direct trains for one station pair and date only. A zero result does not mean the whole city has no train. An area delegation may permit checking another evidence-based pair within that original area; a human-specified station stays locked until they consent to changing that endpoint.",
|
|
79
|
-
"smart": "Use Smart for a reliably established transfer direction or explicit transfer need, broad station comparison, or a precise place needing nearby stations and ground evidence. Preserve each endpoint as area/place/station; a probable transfer alone is not a basis for Smart. Fastest does not automatically mean max_transfers=0.",
|
|
80
|
-
"scope": "Exact cannot claim a citywide or all-transfer optimum. Smart gives its observed scope and missing evidence; its first useful options may be shown before expansion finishes."
|
|
81
|
-
},
|
|
82
|
-
"input_and_expected_result": {
|
|
83
|
-
"exact": {
|
|
84
|
-
"command": "itpay services run itpay-rail-exact --input-json <query-file> --json",
|
|
85
|
-
"example": {
|
|
86
|
-
"origin": "<已核实出发站>",
|
|
87
|
-
"destination": "<已核实到达站>",
|
|
88
|
-
"travel_date": "<用户出行日期 YYYY-MM-DD>"
|
|
89
|
-
},
|
|
90
|
-
"contract": "Only origin, destination and travel_date are accepted. Use station names, not an undocumented station-code field. Filter time, seat and quantity against returned rows. Date is illustrative; use the actual requested queryable date."
|
|
91
|
-
},
|
|
92
|
-
"smart": {
|
|
93
|
-
"command": "itpay services run itpay-rail-smart --input-json <query-file> --json",
|
|
94
|
-
"example": {
|
|
95
|
-
"endpoints": {
|
|
96
|
-
"origin": {
|
|
97
|
-
"kind": "area",
|
|
98
|
-
"text": "<用户出发市县镇完整名称>"
|
|
99
|
-
},
|
|
100
|
-
"destination": {
|
|
101
|
-
"kind": "station",
|
|
102
|
-
"text": "<已核实到达站名>"
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
"travel_date": "<用户出行日期 YYYY-MM-DD>"
|
|
106
|
-
},
|
|
107
|
-
"contract": "The current published input_schema is authoritative: use endpoints only when it exposes endpoints. Each side independently accepts kind area/place/station and a full text; fill kind when supported by endpoint understanding; omit it only when the object meaning is clear but its category genuinely awaits verification, explicitly recorded in the brief. An unknown city is not resolved by omitting kind. A complete named place is legal; coordinates are optional. Area centers choose nearby stations without claiming door-to-door ground travel; place uses verified POI/coordinates when available; station locks exactly one verified railway station and does not mean the traveler is already there. All nine origin/destination combinations follow this same rule. Province-only areas need a narrower location. Never mix endpoints with legacy origin/destination or send a station lock/confirmed flag. Adult passengers is an integer 1–5. arrive_before maps to arrive_by; conflicting aliases are rejected. preference_context is unsupported."
|
|
108
|
-
},
|
|
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
|
-
"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. 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
|
-
},
|
|
113
|
-
"select_and_book": {
|
|
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.",
|
|
115
|
-
"input": "Copy the complete current booking_template.input_example; retain passenger count, every ordered token and the returned seat_type code matched to seat_name. Never use a seat display name as the code, mix selections/selection/legs or guess missing credentials. Technical JSON errors are repaired by the Agent using current input_schema/input_errors/template, preserving original user requirements; ask the human only for a genuinely missing business choice.",
|
|
116
|
-
"command": "itpay services run itpay-rail-booking --input-json <booking-file> --json",
|
|
117
|
-
"identity": "Passenger name, ID and phone are entered on the official protected checkout page, not in query or booking JSON.",
|
|
118
|
-
"whole_journey": "Use the returned complete ticket plan booking_template: ordered selections (2–3 legs) from one owner/snapshot/revision/plan, or selection for one leg. One booking, one refreshed whole quote, one checkout, one order and one payment; each leg issues separately without connection or joint-seat guarantees. Raw legs are single-leg compatibility only; never hand-compose multi-leg legs when complete selections are missing or expired. Do not combine different plans or choose per-leg payment by default. Historical details without a whole-journey template do not prove whole-journey support."
|
|
119
|
-
},
|
|
120
|
-
"review_and_checkout": {
|
|
121
|
-
"review": "Copy the returned complete interaction.input_template.input, current revision and notice. For a whole journey, each ordered leg has its own seat_type and an object preference per passenger; default auto, never null/string arrays. Use known choices and confirm only missing genuine terms consent or material changes. Set accept_non_guaranteed=true only after real consent; delegation or choosing a train alone is not terms consent. The false default is protection, not a broken template; if consent is missing, ask the returned review.consent_question once. Seat/berth placement requests differ from the whole-journey separate-issuance and partial-outcome terms; use the current notice.",
|
|
122
|
-
"equivalent_correction": "If only seat code spelling changes while train, actual seat class, party, price and terms are identical, reuse an existing explicit human approval. Material draft changes or missing approval require human confirmation. Reuse accepted original endpoints, no-seat, overnight and incomplete-coverage facts when unchanged; never silently substitute the destination.",
|
|
123
|
-
"checkout": "Present the complete itinerary, total and single official handoff: one payment, separate issuance. The human enters one passenger set on that page; verified account notification email is reused there and can be changed for this order. Do not ask or guess traveler identity/email in chat. Use the returned checkout payment deadline, not an estimated duration. Handoff is not payment, and payment is not all legs issued."
|
|
124
|
-
},
|
|
125
|
-
"continue_and_aftercare": {
|
|
126
|
-
"same_execution": "Read the same booking/checkout/order. Report issued only when every leg is confirmed issued; partial or unknown outcomes keep the same order and successful tickets, reconcile before retry, and explain the affected leg. No automatic rebuy or full-refund promise.",
|
|
127
|
-
"saved_vs_live": "A saved snapshot remains historical evidence after expiry. Real-time booking or quote credentials may expire and require the documented fresh validation path; rereading an old token does not renew it.",
|
|
128
|
-
"refund": "Read itpay docs show orders-refunds --json and the current order policy. Railway supplier cancellation/return and platform refund have separate facts; a refund request is not completed railway return."
|
|
129
|
-
},
|
|
130
|
-
"exceptional_branches": {
|
|
131
|
-
"login_required": "Use itpay auth login --json, complete the official handoff, then execute the returned recovery command for the same paused query. Login returning a command does not execute it.",
|
|
132
|
-
"rate_limited": "Respect returned retry_after and continue the same execution.",
|
|
133
|
-
"booking_review_invalid": "Read the same booking review and use the returned seat code; reuse real consent only under the strict equivalent-correction rule.",
|
|
134
|
-
"booking_review_changed": "Read the current revision and compare material terms; confirm new terms only when changed.",
|
|
135
|
-
"selection_stale": "Saved result stays readable, but follow the current owner action for new live inventory and quote evidence; do not assume reread refreshes the token.",
|
|
136
|
-
"unknown_paid_outcome": "Read the same checkout/order before any mutation. Never repeat payment or create a new order on an unknown outcome.",
|
|
137
|
-
"lock": "Use the CLI returned device repair-lock recovery only for a lock proven stale; preserve active owners and identity.",
|
|
138
|
-
"empty": "Report verified scope, failures and missing evidence separately. Do not guess a new date or station as a generic repair. Explain checked scope first, then propose at most two evidence-backed directions. Ask consent before changing a locked endpoint; after consent copy the complete original input and change only the accepted side in a new query.",
|
|
139
|
-
"ground_evidence_required": "If trains were found but a precise address deadline cannot be verified without ground or station-exit evidence, report the checked rail scope and missing fact. Ask for a precise location or verifiable connection fact; do not expand rail station pairs by default or call it no trains. Preserve the original place and compare its nearby candidates; only actual user consent may replace the destination with a station.",
|
|
140
|
-
"dependency_unavailable": "Map faults are external failures, not empty location candidates. Preserve resolved endpoints and original conditions. For a retryable wait, respect dependency.retry_at and execute the returned same-execution run command; never restart or sleep inside the CLI. Daily quota, permission or IP failures need service maintenance, not a different address.",
|
|
141
|
-
"partial": "Report saved usable routes together with checked scope and the precise gap. Unqueried hubs, geographic evidence gaps and budget limits are not proof of no trains. Use the returned action only; do not expand station pairs by default. A proposal to relax a condition is allowed, but requires real consent before a changed-scope query.",
|
|
142
|
-
"geographic_notice": "Geographic deviation is a ranking or explanation notice. Current eligibility and booking conditions come from verified dates, connections, inventory, prices, authorization and required ground evidence; do not reject a returned eligible train merely by geometric ratio.",
|
|
143
|
-
"selection_invalid": "Use only the current finite reason and executable recovery. An input constraint error requires the current complete template; stored_selection_inconsistent is a server problem that extra parameters cannot repair. With no recovery, stop: never append hidden fields, recreate a purchase or blame local simulation without evidence.",
|
|
144
|
-
"location_evidence_required": "A unique verified place continues directly; real multiple candidates use only the returned choice action and preserve the original place. Area-only or conflicting identity evidence is not proof the place does not exist: read affected_endpoint, original place, limited resolved_scope and finite reason; use maps/browser for missing evidence or ask only necessary human facts. A terminated query cannot resume: only after new evidence, fill the complete non-executable new_query_template and create once, preserving the other side/date/party/preferences/locks. Do not switch place to area, fabricate reuse_from or repeatedly get a terminal execution. Only a real dependency wait resumes the same execution. Correcting a proven classification within unchanged intent needs no repeated consent; changing destination or enlarging scope needs real consent."
|
|
145
|
-
},
|
|
146
|
-
"search_terms": [
|
|
147
|
-
"rail",
|
|
148
|
-
"railway",
|
|
149
|
-
"train",
|
|
150
|
-
"ticket",
|
|
151
|
-
"火车",
|
|
152
|
-
"火车票",
|
|
153
|
-
"高铁",
|
|
154
|
-
"车票",
|
|
155
|
-
"车次",
|
|
156
|
-
"seat",
|
|
157
|
-
"booking",
|
|
158
|
-
"rail-booking"
|
|
159
|
-
],
|
|
160
|
-
"reading_and_writing": "Use the formal Skill Follow one envelope rules for direct text/file reading, truncation and small JSON input; no parsing scripts.",
|
|
161
|
-
"restore_old_execution": "Only when resuming an existing v4 execution, follow its own declared legacy schema; new queries use the current three-endpoint contract."
|
|
162
|
-
}
|