@itpay/cli 2.0.11 → 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.
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
 
@@ -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)) {
@@ -404,7 +409,7 @@ function buildServiceGuidance(input) {
404
409
  command: `itpay services checkout ${execution.service_execution_id} --capability ${capabilityID}${emailRequired ? " --email <email>" : ""} --json`,
405
410
  requires_human: true,
406
411
  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."
412
+ ? deliveryEmailGuidance(checkoutCapability?.delivery_email_purpose)
408
413
  : "This capability returns an agent-visible result after payment and does not require a delivery email.",
409
414
  });
410
415
  }
@@ -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 = {
@@ -487,6 +480,7 @@ function buildServiceGuidance(input) {
487
480
  requires_payment: capability.requires_payment,
488
481
  vault_required: capability.vault_required,
489
482
  delivery_email_required: capability.delivery_email_required,
483
+ delivery_email_purpose: capability.delivery_email_purpose,
490
484
  price_amount_minor: capability.price_amount_minor,
491
485
  price_currency: capability.price_currency,
492
486
  free_quota_limit: capability.free_quota_limit,
@@ -506,6 +500,18 @@ function buildServiceGuidance(input) {
506
500
  : {}),
507
501
  };
508
502
  }
503
+ function deliveryEmailGuidance(purpose) {
504
+ switch (purpose) {
505
+ case "receipt":
506
+ return "Ask the human for their email. It is used to send the order receipt; never invent or substitute an address.";
507
+ case "claim":
508
+ return "Ask the human for their email. It is used to send the protected result claim link; never invent or substitute an address.";
509
+ case "receipt_and_claim":
510
+ 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.";
511
+ default:
512
+ return "Ask the human for the required email and state only the Backend-declared purpose; never invent or substitute an address.";
513
+ }
514
+ }
509
515
  function firstPrePurchaseCapability(capabilities) {
510
516
  return capabilities.find((capability) => capability.agent_visible && !capability.requires_payment)
511
517
  ?? capabilities.find((capability) => capability.agent_visible);
@@ -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";
@@ -128,7 +129,7 @@ function invokedEnvelope(response, requestedCapability, capabilities, input) {
128
129
  delivery_email_required: checkoutCapability.delivery_email_required,
129
130
  };
130
131
  const price = capabilityPrice(checkoutCapability);
131
- instruction = purchaseConfirmationInstruction("quota_exhausted", price, checkoutCapability.delivery_email_required);
132
+ instruction = purchaseConfirmationInstruction("quota_exhausted", price, checkoutCapability.delivery_email_required, checkoutCapability.delivery_email_purpose);
132
133
  next = {
133
134
  command: checkoutCommand(response.execution.service_execution_id, checkoutCapability, input),
134
135
  reason: `仅在用户明确同意支付 ${price} 后执行;否则停止`,
@@ -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)
@@ -198,17 +208,30 @@ function capabilityPrice(capability) {
198
208
  ? formatMoney(capability.price_amount_minor, capability.price_currency)
199
209
  : "当前发布价格";
200
210
  }
201
- function purchaseConfirmationInstruction(context, price, deliveryEmailRequired, candidateTitle = "") {
211
+ function purchaseConfirmationInstruction(context, price, deliveryEmailRequired, deliveryEmailPurpose, candidateTitle = "") {
212
+ const emailPurpose = deliveryEmailPurposeText(deliveryEmailPurpose);
202
213
  if (context === "quota_exhausted") {
203
214
  return deliveryEmailRequired
204
- ? `免费额度已用完,本次没有调用 Provider,也尚未创建 Quote 或 Checkout。现在只向用户说明:继续当前请求需要支付 ${price},交付还需要用户邮箱;请确认是否购买并提供邮箱。然后停止并等待。用户明确同意并提供真实邮箱前,不要执行 next.command,不要新建 Execution,不要尝试其他 capability、quote、cart、buy、checkout 或 pay 命令。`
215
+ ? `免费额度已用完,本次没有调用 Provider,也尚未创建 Quote 或 Checkout。现在只向用户说明:继续当前请求需要支付 ${price},并提供${emailPurpose};请确认是否购买并提供邮箱。然后停止并等待。用户明确同意并提供真实邮箱前,不要执行 next.command,不要新建 Execution,不要尝试其他 capability、quote、cart、buy、checkout 或 pay 命令。`
205
216
  : `免费额度已用完,本次没有调用 Provider,也尚未创建 Quote 或 Checkout。现在只向用户说明:“继续当前请求需要支付 ${price},是否购买?”然后停止并等待用户明确回复。用户明确同意前,不要执行 next.command,不要新建 Execution,不要尝试其他 capability、quote、cart、buy、checkout 或 pay 命令。`;
206
217
  }
207
218
  const selected = candidateTitle ? `已选择 ${candidateTitle}。` : "当前候选已经确认。";
208
219
  return deliveryEmailRequired
209
- ? `${selected}候选已绑定到当前 Execution,但尚未购买后续服务。现在只向用户说明:继续购买后续服务需要支付 ${price},并提供用于发送交付认领链接的邮箱;请确认是否购买并提供邮箱。然后停止。用户明确同意并提供真实邮箱前,不要执行 next.command,不要创建新 Execution 或 Checkout。`
220
+ ? `${selected}候选已绑定到当前 Execution,但尚未购买后续服务。现在只向用户说明:继续购买后续服务需要支付 ${price},并提供${emailPurpose};请确认是否购买并提供邮箱。然后停止。用户明确同意并提供真实邮箱前,不要执行 next.command,不要创建新 Execution 或 Checkout。`
210
221
  : `${selected}候选已绑定到当前 Execution,但尚未购买后续服务。现在只向用户说明:“继续购买后续服务需要支付 ${price},是否购买?”然后停止。用户明确同意前,不要执行 next.command,不要创建新 Execution 或 Checkout。`;
211
222
  }
223
+ function deliveryEmailPurposeText(purpose) {
224
+ switch (purpose) {
225
+ case "receipt":
226
+ return "用于发送订单收据的真实邮箱";
227
+ case "claim":
228
+ return "用于发送交付认领链接的真实邮箱";
229
+ case "receipt_and_claim":
230
+ return "用于发送订单收据和交付认领链接的真实邮箱";
231
+ default:
232
+ return "服务端声明用途的真实邮箱";
233
+ }
234
+ }
212
235
  function paidContinuation(model, action, input) {
213
236
  if (!action.capability_id)
214
237
  return null;
@@ -226,6 +249,7 @@ function paidContinuation(model, action, input) {
226
249
  price: { amount_minor: capability.price_amount_minor, currency: capability.price_currency },
227
250
  } : {}),
228
251
  delivery_email_required: capability.delivery_email_required,
252
+ ...(capability.delivery_email_purpose ? { delivery_email_purpose: capability.delivery_email_purpose } : {}),
229
253
  },
230
254
  next: {
231
255
  command: checkoutCommand(model.execution.service_execution_id, capability, input, !stateBacked),
@@ -288,7 +312,7 @@ export async function runServicesAction(backend, serviceExecutionID, actionType,
288
312
  ...(continuation ? { checkout: continuation.checkout } : {}),
289
313
  },
290
314
  instruction: continuation
291
- ? purchaseConfirmationInstruction("candidate_selected", continuation.price, continuation.capability.delivery_email_required, selection.title)
315
+ ? purchaseConfirmationInstruction("candidate_selected", continuation.price, continuation.capability.delivery_email_required, continuation.capability.delivery_email_purpose, selection.title)
292
316
  : "候选已绑定到来源 Execution;后续动作必须继续使用该 Execution。",
293
317
  next,
294
318
  recovery: [{
@@ -681,30 +705,37 @@ function servicesNextEnvelope(model) {
681
705
  ? selection
682
706
  ? "Agent-visible 搜索已完成。向用户展示 items 中的编号、title 和 safe_payload,然后停止;不要调用 read-result。只有用户明确选择候选并要求继续时,才执行 next.command。"
683
707
  : "这是当前 Graph 步骤对应的交付;结果已可供 Agent 使用,只使用 safe_payload。"
684
- : "Agent-visible 交付已完成但没有结果项;不要调用 read-result 或重放当前 execution。",
708
+ : "Agent-visible 交付已完成但有 0 个结果。向用户展示空结果并停止;不要调用 read-result、重放当前 Execution、修改输入或创建新 Execution。",
685
709
  next: selection ? {
686
710
  command: `itpay services action ${execution.service_execution_id} --action select_candidate --actor-type human --status approved --candidate <rank> --json`,
687
711
  reason: "仅在用户明确选择后锁定来源候选",
688
712
  } : null,
689
- recovery: items.length > 0 ? [] : [{ command: `itpay services get ${execution.service_execution_id} --json`, reason: "检查交付时间线" }],
713
+ recovery: [],
690
714
  };
691
715
  }
692
716
  if (deliveryMode === "vault_artifact") {
693
717
  const grantStatus = normalizeGrantStatus(delivery?.grant_status);
694
718
  const grantActive = grantStatus === "active";
719
+ const grantPending = grantStatus === "pending";
695
720
  return {
696
- status: grantActive ? "grant_active" : "human_authorization_required",
721
+ status: grantActive ? "grant_active" : grantPending ? "result_preparing" : "human_authorization_required",
697
722
  result: {
698
723
  service_execution_id: execution.service_execution_id,
699
724
  ...(delivery?.capability_id ? { capability_id: delivery.capability_id } : {}),
700
725
  delivery_mode: deliveryMode,
701
726
  grant_status: grantStatus,
727
+ ...(delivery?.preparation ? { preparation: delivery.preparation } : {}),
702
728
  ...(grantActive && delivery?.grant_expires_at ? { grant_expires_at: delivery.grant_expires_at } : {}),
703
729
  },
704
730
  instruction: grantActive
705
731
  ? "这是当前 Graph 步骤对应的交付;用户授权有效,立即读取并遵守字段范围与到期时间。"
706
- : "这是当前 Graph 步骤对应的交付;请用户在订单页面授权,未授权前不要读取或猜测内容。",
707
- next: {
732
+ : grantPending
733
+ ? "用户已经完成授权,服务端正在按已发布执行图准备交付内容。不要再次付款、再次授权、新建 Execution 或调用 read-result;只执行 next.command 查询同一 Execution。"
734
+ : "这是当前 Graph 步骤对应的交付;请用户在订单页面授权,未授权前不要读取或猜测内容。",
735
+ next: grantPending ? {
736
+ command: `itpay services next ${execution.service_execution_id} --json`,
737
+ reason: "等待同一 Execution 的交付准备完成",
738
+ } : {
708
739
  command: `itpay services read-result ${execution.service_execution_id} --json`,
709
740
  reason: grantActive ? "读取当前有效 grant 的结果" : "仅在用户确认授权后执行",
710
741
  },
@@ -724,7 +755,7 @@ function servicesNextEnvelope(model) {
724
755
  phase: execution.phase,
725
756
  checkout: continuation.checkout,
726
757
  },
727
- instruction: purchaseConfirmationInstruction(execution.status === "quota_exhausted" ? "quota_exhausted" : "candidate_selected", continuation.price, continuation.capability.delivery_email_required),
758
+ instruction: purchaseConfirmationInstruction(execution.status === "quota_exhausted" ? "quota_exhausted" : "candidate_selected", continuation.price, continuation.capability.delivery_email_required, continuation.capability.delivery_email_purpose),
728
759
  next: continuation.next,
729
760
  recovery: [],
730
761
  };
@@ -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,7 +95,16 @@ 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";
102
+ const providerConnectionUnavailable = error instanceof HttpError && error.code === "provider_connection_unavailable";
103
+ const providerTemporary = error instanceof HttpError && error.code === "provider_temporarily_unavailable";
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";
107
+ const capabilityInputInvalid = error instanceof HttpError && error.code === "capability_input_invalid";
99
108
  const deviceRecovery = deviceError ? [{
100
109
  command: "itpay skill show itpay-buyer --json",
101
110
  reason: "读取身份边界;该错误需要用户或运营恢复 Backend 登记,不能通过换类型或删除本地身份绕过",
@@ -120,13 +129,50 @@ function reportCLIError(error, contract) {
120
129
  code: incompatible ? "backend_contract_incompatible" : commandError?.code ?? (error instanceof HttpError ? error.code : stateError?.code ?? deviceError?.code ?? contract?.code ?? "command_failed"),
121
130
  message: error instanceof Error ? error.message : String(error),
122
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
+ } : {}),
123
149
  instruction: incompatible
124
- ? "立即向用户报告 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 身份。"
125
153
  : backendInternal
126
154
  ? "Backend 内部故障;立即停止并向用户报告。不要重试、检查或删除 Device 身份、创建替代 Execution、切换 Backend,或尝试 quote、checkout、cart、buy、pay 等付费路径。"
127
- : commandError?.instruction ?? authorizationInstruction ?? contract?.instruction ?? "检查命令参数后重试。",
155
+ : providerConnectionUnavailable
156
+ ? "Provider 请求未发出,预留免费额度已释放;当前 Execution 已失败。立即向用户报告 error.message 并停止,不要自动重试、不要继续同一 Execution,也不要进入任何付费路径。只有运营确认连接恢复且用户明确要求重新查询后,才启动新的 Service Execution。"
157
+ : providerTemporary
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 ?? "检查命令参数后重试。",
128
168
  next: null,
129
- recovery: incompatible || backendInternal ? [] : 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 ?? []),
130
176
  }, {
131
177
  ...(contract?.jsonOutput !== undefined ? { jsonOutput: contract.jsonOutput } : {}),
132
178
  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.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,7 +32,9 @@
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
+ "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.",
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.",
38
40
  "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;付款时把返回的二维码和链接实际展示到当前对话。",
@@ -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
  {
@@ -62,6 +84,70 @@ itpay services invoke <service_execution_id> --capability <capability_id>
62
84
 
63
85
  缺少 required input 时返回 `capability_input_invalid`,recovery 给出带占位符的同一 invoke 命令;CLI 和 Backend 都必须在 Provider 调用前拒绝,Backend 还必须在 execution/event/quota/invocation 写入前拒绝。错误调用付费 capability 时不得给出购买旁路,只能回到同一 Execution 的 `services next`;execution 状态、event、ProviderCalled 均保持不变。
64
86
 
87
+ ## Provider 请求前连接失败
88
+
89
+ 如果 Backend 能确认请求未发出,返回固定的终态错误,不暴露 DNS、IP、Provider URL 或凭证诊断:
90
+
91
+ ```json
92
+ {
93
+ "status": "error",
94
+ "error": {
95
+ "code": "provider_connection_unavailable",
96
+ "message": "provider request was not sent; reserved quota was released"
97
+ },
98
+ "result": {
99
+ "service_execution_id": "<id>",
100
+ "provider_called": false,
101
+ "quota": { "remaining": 3, "limit": 3 }
102
+ },
103
+ "instruction": "Provider 请求未发出,预留免费额度已释放;当前 Execution 已失败。立即向用户报告 error.message 并停止,不要自动重试、不要继续同一 Execution,也不要进入任何付费路径。只有运营确认连接恢复且用户明确要求重新查询后,才启动新的 Service Execution。",
104
+ "next": null,
105
+ "recovery": []
106
+ }
107
+ ```
108
+
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 请求只允许来自用户后续明确提出的新输入。
150
+
65
151
  ## Agent Type / Host
66
152
 
67
153
  `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
@@ -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.11",
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.
@@ -70,6 +73,7 @@ Run `next.command` only after the human says they acted or asks for status. QR r
70
73
 
71
74
  - Agent-visible results come from `services next`; do not use `read-result` for them.
72
75
  - Protected results require a current 15-minute human grant scoped to one delivery, approved fields, and frozen Agent audience.
76
+ - 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
77
  - An Execution may have delivery history; follow `services next` for the Backend-selected current delivery.
74
78
  - A pending refund locks delivery and revokes active grants. Follow the returned refund command and state.
75
79
 
@@ -88,6 +92,10 @@ itpay --agent-type <agent_type> refund get <refund_request_id> --json
88
92
 
89
93
  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
94
 
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.
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
+
91
99
  ## Safety
92
100
 
93
101
  - Never invent service, capability, item, Checkout, Order, grant, or refund IDs.