@itpay/cli 2.1.22 → 2.1.29

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.
@@ -1,7 +1,7 @@
1
1
  import { HttpError } from "../client/http.js";
2
- import { requiresTarget } from "../state/client_context.js";
2
+ import { requiresTarget, validateContext } from "../state/client_context.js";
3
3
  import { formatMoney } from "../render/output.js";
4
- import { CommandContractError, writeCommandEnvelope } from "./guidance.js";
4
+ import { CommandContractError, shellArgument, writeCommandEnvelope } from "./guidance.js";
5
5
  import { buildVaultHandoff } from "./vault_handoff.js";
6
6
  function outputOptions(options, plainResult) {
7
7
  return {
@@ -19,21 +19,21 @@ export async function runVaultList(backend, input) {
19
19
  const value = await backend.listBuyerVaultArtifacts(input);
20
20
  writeCommandEnvelope({
21
21
  status: value.items.length ? "vault_listed" : "no_vault_artifacts",
22
- result: { items: value.items, next_cursor: value.next_cursor || null },
22
+ result: { items: value.items.map(item => ({ ...item, amount: formatMoney(item.amount_minor, item.currency), reader: { command: vaultReadCommand(item.artifact_ref, [], input), reason: "读取这一已购内容" } })), next_cursor: value.next_cursor || null },
23
23
  instruction: value.items.length
24
- ? "用编号、服务名称、内容主体、购买时间、金额和订单号说明匹配结果,不要向用户显示内部内容标识。一个精确匹配可按用户原始查看意图继续读取;多个匹配必须让用户选择。"
24
+ ? "用编号、服务名称、内容主体、购买时间、金额和订单号说明匹配结果,不要向用户显示内部内容标识。一个精确匹配可按用户原始查看意图继续读取;已有任务可明确匹配时读取对应行;只有真实歧义才让用户选择。"
25
25
  : "当前账号没有匹配的已购内容。向用户说明没有找到,不要猜测内容标识、自动购买或发起新的服务查询。",
26
- next: null,
26
+ next: value.next_cursor ? { command: `itpay vault list --limit ${input.limit}${input.query ? ` --query ${shellArgument(input.query)}` : ""} --cursor ${shellArgument(value.next_cursor)}${contextFlags(input)} --json`, reason: "读取同一筛选的下一页" } : null,
27
27
  recovery: [],
28
- }, outputOptions(input, value.items.map((item, index) => `${index + 1}. ${item.service_title}${item.subject_label ? ` · ${item.subject_label}` : ""} · ${formatMoney(item.amount_minor, item.currency)} · ${item.purchased_at} · ${item.order_code} · ${item.order_status}`)));
28
+ }, outputOptions(input));
29
29
  }
30
30
  catch (error) {
31
31
  if (error instanceof HttpError && error.code === "vault_authorization_required") {
32
32
  writeCommandEnvelope({
33
33
  status: "human_authorization_required",
34
34
  result: { intent: "list_purchased_content", query: input.query ?? "" },
35
- instruction: `需要用户确认一次身份和只读权限。执行 next.command 生成官方入口,不要声称链接已经创建;用户完成后重新运行原始 vault list 命令。${accessContextInstruction(input)}`,
36
- next: { command: vaultAccessCommand(undefined, input), reason: "创建一次账号读取授权" }, recovery: [],
35
+ instruction: `需要用户确认一次身份和只读权限。按当前动作或待填模板生成官方入口,不要声称链接已经创建;用户完成后重新运行原始 vault list 命令。${accessContextInstruction(input)}`,
36
+ ...vaultAccessGuidance(undefined, input), recovery: [],
37
37
  }, outputOptions(input));
38
38
  return;
39
39
  }
@@ -41,6 +41,9 @@ export async function runVaultList(backend, input) {
41
41
  }
42
42
  }
43
43
  export async function runVaultAccess(backend, artifactRef, options) {
44
+ const issue = validateContext(options.host, options.target);
45
+ if (issue)
46
+ throw new CommandContractError(issue.code, issue.message, "先补齐当前可信会话的真实宿主和目标;尚未创建授权请求。", [], { schema_version: "itpay.interaction.v1", stage: "input_required", input_template: { command: vaultAccessCommand(artifactRef, options), required_input: issue.code === "target_required" ? ["target"] : ["host"], executable: false } });
44
47
  const value = await backend.createVaultAccessRequest(artifactRef
45
48
  ? { purpose: "artifact_reveal", artifact_ref: artifactRef }
46
49
  : { purpose: "account_window" });
@@ -90,23 +93,23 @@ export async function runVaultRead(backend, artifactRef, sections, options) {
90
93
  : value.status === "result_preparing"
91
94
  ? "这份已购内容仍在准备。稍后只重试同一 read,不要重新授权、购买或调用 Provider。"
92
95
  : "这份已购内容当前不可用。停止,不要重试、重新购买或绕过退款锁。",
93
- next: null, recovery: [],
96
+ next: value.status === "result_preparing" ? { command: vaultReadCommand(artifactRef, normalized, options), reason: "稍后读取同一内容及原sections;不重新授权或购买" } : null, recovery: [],
94
97
  }, outputOptions(options));
95
98
  }
96
99
  catch (error) {
97
100
  if (error instanceof HttpError && error.code === "artifact_authorization_required") {
98
101
  writeCommandEnvelope({
99
102
  status: "human_authorization_required", result: { artifact_ref: artifactRef },
100
- instruction: `这份内容需要用户单独确认读取权限。执行 next.command 生成一次官方入口;用户完成后重新运行原始 read,不要重复创建授权请求。${accessContextInstruction(options)}`,
101
- next: { command: vaultAccessCommand(artifactRef, options), reason: "创建一次内容读取授权" }, recovery: [],
103
+ instruction: `这份内容需要用户单独确认读取权限。按当前动作或待填模板生成一次官方入口;用户完成后重新运行原始 read,不要重复创建授权请求。${accessContextInstruction(options)}`,
104
+ ...vaultAccessGuidance(artifactRef, options), recovery: [],
102
105
  }, outputOptions(options));
103
106
  return;
104
107
  }
105
108
  if (error instanceof HttpError && error.code === "vault_authorization_required") {
106
109
  writeCommandEnvelope({
107
110
  status: "human_authorization_required", result: { artifact_ref: artifactRef },
108
- instruction: `账号读取授权已缺失或过期。执行 next.command 生成一次官方入口;用户完成后重新运行原始 read。${accessContextInstruction(options)}`,
109
- next: { command: vaultAccessCommand(undefined, options), reason: "创建一次账号读取授权" }, recovery: [],
111
+ instruction: `账号读取授权已缺失或过期。按当前动作或待填模板生成一次官方入口;用户完成后重新运行原始 read。${accessContextInstruction(options)}`,
112
+ ...vaultAccessGuidance(undefined, options), recovery: [],
110
113
  }, outputOptions(options));
111
114
  return;
112
115
  }
@@ -121,7 +124,7 @@ export function vaultAccessCommand(artifactRef, options) {
121
124
  const host = options.host ?? (openClaw ? "<host>" : undefined);
122
125
  if (host)
123
126
  parts.push("--host", host);
124
- const target = options.target ?? (openClaw && (!options.host || requiresTarget(options.host)) ? "<target>" : undefined);
127
+ const target = options.target ?? (((openClaw && !options.host) || (options.host && requiresTarget(options.host))) ? "<target>" : undefined);
125
128
  if (target)
126
129
  parts.push("--target", shellArgument(target));
127
130
  parts.push("--json");
@@ -132,10 +135,14 @@ export function accessContextInstruction(options) {
132
135
  ? " 将 <host> 和 <target> 替换为当前可信 OpenClaw 会话的真实值,不要照抄占位符或猜测目标。"
133
136
  : "";
134
137
  }
135
- function shellArgument(value) {
136
- if (value.startsWith("<") && value.endsWith(">"))
137
- return value;
138
- if (/^[\p{L}\p{N}._:=/-]+$/u.test(value))
139
- return value;
140
- return `'${value.replaceAll("'", `'"'"'`)}'`;
138
+ function contextFlags(options) {
139
+ return `${options.host ? ` --host ${options.host}` : ""}${options.target ? ` --target ${shellArgument(options.target)}` : ""}`;
140
+ }
141
+ function vaultReadCommand(artifact, sections, options) {
142
+ return `itpay vault read --artifact ${shellArgument(artifact)}${sections.map(section => ` --section ${shellArgument(section)}`).join("")}${contextFlags(options)} --json`;
143
+ }
144
+ export function vaultAccessGuidance(artifact, options) {
145
+ const command = vaultAccessCommand(artifact, options);
146
+ const missing = [...(options.agentType === "openclaw" && !options.host ? ["host"] : []), ...(options.host && requiresTarget(options.host) && !options.target || options.agentType === "openclaw" && !options.host && !options.target ? ["target"] : [])];
147
+ return missing.length ? { next: null, interaction: { schema_version: "itpay.interaction.v1", stage: "input_required", input_template: { command, required_input: missing, executable: false } } } : { next: { command, reason: "创建一次官方只读授权入口;用户完成后恢复原reader" } };
141
148
  }
package/dist/src/main.js CHANGED
@@ -25,7 +25,7 @@ import { runCancelRefund, runGetRefund, runListRefunds, runRefund, runWatchRefun
25
25
  import { runCartAdd, runCartAddQuoteServer, runCartAddServer, runCartAbandonServer, runCartClear, runCartNext, runCartRemove, runCartRemoveServer, runCartShow, runCartShowServer, } from "./commands/cart.js";
26
26
  import { CommandContractError, errorRecoveryActions, printErrorRecovery, writeCommandEnvelope } from "./commands/guidance.js";
27
27
  import { runDocsList, runDocsShow, runDocsSearch } from "./commands/docs.js";
28
- import { runInstall } from "./commands/install.js";
28
+ import { INSTALL_AGENT_TYPES, runInstall } from "./commands/install.js";
29
29
  import { runSkillShow } from "./commands/skill.js";
30
30
  import { runNext } from "./commands/next.js";
31
31
  import { runVaultAccess, runVaultList, runVaultRead } from "./commands/vault.js";
@@ -139,17 +139,12 @@ function reportCLIError(error, contract) {
139
139
  const providerContractMismatch = error instanceof HttpError && error.code === "provider_contract_mismatch";
140
140
  const capabilityInputInvalid = error instanceof HttpError && error.code === "capability_input_invalid";
141
141
  const deviceKeyResettable = Boolean(deviceError && (deviceError.code === "agent_device_key_rotated" ||
142
- deviceError.code === "agent_device_key_conflict" ||
143
- (deviceError.enrollmentFailed && deviceError.status === 500)));
142
+ deviceError.code === "agent_device_key_conflict"));
144
143
  const deviceRecovery = deviceError ? [
145
144
  {
146
145
  command: "itpay skill show itpay --json",
147
146
  reason: "读取 ItPay 身份边界;该错误需要用户或运营恢复 Backend 登记,不能通过换类型或删除本地身份绕过",
148
147
  },
149
- ...(deviceKeyResettable ? [{
150
- command: "itpay device reset-key --confirm-key-reset --json",
151
- reason: "服务端拒绝以当前私钥完成设备登记;生成全新 Ed25519 密钥并重新登记(旧设备身份在服务端保留为孤儿,不影响新身份和额度谱系)",
152
- }] : []),
153
148
  ] : [];
154
149
  const stateRecovery = stateError ? [{
155
150
  command: "itpay skill show itpay --json",
@@ -167,10 +162,10 @@ function reportCLIError(error, contract) {
167
162
  ? "CLI 已自动续期并重试同一请求一次,仍被拒绝;停止重试,不要切换 Agent Type 或旋转身份。"
168
163
  : deviceError?.code === "agent_device_revoked"
169
164
  ? "Backend 已撤销当前 Device 登记;CLI 没有自动创建替代身份。停止重试并请用户或运营恢复登记。"
170
- : deviceKeyResettable && deviceError?.status === 500
171
- ? "服务端未能完成设备登记。先原样重试一次原命令:服务端会把已登记的同一公钥幂等挂回原设备。若仍返回 internal_error(Backend 未含该修复),执行 itpay device reset-key --confirm-key-reset 生成全新密钥后重试。"
165
+ : deviceError?.enrollmentFailed && deviceError.status === 500
166
+ ? "服务端未能完成设备登记;保持原密钥和Agent Type,说明系统故障,待服务端恢复后继续原任务。不要重置身份。"
172
167
  : deviceKeyResettable
173
- ? "服务端记录显示当前设备私钥已不再有效(已轮换或与既有登记冲突)。执行 itpay device reset-key --confirm-key-reset 生成全新密钥并重新登记。"
168
+ ? "服务端拒绝当前设备私钥登记(轮换或冲突)。停止自动恢复;换钥会放弃旧身份,旧执行访问不会迁移。仅用户或运营明确决定放弃旧身份后,按 device reset-key 帮助执行。"
174
169
  : deviceError
175
170
  ? "Device 身份验证失败;停止重试,不要切换 Agent Type、删除状态或旋转私钥。"
176
171
  : undefined;
@@ -229,6 +224,7 @@ function reportCLIError(error, contract) {
229
224
  ? "移除 ITPAY_BACKEND_URL 使用正式环境,或按当前测试目标准确设置为 https://sandbox.itpay.ai 或 https://dev.itpay.ai。不要通过切换环境规避当前错误。"
230
225
  : commandError?.instruction ?? authorizationInstruction ?? contract?.instruction ?? "检查命令参数后重试。",
231
226
  next: null,
227
+ ...(commandError?.interaction ? { interaction: commandError.interaction } : {}),
232
228
  recovery: incompatible
233
229
  ? requiredCLIVersion
234
230
  ? [distributionUpgradeAction(requiredCLIVersion)]
@@ -238,7 +234,7 @@ function reportCLIError(error, contract) {
238
234
  : backendOverrideError ? [] : commandError?.recovery ?? (stateError ? stateRecovery : lockError ? lockRecovery : deviceError ? deviceRecovery : identityRecovery ? httpRecovery : contract?.recovery ?? []),
239
235
  }, {
240
236
  ...(contract?.jsonOutput !== undefined ? { jsonOutput: contract.jsonOutput } : backendOverrideError || lockError || stateError ? { jsonOutput: process.argv.includes("--json") } : {}),
241
- output: (text) => { process.stderr.write(text); },
237
+ output: (text) => { process.stdout.write(text); },
242
238
  });
243
239
  process.exitCode = 1;
244
240
  return;
@@ -323,7 +319,7 @@ for (const action of ["login", "status"]) {
323
319
  // and non-JSON output is human-readable instead of a raw JSON dump.
324
320
  const envelope = {
325
321
  status: String(result.status ?? "error"),
326
- result: (result.result ?? result),
322
+ result: (result.result ?? Object.fromEntries(Object.entries(result).filter(([key]) => !["status", "instruction", "next", "recovery", "handoff"].includes(key)))),
327
323
  ...(result.handoff ? { handoff: result.handoff } : {}),
328
324
  instruction: typeof result.instruction === "string" ? result.instruction : "按返回状态继续。",
329
325
  next: (result.next ?? null),
@@ -422,8 +418,11 @@ deviceCmd
422
418
  .action(async (options) => {
423
419
  const config = loadConfig();
424
420
  try {
421
+ if (!config.agentType || !INSTALL_AGENT_TYPES.some(type => type === config.agentType)) {
422
+ throw new CommandContractError("agent_type_required", "supported Agent Type required before key reset", "先按真实宿主配置正式支持的Agent Type;尚未修改本地密钥。", [{ command: "itpay install --json", reason: "读取当前宿主配置" }]);
423
+ }
425
424
  if (!options.confirmKeyReset) {
426
- throw new CommandContractError("key_reset_confirmation_required", "--confirm-key-reset is required", "仅当服务端拒绝以当前私钥完成设备登记(internal_error、agent_device_key_rotated 或 agent_device_key_conflict)时使用;会放弃本地设备身份并重新登记,旧设备在服务端保留为孤儿。", [{ command: "itpay docs show identity-and-sessions --json", reason: "检查适用边界" }]);
425
+ throw new CommandContractError("key_reset_confirmation_required", "--confirm-key-reset is required", "仅在用户或运营明确决定放弃旧身份后使用;会丢弃本地私钥,旧执行访问不会迁移。internal_error 需服务端恢复,不能换钥。", [{ command: "itpay docs show identity-and-sessions --json", reason: "检查适用边界" }]);
427
426
  }
428
427
  const reset = await new DeviceAuthority({
429
428
  baseURL: config.baseURL,
@@ -437,9 +436,9 @@ deviceCmd
437
436
  private_key_preserved: false,
438
437
  server_side_device: "orphaned_under_previous_key",
439
438
  },
440
- instruction: "本地设备私钥已重置;下一次需要设备身份的命令会以全新 Ed25519 密钥重新登记并获得新的额度谱系。服务端旧设备记录保留为孤儿;如需清理请走运营流程。",
439
+ instruction: "本地设备私钥已重置;下一次需要设备身份的命令会以全新 Ed25519 密钥重新登记;这不能恢复旧身份的订单或用于刷新额度。服务端旧设备记录保留为孤儿;如需清理请走运营流程。",
441
440
  next: {
442
- command: `itpay --agent-type ${config.agentType ?? "<agent_type>"} services list --limit 1 --json`,
441
+ command: `itpay --agent-type ${config.agentType} services list --limit 1 --json`,
443
442
  reason: "用无业务写入的签名请求完成新密钥的重新登记",
444
443
  },
445
444
  recovery: [],
@@ -1477,12 +1476,18 @@ services
1477
1476
  .argument("<service_execution_id>")
1478
1477
  .requiredOption("--capability <capability_id>")
1479
1478
  .option("--input <key=value>", "redacted input summary", collectOption, [])
1479
+ .option("--input-json <file>", "JSON object containing the complete capability input")
1480
1480
  .option("--json", "output JSON")
1481
1481
  .action(async (serviceExecutionID, options) => {
1482
1482
  const config = loadConfig();
1483
1483
  const backend = newBackendClient(config);
1484
1484
  try {
1485
- await runServicesInvoke(backend, config, serviceExecutionID, options.capability, parseKeyValueList(options.input), { jsonOutput: Boolean(options.json) });
1485
+ const recovery = [{ command: `itpay services next ${serviceExecutionID} --json`, reason: "读取当前动作要求" }];
1486
+ if (options.inputJson && options.input.length > 0) {
1487
+ throw new CommandContractError("capability_input_invalid", "--input and --input-json cannot be combined", "嵌套输入用 --input-json <file>,扁平输入用 --input key=value,二选一;本次未调用供应商。", recovery);
1488
+ }
1489
+ const input = options.inputJson ? readInputJsonObject(options.inputJson, "capability_input_invalid", recovery) : parseKeyValueList(options.input);
1490
+ await runServicesInvoke(backend, config, serviceExecutionID, options.capability, input, { jsonOutput: Boolean(options.json) });
1486
1491
  }
1487
1492
  catch (error) {
1488
1493
  reportCLIError(error, {
@@ -1687,22 +1692,28 @@ services
1687
1692
  });
1688
1693
  services
1689
1694
  .command("read-result")
1690
- .description("Read a human-granted service result for this agent")
1695
+ .description("Read saved railway results or human-granted service content")
1691
1696
  .argument("<service_execution_id>")
1692
- .option("--snapshot <snapshot_id>", "rail.progressive.v2: read one journey from a committed planning snapshot")
1697
+ .option("--snapshot <snapshot_id>", "Smart: saved snapshot catalog; add --journey for a journey detail. Exact: saved station-pair rows without --snapshot")
1693
1698
  .option("--journey <journey_id>", "rail.progressive.v2: journey id to read")
1699
+ .option("--offset <offset>", "saved journey offset", Number, 0)
1700
+ .option("--limit <limit>", "rows per page (1-20): Exact 20, Smart 3", Number)
1701
+ .option("--all", "read every saved journey page")
1694
1702
  .option("--json", "output JSON instead of terminal text")
1695
1703
  .action(async (serviceExecutionID, options) => {
1696
1704
  const config = loadConfig();
1697
1705
  const backend = newBackendClient(config);
1698
1706
  try {
1699
- await runServicesReadResult(backend, serviceExecutionID, { jsonOutput: Boolean(options.json), snapshot: options.snapshot, journey: options.journey });
1707
+ await runServicesReadResult(backend, serviceExecutionID, { jsonOutput: Boolean(options.json), snapshot: options.snapshot, journey: options.journey,
1708
+ offset: options.offset, limit: options.limit, all: Boolean(options.all) });
1700
1709
  }
1701
1710
  catch (error) {
1702
1711
  reportCLIError(error, {
1703
1712
  jsonOutput: Boolean(options.json),
1704
- code: "agent_access_denied",
1705
- instruction: "请用户在订单页面重新授权;不要使用开发者权限绕过授权或退款锁。",
1713
+ code: error instanceof HttpError && error.code === "agent_access_denied" ? "agent_access_denied" : "service_result_read_failed",
1714
+ instruction: error instanceof HttpError && error.code === "agent_access_denied"
1715
+ ? "请用户在订单页面重新授权;不要使用开发者权限绕过授权或退款锁。"
1716
+ : "读取同一服务的已保存结果;若快照已不可读,先查看该服务当前状态和有效动作。",
1706
1717
  recovery: [{ command: `itpay services next ${serviceExecutionID} --json`, reason: "检查交付模式和 grant 状态" }],
1707
1718
  });
1708
1719
  }
@@ -59,11 +59,11 @@ const TERMINAL_SESSION_STATES = {
59
59
  },
60
60
  cancelled: {
61
61
  status: 'auth_cancelled',
62
- instruction: '用户在官方页取消了授权。如仍需登录,重新运行 itpay auth login。',
62
+ instruction: '用户在官方页取消了授权;停止登录。只有用户明确重新要求授权时才发起新的登录。',
63
63
  },
64
64
  denied: {
65
65
  status: 'auth_denied',
66
- instruction: '官方授权被拒绝。重新运行 itpay auth login 生成新请求。',
66
+ instruction: '官方授权被拒绝;停止登录,不自动创建新请求。只有用户明确重新要求授权时才重新登录。',
67
67
  },
68
68
  };
69
69
  class AuthRequestError extends Error {
@@ -289,7 +289,7 @@ async function accountAuth(action, baseURL, env, fetcher, backend) {
289
289
  status: terminal.status,
290
290
  result: { dashboard_auth_session_id: state.sessionID, stage: progress.status },
291
291
  instruction: terminal.instruction,
292
- next: { command: 'itpay auth login --json', reason: '重新发起官方授权' },
292
+ next: ['denied', 'cancelled'].includes(progress.status) ? null : { command: 'itpay auth login --json', reason: '需要登录时重新发起官方授权' },
293
293
  recovery: [],
294
294
  };
295
295
  }
@@ -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.22";
18
+ export const CLI_VERSION = "2.1.29";
19
19
  export const API_CONTRACT_REVISION = "sha256:2f9fbcdc5cd0630910abb52671304cc019e348e9a778017ec92ddc3513b3639f";
20
20
  const CART_SESSION_DEFAULT_DIR = ".itpay-v3";
21
21
  const CART_SESSION_FILENAME = "cart.json";
@@ -21,7 +21,7 @@
21
21
  "service_flow": "Explain discovery as an auxiliary identity-confirmation step, including free quota and paid continuation. Present primary_service as the actual paid outcome. Read all values from the manifest.",
22
22
  "variant_selection": "Internal IDs are for the Agent's next command only and are never shown or requested from the human.",
23
23
  "price_note": "amount_minor is in minor units (e.g., 10000 = 100.00 CNY). The CLI formats prices as XX.YY currency automatically.",
24
- "railway": "Railway entries point to itpay docs show rail-booking --json; use its query and booking contracts instead of a generic start→invoke→cart sequence."
24
+ "railway": "Railway entries point to itpay docs show rail-booking; use its query and booking contracts instead of a generic start→invoke→cart sequence."
25
25
  },
26
26
  "agent_rules": [
27
27
  "Use catalog list only when the human asks for a new service, not when they want an earlier purchase.",
@@ -23,7 +23,7 @@
23
23
  "When a previously unseen Agent Type is registered under an existing Device, the CLI skips locally known Agent Instances that the Backend reports as revoked and uses the first remaining valid Instance. It never reactivates a revoked Instance or replaces the Device; if none is valid, registration stops.",
24
24
  "When the Backend reports agent_device_not_found (HTTP 404) for a locally known registration — for example after the Backend registration database was rebuilt — the CLI drops only that Backend's registration and re-enrolls once with the same private key, re-attaching to the same device and quota lineage where the Backend still holds the key.",
25
25
  "A revoked v2 registration requires explicit operator recovery; it is never silently replaced.",
26
- "Re-enrolling a public key the Backend already holds re-attaches to the existing device and quota lineage after proof-of-possession; it never creates a duplicate device. If enrollment verification keeps failing with internal_error, or returns agent_device_key_rotated or agent_device_key_conflict, `itpay device reset-key --confirm-key-reset` is the supported way to start over with a fresh key."
26
+ "Re-enrolling a public key the Backend already holds re-attaches to the existing device and quota lineage after proof-of-possession; it never creates a duplicate device. An internal_error is a server failure: preserve the key and wait for service recovery. If enrollment returns agent_device_key_rotated or agent_device_key_conflict, stop automatic recovery. Only an explicit human/operator decision to abandon the old identity permits reset-key; old execution access does not migrate."
27
27
  ],
28
28
  "local_state_recovery": [
29
29
  "Device state uses an owner-only atomic file replacement and a lock that the CLI can inspect. On a stale lock, run the returned device repair-lock command; it only reclaims a proven stale owner.",
@@ -47,11 +47,6 @@
47
47
  "command": "itpay --agent-type <agent_type> device recover --confirm-backend-reset --json",
48
48
  "success_signal": "only the selected Backend registration is removed and the private key and other Backend registration remain"
49
49
  },
50
- {
51
- "intent": "abandon the local device key after the Backend refuses to complete enrollment with it (persistent internal_error, agent_device_key_rotated, or agent_device_key_conflict)",
52
- "command": "itpay device reset-key --confirm-key-reset --json",
53
- "success_signal": "the local private key is discarded and the next command enrolls as a brand-new device"
54
- },
55
50
  {
56
51
  "intent": "repair only a proven stale local device lock",
57
52
  "command": "itpay device repair-lock --json",
@@ -103,5 +98,6 @@
103
98
  "missing": "auth_session_missing means the cached session is gone. An explicit auth login opens one fresh official session in that call; auth status stays read-only.",
104
99
  "completed_resume": "After login completes, execute the returned command to resume the same paused service execution. Login does not silently run that command.",
105
100
  "path_b": "A buyer who reaches checkout without a login completes identity inside the protected checkout page; never force a separate itpay auth login first."
106
- }
101
+ },
102
+ "key_reset_decision": "Only after explicit user/operator acceptance of abandoning the old identity, consult device reset-key help; this is not automatic error recovery, and old execution access does not migrate."
107
103
  }
@@ -92,5 +92,11 @@
92
92
  "交付失败",
93
93
  "没有收到结果",
94
94
  "能不能退"
95
- ]
95
+ ],
96
+ "rail_notifications": {
97
+ "channels": "Delivery notices reach the checkout-confirmed contact: email always works; a verified mainland SMS recipient is optional and checkout-scoped. The passenger phone, the login phone and the notification recipient are independent facts.",
98
+ "status_read": "Use the current returned reader for ticket state; order summaries do not expose per-leg notification delivery records. Do not infer a sent or delivered notification.",
99
+ "truthfulness": "Provider acceptance is not delivery; an uncertain send outcome is reconciled, not silently retried. A notification failure never changes ticket or refund state; report it as a delivery detail, not an order failure.",
100
+ "unavailable": "If SMS is not configured or a recipient is unverified, email still satisfies the contact policy — never present an unverified phone as enabled."
101
+ }
96
102
  }
@@ -35,7 +35,7 @@
35
35
  "authorization_flow": [
36
36
  "Run the original list, orders, or read command.",
37
37
  "OpenClaw must pass its current trusted host and required target on the original command so the authorization continuation preserves the real presentation destination.",
38
- "If it returns human_authorization_required, run only its next.command once.",
38
+ "If it returns human_authorization_required and context is complete, run its executable next.command once. If next is null or context/consent is missing, fill the returned interaction/input_template first; never invent host, target or consent.",
39
39
  "Make the returned handoff genuinely visible and stop.",
40
40
  "After the human says authorization is complete, rerun the original read command unchanged.",
41
41
  "Never rerun vault access as a status check."
@@ -27,7 +27,7 @@
27
27
  "refund_or_paid_problem": "Resume the known Order or Refund before creating anything new.",
28
28
  "service_feedback": "Finish the original order outcome first, then submit one safe Agent postmortem; include a rating or comment only when the human explicitly gives it.",
29
29
  "ambiguous": "Ask whether the human wants an earlier purchase or a new query before calling ItPay.",
30
- "railway": "Known railway intent: itpay docs show rail-booking --json. Read once, then follow its Exact/Smart route."
30
+ "railway": "Known railway intent: itpay docs show rail-booking. Read once, then follow its Exact/Smart route."
31
31
  },
32
32
  "commands": [
33
33
  {
@@ -39,10 +39,10 @@
39
39
  "agent_rules": [
40
40
  "Use the CLI as the control surface; infer the human's intent before the first command and never hardcode a service workflow.",
41
41
  "Treat result as current facts, instruction as how to use those facts, next as the preferred executable action, and recovery as exceptional paths.",
42
- "Run one state-changing command at a time. Treat next.command as the preferred continuation: execute it unchanged after filling required user data only when the current result has not already satisfied the user's goal.",
42
+ "Run one state-changing command at a time. Treat next.command as the preferred continuation: execute it unchanged only when its required values are already present; fill a non-executable input_template separately only when the current result has not already satisfied the user's goal.",
43
43
  "Keep the same explicit Agent Type through next and recovery commands; same-type windows reuse one Backend Agent Instance.",
44
44
  "When testing sandbox, execute every returned command unchanged so its ITPAY_BACKEND_URL=https://sandbox.itpay.ai prefix is preserved.",
45
- "Use --target only for a Host presentation destination. Put capability business values in --input key=value exactly as required by the schema.",
45
+ "Use --target only for a Host presentation destination. Use --input key=value only for simple scalars; nested objects use a small JSON file with the existing --input-json command.",
46
46
  "Ask the user for required email or contact values and explain their purpose; never invent them.",
47
47
  "Explain useful service facts in ordinary language; never ask the human to run commands or learn internal IDs.",
48
48
  "Normal payment uses the returned ItPay Checkout handoff.",
@@ -106,5 +106,6 @@
106
106
  "rating",
107
107
  "target",
108
108
  "input"
109
- ]
109
+ ],
110
+ "reading_results": "Read normal CLI text directly, or parse the complete --json envelope with the host file reader. If saving stdout to a file, first check the command exit code, then read the whole file with cat or the existing file tool. Keep stderr separate; do not use head, tail, regex or a custom script to recover truncated results. Write complex input as a small JSON file and use --input-json."
110
111
  }