@itpay/cli 2.0.12 → 2.0.13

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.
@@ -10,6 +10,11 @@ export async function requirePlatformCompatibility(backend) {
10
10
  throw new HttpError(426, {
11
11
  code: "client_upgrade_required",
12
12
  message: `CLI ${CLI_VERSION} contract ${API_CONTRACT_REVISION} is incompatible with platform ${platform.platform_revision} contract ${platform.api_contract_revision} (minimum CLI ${platform.minimum_cli_version}, maximum major ${platform.maximum_cli_major})`,
13
+ minimum_cli_version: platform.minimum_cli_version,
14
+ maximum_cli_major: platform.maximum_cli_major,
15
+ platform_revision: platform.platform_revision,
16
+ api_contract_revision: platform.api_contract_revision,
17
+ upgrade_command: `npm install -g @itpay/cli@${platform.minimum_cli_version}`,
13
18
  }, "CLI is incompatible with the active ItPay platform release");
14
19
  }
15
20
  function compareVersions(left, right) {
@@ -25,7 +25,7 @@ export function writeCommandEnvelope(value, options = {}) {
25
25
  return;
26
26
  }
27
27
  out(`${qualified.status}\n`);
28
- const facts = "result" in qualified ? qualified.result : qualified.error;
28
+ const facts = "error" in qualified ? qualified.error : qualified.result;
29
29
  if (options.plainResult) {
30
30
  for (const line of options.plainResult)
31
31
  out(`${line}\n`);
@@ -34,6 +34,11 @@ export function writeCommandEnvelope(value, options = {}) {
34
34
  for (const [key, fact] of Object.entries(facts)) {
35
35
  out(`${key}: ${typeof fact === "string" ? fact : JSON.stringify(fact)}\n`);
36
36
  }
37
+ if ("error" in qualified && qualified.result) {
38
+ for (const [key, fact] of Object.entries(qualified.result)) {
39
+ out(`${key}: ${typeof fact === "string" ? fact : JSON.stringify(fact)}\n`);
40
+ }
41
+ }
37
42
  }
38
43
  if ("handoff" in qualified && qualified.handoff) {
39
44
  for (const [key, fact] of Object.entries(qualified.handoff)) {
@@ -417,13 +422,7 @@ function buildServiceGuidance(input) {
417
422
  }
418
423
  }
419
424
  else if ((input.providerCalled || execution.next_action === "select_candidate") && (input.resultItems?.length ?? 0) === 0) {
420
- nextActions.push({
421
- id: "start_refined_search",
422
- label: "Start a new execution with a more specific company name",
423
- command: `itpay services start ${execution.service_id}`,
424
- requires_human: true,
425
- reason: "No candidates were found. This execution is finished; use one new execution per new keyword.",
426
- });
425
+ // Terminal empty result: a later explicit human request starts a new flow.
427
426
  }
428
427
  else if (needsHumanSelection(execution, resultItem)) {
429
428
  nextActions.push({
@@ -433,12 +432,6 @@ function buildServiceGuidance(input) {
433
432
  requires_human: true,
434
433
  reason: "Do not choose a candidate without explicit human confirmation.",
435
434
  });
436
- nextActions.push({
437
- id: "start_another_search",
438
- label: "Search another company in a new execution",
439
- command: `itpay services start ${execution.service_id}`,
440
- reason: "This execution has completed its one keyword lookup; do not reuse it for another keyword.",
441
- });
442
435
  }
443
436
  else if (prePurchase) {
444
437
  const action = {
@@ -13,7 +13,7 @@ const INSTRUCTIONS = {
13
13
  "codex-cli": "在 Codex CLI 中始终传这个 Agent Type;付款交接只应显示在用户可见终端。",
14
14
  "claude-code-desktop": "在 Claude Code Desktop 中始终传这个 Agent Type;付款时把返回的二维码和链接实际展示到当前对话。",
15
15
  "claude-code-cli": "在 Claude Code CLI 中始终传这个 Agent Type;付款交接只应显示在用户可见终端。",
16
- workbuddy: "在 WorkBuddy 中始终传这个 Agent TypeCheckout 返回 qr_image_url 时,必须按当次 instruction 调用 present_files 在右侧打开二维码,不要检查本地二维码文件。",
16
+ workbuddy: "在 WorkBuddy 中始终传这个 Agent Type。运行会持久化 ~/.itpay-v3 的 ItPay Bash 命令前,把 Shell 工具参数 dangerouslyDisableSandbox 设为 true;用户拒绝权限时停止,不要切换 Node、删除 identity 或处理 lock。Checkout 返回 qr_image_url 时,按当次 instruction 调用 present_files 在右侧打开二维码,不要检查本地二维码文件。",
17
17
  };
18
18
  export function runInstall(target, options = {}) {
19
19
  if (!target || target === "list") {
@@ -106,13 +106,14 @@ function invokedEnvelope(response, requestedCapability, capabilities, input) {
106
106
  const baseResult = {
107
107
  service_execution_id: response.execution.service_execution_id,
108
108
  capability_id: requestedCapability.capability_id,
109
+ query: input,
109
110
  items,
110
111
  ...(quota ? { quota } : {}),
111
112
  };
112
113
  let status = items.length > 0 ? "result_ready" : "no_result";
113
114
  let instruction = items.length > 0
114
115
  ? "向用户展示编号和 safe_payload;若候选列表已满足用户目标,在此停止。仅在用户明确选择并希望继续时,才在当前 Execution 提交对应 rank。"
115
- : "Provider 已返回空结果;不要重放当前 execution,按下一步恢复。";
116
+ : `没有找到与“${queryText(input)}”匹配的结果。向用户展示本次为 0 个结果并停止。不要修改、缩短或猜测其他输入;只有用户明确提供新输入后,才能启动新的查询。`;
116
117
  let next = null;
117
118
  if (response.effective_quota?.exhausted) {
118
119
  status = "quota_exhausted";
@@ -148,9 +149,7 @@ function invokedEnvelope(response, requestedCapability, capabilities, input) {
148
149
  };
149
150
  }
150
151
  else if (items.length === 0) {
151
- next = response.provider_called
152
- ? { command: `itpay services start ${response.execution.service_id}`, reason: "为新的服务输入启动新 execution" }
153
- : { command: `itpay services next ${response.execution.service_execution_id} --json`, reason: "读取服务端恢复动作" };
152
+ next = null;
154
153
  }
155
154
  return {
156
155
  value: { status, result: baseResult, instruction, next, recovery: [] },
@@ -162,11 +161,18 @@ function serviceResultPlainLines(result) {
162
161
  `service_execution_id: ${String(result.service_execution_id)}`,
163
162
  `capability_id: ${String(result.capability_id)}`,
164
163
  ];
164
+ const items = result.items;
165
+ const query = result.query;
166
+ if (query) {
167
+ for (const [key, value] of Object.entries(query))
168
+ lines.push(`${key}: ${String(value)}`);
169
+ }
170
+ if (items.length === 0)
171
+ lines.push("results: 0");
165
172
  if (result.quota)
166
173
  lines.push(`quota: ${JSON.stringify(result.quota)}`);
167
174
  if (result.checkout)
168
175
  lines.push(`checkout: ${JSON.stringify(result.checkout)}`);
169
- const items = result.items;
170
176
  if (items.length > 0) {
171
177
  lines.push("items:");
172
178
  for (const item of items) {
@@ -178,6 +184,10 @@ function serviceResultPlainLines(result) {
178
184
  }
179
185
  return lines;
180
186
  }
187
+ function queryText(input) {
188
+ const value = Object.values(input).find((item) => typeof item === "string" && item.trim() !== "");
189
+ return typeof value === "string" ? value : JSON.stringify(input);
190
+ }
181
191
  function missingRequiredInput(schema, input) {
182
192
  return requiredInputFields(schema).filter((field) => {
183
193
  if (!(field in input) || input[field] === null || input[field] === undefined)
@@ -695,12 +705,12 @@ function servicesNextEnvelope(model) {
695
705
  ? selection
696
706
  ? "Agent-visible 搜索已完成。向用户展示 items 中的编号、title 和 safe_payload,然后停止;不要调用 read-result。只有用户明确选择候选并要求继续时,才执行 next.command。"
697
707
  : "这是当前 Graph 步骤对应的交付;结果已可供 Agent 使用,只使用 safe_payload。"
698
- : "Agent-visible 交付已完成但没有结果项;不要调用 read-result 或重放当前 execution。",
708
+ : "Agent-visible 交付已完成但有 0 个结果。向用户展示空结果并停止;不要调用 read-result、重放当前 Execution、修改输入或创建新 Execution。",
699
709
  next: selection ? {
700
710
  command: `itpay services action ${execution.service_execution_id} --action select_candidate --actor-type human --status approved --candidate <rank> --json`,
701
711
  reason: "仅在用户明确选择后锁定来源候选",
702
712
  } : null,
703
- recovery: items.length > 0 ? [] : [{ command: `itpay services get ${execution.service_execution_id} --json`, reason: "检查交付时间线" }],
713
+ recovery: [],
704
714
  };
705
715
  }
706
716
  if (deliveryMode === "vault_artifact") {
@@ -24,7 +24,7 @@ export function runSkillShow(name, options = {}) {
24
24
  result: { skill: BUYER_SKILL, content },
25
25
  instruction: agentType
26
26
  ? agentType === "workbuddy"
27
- ? "完整读取并遵守 Skill;保持 workbuddy、同一 Node/CLI launcher 和可持久写入 Device 状态的执行权限。内部诊断不要逐步转述给用户。"
27
+ ? "完整读取并遵守 Skill;保持 workbuddy 和同一 Node/CLI launcher。运行会持久化 ~/.itpay-v3 ItPay Bash 命令前,把 Shell 工具参数 dangerouslyDisableSandbox 设为 true;用户拒绝权限时停止,不要切换 Node、删除 identity 或处理 lock。"
28
28
  : `完整读取并遵守 Skill;当前 Agent Type 是 ${agentType},后续命令保持不变。`
29
29
  : "完整读取并遵守 Skill;先如实选择当前运行环境对应的 Agent Type。",
30
30
  next: agentType
package/dist/src/main.js CHANGED
@@ -95,10 +95,15 @@ function reportCLIError(error, contract) {
95
95
  error.code === "client_compatibility_headers_required" ||
96
96
  error.code === "platform_release_unavailable" ||
97
97
  (error.status === 404 && error.code === "unknown_error"));
98
+ const requiredCLIVersion = incompatible && error instanceof HttpError && /^\d+\.\d+\.\d+$/.test(error.payload?.minimum_cli_version ?? "")
99
+ ? error.payload.minimum_cli_version
100
+ : undefined;
98
101
  const backendInternal = error instanceof HttpError && error.status === 500 && error.code === "internal_error";
99
102
  const providerConnectionUnavailable = error instanceof HttpError && error.code === "provider_connection_unavailable";
100
103
  const providerTemporary = error instanceof HttpError && error.code === "provider_temporarily_unavailable";
101
104
  const providerRejected = error instanceof HttpError && error.code === "provider_rejected";
105
+ const providerInputRejected = error instanceof HttpError && error.code === "provider_input_rejected";
106
+ const providerContractMismatch = error instanceof HttpError && error.code === "provider_contract_mismatch";
102
107
  const capabilityInputInvalid = error instanceof HttpError && error.code === "capability_input_invalid";
103
108
  const deviceRecovery = deviceError ? [{
104
109
  command: "itpay skill show itpay-buyer --json",
@@ -124,21 +129,50 @@ function reportCLIError(error, contract) {
124
129
  code: incompatible ? "backend_contract_incompatible" : commandError?.code ?? (error instanceof HttpError ? error.code : stateError?.code ?? deviceError?.code ?? contract?.code ?? "command_failed"),
125
130
  message: error instanceof Error ? error.message : String(error),
126
131
  },
132
+ ...(requiredCLIVersion ? {
133
+ result: {
134
+ current_cli_version: CLI_VERSION,
135
+ required_cli_version: requiredCLIVersion,
136
+ },
137
+ } : error instanceof HttpError && error.payload?.service_execution_id ? {
138
+ result: {
139
+ service_execution_id: error.payload.service_execution_id,
140
+ provider_called: error.payload.provider_called === true,
141
+ ...(error.payload.effective_quota ? {
142
+ quota: {
143
+ remaining: error.payload.effective_quota.remaining,
144
+ limit: error.payload.effective_quota.limit,
145
+ },
146
+ } : {}),
147
+ },
148
+ } : {}),
127
149
  instruction: incompatible
128
- ? "立即向用户报告 error.message 并结束本次任务。不要运行任何其他 itpay、npm、which、device、docs、cart、orders 或 services 命令;不要寻找、安装或切换其他 CLI。只有运营明确提供兼容 CLI 后,才能在新的任务中重新开始。"
150
+ ? requiredCLIVersion
151
+ ? "当前 CLI 与 Backend 合约不兼容。停止所有 ItPay 业务命令;只执行 recovery.command,将 @itpay/cli 更新到 Backend 指定的精确版本。安装完成后确认 itpay --version 与 result.required_cli_version 完全一致,再重新运行 readyz。不要安装 latest、猜测版本、切换 Agent Type 或删除 Device 身份。"
152
+ : "立即向用户报告 error.message 并结束本次任务。Backend 未提供可验证的兼容 CLI 版本;不要运行其他 ItPay 或 npm 命令,不要猜测版本、切换 Agent Type 或删除 Device 身份。"
129
153
  : backendInternal
130
154
  ? "Backend 内部故障;立即停止并向用户报告。不要重试、检查或删除 Device 身份、创建替代 Execution、切换 Backend,或尝试 quote、checkout、cart、buy、pay 等付费路径。"
131
155
  : providerConnectionUnavailable
132
156
  ? "Provider 请求未发出,预留免费额度已释放;当前 Execution 已失败。立即向用户报告 error.message 并停止,不要自动重试、不要继续同一 Execution,也不要进入任何付费路径。只有运营确认连接恢复且用户明确要求重新查询后,才启动新的 Service Execution。"
133
157
  : providerTemporary
134
- ? "上游服务暂时不可用;向用户逐字报告 error.message 并停止,不要自动重试。请求是否可能计费以 Backend 的消费事实为准;只有用户明确要求再次查询后,才可按 next/recovery 重新开始。"
135
- : providerRejected
136
- ? "上游拒绝了本次输入;向用户逐字报告 error.message 并停止,不要重试同一 Execution。Provider 已收到响应,本次请求可能计费;只有用户提供修正后的输入后,才可按 next/recovery 继续。"
137
- : capabilityInputInvalid
138
- ? "输入未通过本地校验,上游尚未被调用且用户额度未变化。向用户逐字报告 error.message 并停止,不要原样重试或运行其他恢复命令。用户提供修正后的输入后,继续使用当前未结束的 Execution。"
139
- : commandError?.instruction ?? authorizationInstruction ?? contract?.instruction ?? "检查命令参数后重试。",
158
+ ? "上游服务暂时不可用;向用户逐字报告 error.message result.quota 并停止,不要自动重试、不要创建新 Execution。只有用户之后明确提出新请求,才可重新开始。"
159
+ : providerInputRejected
160
+ ? `Provider 明确拒绝了该输入:${error instanceof Error ? error.message : String(error)}。请向用户报告 error.message result.quota 并停止。不要自行修改输入、不要重试、不要创建新 Execution;只有用户明确提供新输入后才能重新查询。`
161
+ : providerContractMismatch
162
+ ? "Provider 响应与已发布契约不一致。这不是用户输入问题。立即停止,不要修改输入、不要重试、不要创建新 Execution,也不要进入付费路径;向用户报告平台故障和 result.quota。"
163
+ : providerRejected
164
+ ? "Provider 拒绝了本次请求,但未声明这是输入错误;向用户逐字报告 error.message 和 result.quota 并停止。不要修改输入、不要重试、不要创建新 Execution。"
165
+ : capabilityInputInvalid
166
+ ? "输入未通过本地校验,上游尚未被调用且用户额度未变化。向用户逐字报告 error.message 并停止,不要原样重试或运行其他恢复命令。用户提供修正后的输入后,继续使用当前未结束的 Execution。"
167
+ : commandError?.instruction ?? authorizationInstruction ?? contract?.instruction ?? "检查命令参数后重试。",
140
168
  next: null,
141
- recovery: incompatible || backendInternal || providerConnectionUnavailable || providerTemporary || providerRejected || capabilityInputInvalid ? [] : commandError?.recovery ?? (stateError ? stateRecovery : deviceError ? deviceRecovery : identityRecovery ? httpRecovery : contract?.recovery ?? []),
169
+ recovery: incompatible
170
+ ? requiredCLIVersion
171
+ ? [{ command: `npm install -g @itpay/cli@${requiredCLIVersion}`, reason: "安装 Backend 指定的兼容 CLI 版本" }]
172
+ : []
173
+ : backendInternal || providerConnectionUnavailable || providerTemporary || providerInputRejected || providerContractMismatch || providerRejected || capabilityInputInvalid
174
+ ? []
175
+ : commandError?.recovery ?? (stateError ? stateRecovery : deviceError ? deviceRecovery : identityRecovery ? httpRecovery : contract?.recovery ?? []),
142
176
  }, {
143
177
  ...(contract?.jsonOutput !== undefined ? { jsonOutput: contract.jsonOutput } : {}),
144
178
  output: (text) => { process.stderr.write(text); },
@@ -10,8 +10,8 @@ import { declaredAgentType } from "./agent_type.js";
10
10
  import { DeviceAuthority } from "./device_authority.js";
11
11
  import { OperationJournal } from "./operation_journal.js";
12
12
  export const DEFAULT_BASE_URL = "https://app.itpay.ai";
13
- export const CLI_VERSION = "2.0.12";
14
- export const API_CONTRACT_REVISION = "sha256:ea7d716f444a01e9d0109f12a53b56ce16d5ab813795a18a7a63f24a8c752073";
13
+ export const CLI_VERSION = "2.0.13";
14
+ export const API_CONTRACT_REVISION = "sha256:7f4c40b082292bf823631bcd37d452f4a8537153e30636d5eb3a2b24a77ce602";
15
15
  const CART_SESSION_DEFAULT_DIR = ".itpay-v3";
16
16
  const CART_SESSION_FILENAME = "cart.json";
17
17
  const OPERATION_JOURNAL_FILENAME = "operations.json";
@@ -32,6 +32,8 @@
32
32
  ],
33
33
  "agent_rules": [
34
34
  "Install with npm install -g @itpay/cli.",
35
+ "If backend_contract_incompatible includes result.required_cli_version, stop all ItPay business commands and run only the exact npm install recovery returned by the CLI; never replace its version with latest.",
36
+ "After upgrading, require itpay --version to equal result.required_cli_version before running readyz again; never change Agent Type or Device identity to recover compatibility.",
35
37
  "Use the default https://app.itpay.ai API unless an environment override is deliberate.",
36
38
  "Use one exact type: codex-desktop, codex-cli, claude-code-desktop, claude-code-cli, or workbuddy.",
37
39
  "One local private key is reused, but each exact Backend API base URL has its own server registration and quota lineage.",
@@ -83,7 +83,7 @@ itpay install [target] [--json]
83
83
  | `codex-cli` | `terminal` | 只在用户可见终端展示付款交接。 |
84
84
  | `claude-code-desktop` | `claude-code` | 桌面对话必须实际展示二维码和付款链接。 |
85
85
  | `claude-code-cli` | `terminal` | 只在用户可见终端展示付款交接。 |
86
- | `workbuddy` | `plain-chat` | Checkout 返回 `qr_image_url` 时,按当次 instruction 调用 `present_files` 在右侧打开二维码;不检查本地二维码文件。 |
86
+ | `workbuddy` | `plain-chat` | 会持久化 `~/.itpay-v3` 的 Bash 调用先设 Shell 工具参数 `dangerouslyDisableSandbox=true`;拒绝权限时停止,不切换 Node、不删除 identity、不处理 lock。Checkout 返回 `qr_image_url` 时按 instruction 调用 `present_files`。 |
87
87
 
88
88
  显式 `--host` 可以在后续 commerce 命令覆盖默认 Host,但不会改变 Agent Type 或设备归属。
89
89
 
@@ -33,6 +33,32 @@ itpay readyz [--json]
33
33
 
34
34
  连接失败时返回 `backend_unavailable`,要求核对 `ITPAY_BACKEND_URL` 后重试同一命令,不得继续下单。
35
35
 
36
+ CLI 已取得 Backend 的兼容性合同、但当前版本或 contract hash 不匹配时,返回一个可执行且版本固定的恢复动作:
37
+
38
+ ```json
39
+ {
40
+ "status": "error",
41
+ "error": {
42
+ "code": "backend_contract_incompatible",
43
+ "message": "CLI 2.0.13 contract sha256:client is incompatible with platform v3.example contract sha256:server (minimum CLI 2.0.14, maximum major 2)"
44
+ },
45
+ "result": {
46
+ "current_cli_version": "2.0.13",
47
+ "required_cli_version": "2.0.14"
48
+ },
49
+ "instruction": "当前 CLI 与 Backend 合约不兼容。停止所有 ItPay 业务命令;只执行 recovery.command,将 @itpay/cli 更新到 Backend 指定的精确版本。安装完成后确认 itpay --version 与 result.required_cli_version 完全一致,再重新运行 readyz。不要安装 latest、猜测版本、切换 Agent Type 或删除 Device 身份。",
50
+ "next": null,
51
+ "recovery": [
52
+ {
53
+ "command": "npm install -g @itpay/cli@2.0.14",
54
+ "reason": "安装 Backend 指定的兼容 CLI 版本"
55
+ }
56
+ ]
57
+ }
58
+ ```
59
+
60
+ 只允许使用 Backend 返回的 `minimum_cli_version` 生成精确 npm 版本。兼容性合同不可用、缺少合法版本或仅有无法验证的错误文本时,仍须停止且不得猜测安装版本。
61
+
36
62
  ## Agent Type / Host
37
63
 
38
64
  本命令不渲染 Host 内容。若已声明 Agent Type,`result.agent_type` 会确认该类型,且返回的 Skill 命令保留同一 `--agent-type`;未声明时 Skill 会先引导 `install`。
@@ -5,7 +5,7 @@
5
5
  调用当前 phase 允许的非付费 Agent-visible capability。输入先按 capability schema 校验,校验失败不得迁移 execution 或记录 Provider 已调用。
6
6
 
7
7
  **上游:** `services start/next` 明确返回 invoke。
8
- **下游:** 候选结果、人工 action、付费 Quote 或新 execution。
8
+ **下游:** 候选结果、人工 action 或付费 Quote。一次 invoke 没有结果或返回 Provider 错误时必须停止;只有用户之后明确提供新输入,才启动新的 execution。
9
9
 
10
10
  ## 语法与参数
11
11
 
@@ -33,9 +33,31 @@ itpay services invoke <service_execution_id> --capability <capability_id>
33
33
  }
34
34
  ```
35
35
 
36
- ## 无结果与额度耗尽
36
+ ## 无结果
37
37
 
38
- 无结果时明确说明 Provider 已返回空结果,并根据服务端 graph 决定重试同一 execution 或启动新 execution。额度耗尽时,普通单 Execution 流程返回完整的 `services checkout` 单项快捷命令;`services quote -> cart add --quote -> buy --cart` 只用于用户明确要求把多个独立 Execution 合并付款的高级流程。
38
+ Provider 已收到请求但没有匹配项时,该 invocation 成功完成、提交一次真实额度消费并返回权威剩余额度。CLI 不提供可自动执行的下一步:Agent 必须展示 0 个结果并停止,不得缩短、改写或猜测输入。
39
+
40
+ ```json
41
+ {
42
+ "status": "no_result",
43
+ "result": {
44
+ "service_execution_id": "<id>",
45
+ "capability_id": "company_name_suggestion",
46
+ "query": { "keyword": "北京赢在未来公司" },
47
+ "items": [],
48
+ "quota": { "remaining": 1, "limit": 3 }
49
+ },
50
+ "instruction": "没有找到与“北京赢在未来公司”匹配的结果。向用户展示本次为 0 个结果并停止。不要修改、缩短或猜测其他输入;只有用户明确提供新输入后,才能启动新的查询。",
51
+ "next": null,
52
+ "recovery": []
53
+ }
54
+ ```
55
+
56
+ 文本输出只包含 execution、capability、keyword、`results: 0`、quota 和同一条 instruction;不得附带 Provider raw payload、Operation ID 或调试信息。
57
+
58
+ ## 额度耗尽
59
+
60
+ 额度耗尽时,普通单 Execution 流程返回完整的 `services checkout` 单项快捷命令;`services quote -> cart add --quote -> buy --cart` 只用于用户明确要求把多个独立 Execution 合并付款的高级流程。
39
61
 
40
62
  ```json
41
63
  {
@@ -73,13 +95,58 @@ itpay services invoke <service_execution_id> --capability <capability_id>
73
95
  "code": "provider_connection_unavailable",
74
96
  "message": "provider request was not sent; reserved quota was released"
75
97
  },
98
+ "result": {
99
+ "service_execution_id": "<id>",
100
+ "provider_called": false,
101
+ "quota": { "remaining": 3, "limit": 3 }
102
+ },
76
103
  "instruction": "Provider 请求未发出,预留免费额度已释放;当前 Execution 已失败。立即向用户报告 error.message 并停止,不要自动重试、不要继续同一 Execution,也不要进入任何付费路径。只有运营确认连接恢复且用户明确要求重新查询后,才启动新的 Service Execution。",
77
104
  "next": null,
78
105
  "recovery": []
79
106
  }
80
107
  ```
81
108
 
82
- 该终态不允许 CLI 猜测网络修复、重复 invoke 或转入购买。连接恢复后也不能复用失败 Execution;必须同时满足“运营已确认恢复”和“用户明确要求再次查询”,才创建新 Execution。
109
+ `result.quota` 是释放预留后的权威余额,不是请求前预留时的临时值。该终态不允许 CLI 猜测网络修复、重复 invoke 或转入购买。连接恢复后也不能复用失败 Execution;必须同时满足“运营已确认恢复”和“用户明确要求再次查询”,才创建新 Execution。
110
+
111
+ ## Provider 输入、临时和契约错误
112
+
113
+ Provider 已收到请求时,Backend 返回同一 Execution 的权威调用和额度事实。CLI 不读取 raw payload,也不自行推断错误种类。
114
+
115
+ 明确输入错误:
116
+
117
+ ```json
118
+ {
119
+ "status": "error",
120
+ "error": { "code": "provider_input_rejected", "message": "输入的名称不合法" },
121
+ "result": {
122
+ "service_execution_id": "<id>",
123
+ "provider_called": true,
124
+ "quota": { "remaining": 0, "limit": 3 }
125
+ },
126
+ "instruction": "Provider 明确拒绝了该输入:输入的名称不合法。请向用户报告 error.message 和 result.quota 并停止。不要自行修改输入、不要重试、不要创建新 Execution;只有用户明确提供新输入后才能重新查询。",
127
+ "next": null,
128
+ "recovery": []
129
+ }
130
+ ```
131
+
132
+ 契约错误:
133
+
134
+ ```json
135
+ {
136
+ "status": "error",
137
+ "error": { "code": "provider_contract_mismatch", "message": "provider response did not match the published contract" },
138
+ "result": {
139
+ "service_execution_id": "<id>",
140
+ "provider_called": true,
141
+ "quota": { "remaining": 0, "limit": 3 }
142
+ },
143
+ "instruction": "Provider 响应与已发布契约不一致。这不是用户输入问题。立即停止,不要修改输入、不要重试、不要创建新 Execution,也不要进入付费路径;向用户报告平台故障和 result.quota。",
144
+ "next": null,
145
+ "recovery": []
146
+ }
147
+ ```
148
+
149
+ `provider_temporarily_unavailable` 同样必须停止且不得自动重试。输入错误、临时错误和契约错误都不得返回 `next.command` 或 recovery 命令;新的 Provider 请求只允许来自用户后续明确提出的新输入。
83
150
 
84
151
  ## Agent Type / Host
85
152
 
@@ -14,4 +14,4 @@ itpay [--agent-type <agent_type>] skill show itpay-buyer [--json]
14
14
 
15
15
  Skill 是操作和安全合同,不是服务端业务状态。执行时仍以每个命令当前 envelope 的 `result`、`instruction` 和 `next` 为准。
16
16
 
17
- 所有 Agent Type 使用相同的 JSON 外壳、字段和命令参数。`workbuddy` 只在 `instruction` 中额外强调保持同一 Node/CLI launcher、持久写权限和减少内部诊断转述;它不改变输入或输出 schema。
17
+ 所有 Agent Type 使用相同的 JSON 外壳、字段和命令参数。`workbuddy` 只在 `instruction` 中额外要求:写入 `~/.itpay-v3` 前把 Shell 工具参数 `dangerouslyDisableSandbox` 设为 `true`;权限被拒绝时停止,不切换 Node、不删除 identity、不处理 lock。它不改变输入或输出 schema。
@@ -70,6 +70,9 @@ next: <one command>
70
70
  - 用户口头说“已付款”不是支付事实;仅服务端 `verified` 状态有效。
71
71
  - `agent_device_session_required` 只触发一次 session 续期和同请求重试;第二次失败必须返回,不得循环或换身份。
72
72
  - `device_state_unwritable` 表示当前运行环境不能可靠持久化 `~/.itpay-v3`;保持同一 Agent Type 与 CLI/Node launcher,恢复 Host 写权限后重试原命令,不得手工造 lock、删除身份或换运行时碰运气。
73
+ - WorkBuddy 在运行会持久化 `~/.itpay-v3` 的 ItPay Bash 命令前,必须把 Shell 工具参数 `dangerouslyDisableSandbox` 设为 `true`。若用户拒绝该权限,命令尚未执行:停止并报告,不得切换 Node、删除 identity、手工处理 lock 或换 Agent Type。
74
+ - `provider_input_rejected` 只表示 Provider 明确声明输入无效;`provider_contract_mismatch` 表示响应无法按已发布契约解释,绝不能归咎于用户输入。两者都必须停止且没有自动 recovery。
75
+ - `backend_contract_incompatible` 只有在 Backend 返回合法 `minimum_cli_version` 时才能提供一个精确的 `npm install -g @itpay/cli@<version>` recovery。不得使用 `latest`、解析 message 猜版本或继续任何业务命令;升级后必须先用 `itpay --version` 核对完全一致,再重新运行 `readyz`。
73
76
 
74
77
  ## Instruction 模板
75
78
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itpay/cli",
3
- "version": "2.0.12",
3
+ "version": "2.0.13",
4
4
  "description": "ItPay CLI for V3 checkout, payment, order, refund, and agent-facing render flows.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,6 +13,7 @@ Use the CLI as the only ItPay control surface. Never recreate API calls or hardc
13
13
  ## Envelope Rule
14
14
 
15
15
  - Keep one honest Agent Type, CLI/Node launcher, and Host permission context. Supported types: `codex-desktop`, `codex-cli`, `claude-code-desktop`, `claude-code-cli`, `workbuddy`.
16
+ - WorkBuddy: before any ItPay Bash command that persists `~/.itpay-v3`, set the Shell tool parameter `dangerouslyDisableSandbox` to `true`. If the human denies that permission, the command did not run: stop and report it. Never switch Node, delete identity, edit locks, or change Agent Type.
16
17
  - Windows, tasks, chats, processes, and model sessions are not new Agents. Never rotate Agent Type or identity to reset quota.
17
18
  - Read `status` and `result` as facts, follow `instruction`, and execute at most the applicable `next.command`; use `recovery` only when it cannot continue.
18
19
  - `next.command` is not unconditional. If the result satisfies the user's goal, present useful facts and stop; never dump the whole envelope.
@@ -30,6 +31,8 @@ itpay --agent-type <agent_type> readyz --json
30
31
 
31
32
  Follow the returned `next.command`. After typed `readyz`, read this complete Skill again, then continue to Catalog.
32
33
 
34
+ If `backend_contract_incompatible` returns `result.required_cli_version`, stop every ItPay business command. Run only its exact `npm install -g @itpay/cli@<version>` recovery; never substitute `latest`. Confirm `itpay --version` exactly matches the required version, then restart with typed `readyz`. Never change Agent Type or Device identity to bypass compatibility.
35
+
33
36
  ## Identity And Sessions
34
37
 
35
38
  - One local Ed25519 private key represents this installation. Never expose, copy, delete, or rotate it during normal recovery.
@@ -91,6 +94,8 @@ Reuse the same Execution and Checkout. Never start another Execution, create ano
91
94
 
92
95
  `provider_connection_unavailable` is a terminal exception: Backend confirms no Provider request was sent and releases the reservation, then fails that Execution. Stop with no recovery command. Only after an operator confirms connectivity is restored and the human explicitly asks to query again may you start a new Execution.
93
96
 
97
+ `no_result` is a completed Provider call with zero items. Show the query, zero results, and the returned quota, then stop. Never shorten, rewrite, or guess another input. `provider_input_rejected`, `provider_temporarily_unavailable`, and `provider_contract_mismatch` are also terminal for the current request: report the exact safe message and quota facts, run no recovery command, and wait for a new explicit human request.
98
+
94
99
  ## Safety
95
100
 
96
101
  - Never invent service, capability, item, Checkout, Order, grant, or refund IDs.