@itpay/cli 2.0.11 → 2.0.12

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 CHANGED
@@ -11,7 +11,7 @@ itpay --agent-type codex-desktop readyz --json
11
11
  # follow next.command: typed skill show, then catalog list
12
12
  ```
13
13
 
14
- The default API is temporarily `https://dev.itpay.ai` during the 2.0.11 beta. Set `ITPAY_BACKEND_URL` only for an intentional override.
14
+ The default API is `https://app.itpay.ai`. Set `ITPAY_BACKEND_URL` only for an intentional environment override.
15
15
 
16
16
  ## Output Contract
17
17
 
@@ -404,7 +404,7 @@ function buildServiceGuidance(input) {
404
404
  command: `itpay services checkout ${execution.service_execution_id} --capability ${capabilityID}${emailRequired ? " --email <email>" : ""} --json`,
405
405
  requires_human: true,
406
406
  reason: emailRequired
407
- ? "Ask the human for their email. It is used to send the protected result claim link; never invent or substitute an address."
407
+ ? deliveryEmailGuidance(checkoutCapability?.delivery_email_purpose)
408
408
  : "This capability returns an agent-visible result after payment and does not require a delivery email.",
409
409
  });
410
410
  }
@@ -487,6 +487,7 @@ function buildServiceGuidance(input) {
487
487
  requires_payment: capability.requires_payment,
488
488
  vault_required: capability.vault_required,
489
489
  delivery_email_required: capability.delivery_email_required,
490
+ delivery_email_purpose: capability.delivery_email_purpose,
490
491
  price_amount_minor: capability.price_amount_minor,
491
492
  price_currency: capability.price_currency,
492
493
  free_quota_limit: capability.free_quota_limit,
@@ -506,6 +507,18 @@ function buildServiceGuidance(input) {
506
507
  : {}),
507
508
  };
508
509
  }
510
+ function deliveryEmailGuidance(purpose) {
511
+ switch (purpose) {
512
+ case "receipt":
513
+ return "Ask the human for their email. It is used to send the order receipt; never invent or substitute an address.";
514
+ case "claim":
515
+ return "Ask the human for their email. It is used to send the protected result claim link; never invent or substitute an address.";
516
+ case "receipt_and_claim":
517
+ return "Ask the human for their email. It is used to send the order receipt and protected result claim link; never invent or substitute an address.";
518
+ default:
519
+ return "Ask the human for the required email and state only the Backend-declared purpose; never invent or substitute an address.";
520
+ }
521
+ }
509
522
  function firstPrePurchaseCapability(capabilities) {
510
523
  return capabilities.find((capability) => capability.agent_visible && !capability.requires_payment)
511
524
  ?? capabilities.find((capability) => capability.agent_visible);
@@ -128,7 +128,7 @@ function invokedEnvelope(response, requestedCapability, capabilities, input) {
128
128
  delivery_email_required: checkoutCapability.delivery_email_required,
129
129
  };
130
130
  const price = capabilityPrice(checkoutCapability);
131
- instruction = purchaseConfirmationInstruction("quota_exhausted", price, checkoutCapability.delivery_email_required);
131
+ instruction = purchaseConfirmationInstruction("quota_exhausted", price, checkoutCapability.delivery_email_required, checkoutCapability.delivery_email_purpose);
132
132
  next = {
133
133
  command: checkoutCommand(response.execution.service_execution_id, checkoutCapability, input),
134
134
  reason: `仅在用户明确同意支付 ${price} 后执行;否则停止`,
@@ -198,17 +198,30 @@ function capabilityPrice(capability) {
198
198
  ? formatMoney(capability.price_amount_minor, capability.price_currency)
199
199
  : "当前发布价格";
200
200
  }
201
- function purchaseConfirmationInstruction(context, price, deliveryEmailRequired, candidateTitle = "") {
201
+ function purchaseConfirmationInstruction(context, price, deliveryEmailRequired, deliveryEmailPurpose, candidateTitle = "") {
202
+ const emailPurpose = deliveryEmailPurposeText(deliveryEmailPurpose);
202
203
  if (context === "quota_exhausted") {
203
204
  return deliveryEmailRequired
204
- ? `免费额度已用完,本次没有调用 Provider,也尚未创建 Quote 或 Checkout。现在只向用户说明:继续当前请求需要支付 ${price},交付还需要用户邮箱;请确认是否购买并提供邮箱。然后停止并等待。用户明确同意并提供真实邮箱前,不要执行 next.command,不要新建 Execution,不要尝试其他 capability、quote、cart、buy、checkout 或 pay 命令。`
205
+ ? `免费额度已用完,本次没有调用 Provider,也尚未创建 Quote 或 Checkout。现在只向用户说明:继续当前请求需要支付 ${price},并提供${emailPurpose};请确认是否购买并提供邮箱。然后停止并等待。用户明确同意并提供真实邮箱前,不要执行 next.command,不要新建 Execution,不要尝试其他 capability、quote、cart、buy、checkout 或 pay 命令。`
205
206
  : `免费额度已用完,本次没有调用 Provider,也尚未创建 Quote 或 Checkout。现在只向用户说明:“继续当前请求需要支付 ${price},是否购买?”然后停止并等待用户明确回复。用户明确同意前,不要执行 next.command,不要新建 Execution,不要尝试其他 capability、quote、cart、buy、checkout 或 pay 命令。`;
206
207
  }
207
208
  const selected = candidateTitle ? `已选择 ${candidateTitle}。` : "当前候选已经确认。";
208
209
  return deliveryEmailRequired
209
- ? `${selected}候选已绑定到当前 Execution,但尚未购买后续服务。现在只向用户说明:继续购买后续服务需要支付 ${price},并提供用于发送交付认领链接的邮箱;请确认是否购买并提供邮箱。然后停止。用户明确同意并提供真实邮箱前,不要执行 next.command,不要创建新 Execution 或 Checkout。`
210
+ ? `${selected}候选已绑定到当前 Execution,但尚未购买后续服务。现在只向用户说明:继续购买后续服务需要支付 ${price},并提供${emailPurpose};请确认是否购买并提供邮箱。然后停止。用户明确同意并提供真实邮箱前,不要执行 next.command,不要创建新 Execution 或 Checkout。`
210
211
  : `${selected}候选已绑定到当前 Execution,但尚未购买后续服务。现在只向用户说明:“继续购买后续服务需要支付 ${price},是否购买?”然后停止。用户明确同意前,不要执行 next.command,不要创建新 Execution 或 Checkout。`;
211
212
  }
213
+ function deliveryEmailPurposeText(purpose) {
214
+ switch (purpose) {
215
+ case "receipt":
216
+ return "用于发送订单收据的真实邮箱";
217
+ case "claim":
218
+ return "用于发送交付认领链接的真实邮箱";
219
+ case "receipt_and_claim":
220
+ return "用于发送订单收据和交付认领链接的真实邮箱";
221
+ default:
222
+ return "服务端声明用途的真实邮箱";
223
+ }
224
+ }
212
225
  function paidContinuation(model, action, input) {
213
226
  if (!action.capability_id)
214
227
  return null;
@@ -226,6 +239,7 @@ function paidContinuation(model, action, input) {
226
239
  price: { amount_minor: capability.price_amount_minor, currency: capability.price_currency },
227
240
  } : {}),
228
241
  delivery_email_required: capability.delivery_email_required,
242
+ ...(capability.delivery_email_purpose ? { delivery_email_purpose: capability.delivery_email_purpose } : {}),
229
243
  },
230
244
  next: {
231
245
  command: checkoutCommand(model.execution.service_execution_id, capability, input, !stateBacked),
@@ -288,7 +302,7 @@ export async function runServicesAction(backend, serviceExecutionID, actionType,
288
302
  ...(continuation ? { checkout: continuation.checkout } : {}),
289
303
  },
290
304
  instruction: continuation
291
- ? purchaseConfirmationInstruction("candidate_selected", continuation.price, continuation.capability.delivery_email_required, selection.title)
305
+ ? purchaseConfirmationInstruction("candidate_selected", continuation.price, continuation.capability.delivery_email_required, continuation.capability.delivery_email_purpose, selection.title)
292
306
  : "候选已绑定到来源 Execution;后续动作必须继续使用该 Execution。",
293
307
  next,
294
308
  recovery: [{
@@ -692,19 +706,26 @@ function servicesNextEnvelope(model) {
692
706
  if (deliveryMode === "vault_artifact") {
693
707
  const grantStatus = normalizeGrantStatus(delivery?.grant_status);
694
708
  const grantActive = grantStatus === "active";
709
+ const grantPending = grantStatus === "pending";
695
710
  return {
696
- status: grantActive ? "grant_active" : "human_authorization_required",
711
+ status: grantActive ? "grant_active" : grantPending ? "result_preparing" : "human_authorization_required",
697
712
  result: {
698
713
  service_execution_id: execution.service_execution_id,
699
714
  ...(delivery?.capability_id ? { capability_id: delivery.capability_id } : {}),
700
715
  delivery_mode: deliveryMode,
701
716
  grant_status: grantStatus,
717
+ ...(delivery?.preparation ? { preparation: delivery.preparation } : {}),
702
718
  ...(grantActive && delivery?.grant_expires_at ? { grant_expires_at: delivery.grant_expires_at } : {}),
703
719
  },
704
720
  instruction: grantActive
705
721
  ? "这是当前 Graph 步骤对应的交付;用户授权有效,立即读取并遵守字段范围与到期时间。"
706
- : "这是当前 Graph 步骤对应的交付;请用户在订单页面授权,未授权前不要读取或猜测内容。",
707
- next: {
722
+ : grantPending
723
+ ? "用户已经完成授权,服务端正在按已发布执行图准备交付内容。不要再次付款、再次授权、新建 Execution 或调用 read-result;只执行 next.command 查询同一 Execution。"
724
+ : "这是当前 Graph 步骤对应的交付;请用户在订单页面授权,未授权前不要读取或猜测内容。",
725
+ next: grantPending ? {
726
+ command: `itpay services next ${execution.service_execution_id} --json`,
727
+ reason: "等待同一 Execution 的交付准备完成",
728
+ } : {
708
729
  command: `itpay services read-result ${execution.service_execution_id} --json`,
709
730
  reason: grantActive ? "读取当前有效 grant 的结果" : "仅在用户确认授权后执行",
710
731
  },
@@ -724,7 +745,7 @@ function servicesNextEnvelope(model) {
724
745
  phase: execution.phase,
725
746
  checkout: continuation.checkout,
726
747
  },
727
- instruction: purchaseConfirmationInstruction(execution.status === "quota_exhausted" ? "quota_exhausted" : "candidate_selected", continuation.price, continuation.capability.delivery_email_required),
748
+ instruction: purchaseConfirmationInstruction(execution.status === "quota_exhausted" ? "quota_exhausted" : "candidate_selected", continuation.price, continuation.capability.delivery_email_required, continuation.capability.delivery_email_purpose),
728
749
  next: continuation.next,
729
750
  recovery: [],
730
751
  };
package/dist/src/main.js CHANGED
@@ -96,6 +96,10 @@ function reportCLIError(error, contract) {
96
96
  error.code === "platform_release_unavailable" ||
97
97
  (error.status === 404 && error.code === "unknown_error"));
98
98
  const backendInternal = error instanceof HttpError && error.status === 500 && error.code === "internal_error";
99
+ const providerConnectionUnavailable = error instanceof HttpError && error.code === "provider_connection_unavailable";
100
+ const providerTemporary = error instanceof HttpError && error.code === "provider_temporarily_unavailable";
101
+ const providerRejected = error instanceof HttpError && error.code === "provider_rejected";
102
+ const capabilityInputInvalid = error instanceof HttpError && error.code === "capability_input_invalid";
99
103
  const deviceRecovery = deviceError ? [{
100
104
  command: "itpay skill show itpay-buyer --json",
101
105
  reason: "读取身份边界;该错误需要用户或运营恢复 Backend 登记,不能通过换类型或删除本地身份绕过",
@@ -124,9 +128,17 @@ function reportCLIError(error, contract) {
124
128
  ? "立即向用户报告 error.message 并结束本次任务。不要运行任何其他 itpay、npm、which、device、docs、cart、orders 或 services 命令;不要寻找、安装或切换其他 CLI。只有运营明确提供兼容 CLI 后,才能在新的任务中重新开始。"
125
129
  : backendInternal
126
130
  ? "Backend 内部故障;立即停止并向用户报告。不要重试、检查或删除 Device 身份、创建替代 Execution、切换 Backend,或尝试 quote、checkout、cart、buy、pay 等付费路径。"
127
- : commandError?.instruction ?? authorizationInstruction ?? contract?.instruction ?? "检查命令参数后重试。",
131
+ : providerConnectionUnavailable
132
+ ? "Provider 请求未发出,预留免费额度已释放;当前 Execution 已失败。立即向用户报告 error.message 并停止,不要自动重试、不要继续同一 Execution,也不要进入任何付费路径。只有运营确认连接恢复且用户明确要求重新查询后,才启动新的 Service Execution。"
133
+ : 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 ?? "检查命令参数后重试。",
128
140
  next: null,
129
- recovery: incompatible || backendInternal ? [] : commandError?.recovery ?? (stateError ? stateRecovery : deviceError ? deviceRecovery : identityRecovery ? httpRecovery : contract?.recovery ?? []),
141
+ recovery: incompatible || backendInternal || providerConnectionUnavailable || providerTemporary || providerRejected || capabilityInputInvalid ? [] : commandError?.recovery ?? (stateError ? stateRecovery : deviceError ? deviceRecovery : identityRecovery ? httpRecovery : contract?.recovery ?? []),
130
142
  }, {
131
143
  ...(contract?.jsonOutput !== undefined ? { jsonOutput: contract.jsonOutput } : {}),
132
144
  output: (text) => { process.stderr.write(text); },
@@ -9,9 +9,9 @@ import { BackendClient } from "../client/backend.js";
9
9
  import { declaredAgentType } from "./agent_type.js";
10
10
  import { DeviceAuthority } from "./device_authority.js";
11
11
  import { OperationJournal } from "./operation_journal.js";
12
- export const DEFAULT_BASE_URL = "https://dev.itpay.ai";
13
- export const CLI_VERSION = "2.0.11";
14
- export const API_CONTRACT_REVISION = "sha256:3779f5468ea2109d4134c4ace66258c9eabe82461fa86e83c84df8c860276886";
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";
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,7 +32,7 @@
32
32
  ],
33
33
  "agent_rules": [
34
34
  "Install with npm install -g @itpay/cli.",
35
- "Use the default https://dev.itpay.ai beta API unless an environment override is deliberate.",
35
+ "Use the default https://app.itpay.ai API unless an environment override is deliberate.",
36
36
  "Use one exact type: codex-desktop, codex-cli, claude-code-desktop, claude-code-cli, or workbuddy.",
37
37
  "One local private key is reused, but each exact Backend API base URL has its own server registration and quota lineage.",
38
38
  "Within one Backend registration, each Agent Type has one Agent Instance; all windows and chats of the same type reuse it.",
@@ -29,7 +29,7 @@ itpay install [target] [--json]
29
29
  "result": {
30
30
  "agent_type": "codex-desktop",
31
31
  "default_host": "codex",
32
- "default_api": "https://dev.itpay.ai",
32
+ "default_api": "https://app.itpay.ai",
33
33
  "install_command": "npm install -g @itpay/cli"
34
34
  },
35
35
  "instruction": "在 Codex Desktop 中始终传这个 Agent Type;付款时把返回的二维码和链接实际展示到当前对话。",
@@ -62,6 +62,25 @@ itpay services invoke <service_execution_id> --capability <capability_id>
62
62
 
63
63
  缺少 required input 时返回 `capability_input_invalid`,recovery 给出带占位符的同一 invoke 命令;CLI 和 Backend 都必须在 Provider 调用前拒绝,Backend 还必须在 execution/event/quota/invocation 写入前拒绝。错误调用付费 capability 时不得给出购买旁路,只能回到同一 Execution 的 `services next`;execution 状态、event、ProviderCalled 均保持不变。
64
64
 
65
+ ## Provider 请求前连接失败
66
+
67
+ 如果 Backend 能确认请求未发出,返回固定的终态错误,不暴露 DNS、IP、Provider URL 或凭证诊断:
68
+
69
+ ```json
70
+ {
71
+ "status": "error",
72
+ "error": {
73
+ "code": "provider_connection_unavailable",
74
+ "message": "provider request was not sent; reserved quota was released"
75
+ },
76
+ "instruction": "Provider 请求未发出,预留免费额度已释放;当前 Execution 已失败。立即向用户报告 error.message 并停止,不要自动重试、不要继续同一 Execution,也不要进入任何付费路径。只有运营确认连接恢复且用户明确要求重新查询后,才启动新的 Service Execution。",
77
+ "next": null,
78
+ "recovery": []
79
+ }
80
+ ```
81
+
82
+ 该终态不允许 CLI 猜测网络修复、重复 invoke 或转入购买。连接恢复后也不能复用失败 Execution;必须同时满足“运营已确认恢复”和“用户明确要求再次查询”,才创建新 Execution。
83
+
65
84
  ## Agent Type / Host
66
85
 
67
86
  `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy` 五种 Agent Type 的 safe result 一致。instruction 可以适配对话表述,但不得隐藏 quota、价格或 schema 错误。
@@ -65,7 +65,7 @@ itpay services next <service_execution_id> [--json]
65
65
  }
66
66
  ]
67
67
  },
68
- "instruction": "付费模糊搜索已完成。现在把 items 中的编号、title 和 safe_payload 展示给用户,然后停止。本结果是 agent-visible,不要调用 read-result。若用户的目标只是搜索候选企业,任务已经完成;只有用户之后明确选择某个候选并要求继续时,才执行 next.command。不要自动购买后续报告。",
68
+ "instruction": "付费 Agent-visible 搜索已完成。现在把 items 中的编号、title 和 safe_payload 展示给用户,然后停止。本结果是 agent-visible,不要调用 read-result。若用户的目标只是候选搜索,任务已经完成;只有用户之后明确选择某个候选并要求继续时,才执行 next.command。不要自动购买后续报告。",
69
69
  "next": {
70
70
  "command": "itpay services action <id> --action select_candidate --actor-type human --status approved --candidate <rank> --json",
71
71
  "reason": "仅在用户明确选择候选并要求继续时执行"
@@ -98,6 +98,33 @@ itpay services next <service_execution_id> [--json]
98
98
  }
99
99
  ```
100
100
 
101
+ 用户已经授权、但服务端仍在按已发布执行图准备 Vault 交付时,必须只轮询同一 Execution:
102
+
103
+ ```json
104
+ {
105
+ "status": "result_preparing",
106
+ "result": {
107
+ "service_execution_id": "<id>",
108
+ "capability_id": "<capability_id>",
109
+ "delivery_mode": "vault_artifact",
110
+ "grant_status": "pending",
111
+ "preparation": {
112
+ "status": "running",
113
+ "total_nodes": 4,
114
+ "completed_nodes": 2,
115
+ "succeeded_nodes": 2,
116
+ "failed_nodes": 0
117
+ }
118
+ },
119
+ "instruction": "用户已经完成授权,服务端正在按已发布执行图准备交付内容。不要再次付款、再次授权、新建 Execution 或调用 read-result;只执行 next.command 查询同一 Execution。",
120
+ "next": {
121
+ "command": "itpay services next <id> --json",
122
+ "reason": "等待同一 Execution 的交付准备完成"
123
+ },
124
+ "recovery": []
125
+ }
126
+ ```
127
+
101
128
  有效 grant 存在时:
102
129
 
103
130
  ```json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itpay/cli",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "ItPay CLI for V3 checkout, payment, order, refund, and agent-facing render flows.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -70,6 +70,7 @@ Run `next.command` only after the human says they acted or asks for status. QR r
70
70
 
71
71
  - Agent-visible results come from `services next`; do not use `read-result` for them.
72
72
  - Protected results require a current 15-minute human grant scoped to one delivery, approved fields, and frozen Agent audience.
73
+ - If `services next` returns `result_preparing`, authorization is already complete. Run only its same-Execution `next.command`; do not pay, authorize, start, or call `read-result` again.
73
74
  - An Execution may have delivery history; follow `services next` for the Backend-selected current delivery.
74
75
  - A pending refund locks delivery and revokes active grants. Follow the returned refund command and state.
75
76
 
@@ -88,6 +89,8 @@ itpay --agent-type <agent_type> refund get <refund_request_id> --json
88
89
 
89
90
  Reuse the same Execution and Checkout. Never start another Execution, create another Checkout, change payment route, or replay a capability to bypass quota, selection, payment, delivery, grant, or refund state.
90
91
 
92
+ `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
+
91
94
  ## Safety
92
95
 
93
96
  - Never invent service, capability, item, Checkout, Order, grant, or refund IDs.