@itpay/cli 2.0.35 → 2.0.37

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.
Files changed (53) hide show
  1. package/README.md +1 -0
  2. package/dist/src/commands/buy.js +2 -2
  3. package/dist/src/commands/checkout_handoff.js +10 -1
  4. package/dist/src/commands/install.js +2 -0
  5. package/dist/src/commands/pay.js +13 -5
  6. package/dist/src/commands/vault_handoff.js +7 -1
  7. package/dist/src/state/client_context.js +2 -0
  8. package/dist/src/state/config.js +2 -2
  9. package/dist/src/state/device_authority.js +26 -10
  10. package/dist/src/state/operation_journal.js +76 -63
  11. package/docs/agent/buyer/identity-and-sessions.json +2 -1
  12. package/docs/agent/buyer/install-and-setup.json +3 -2
  13. package/docs/agent/buyer/render-hosts.json +7 -0
  14. package/docs/cli-reference/agent-types.md +8 -3
  15. package/docs/cli-reference/commands/buy.md +2 -1
  16. package/docs/cli-reference/commands/cart/add.md +1 -1
  17. package/docs/cli-reference/commands/cart/clear.md +1 -1
  18. package/docs/cli-reference/commands/cart/index.md +1 -1
  19. package/docs/cli-reference/commands/cart/next.md +1 -1
  20. package/docs/cli-reference/commands/cart/remove.md +1 -1
  21. package/docs/cli-reference/commands/cart/show.md +1 -1
  22. package/docs/cli-reference/commands/catalog/index.md +1 -1
  23. package/docs/cli-reference/commands/catalog/list.md +1 -1
  24. package/docs/cli-reference/commands/checkout.md +1 -0
  25. package/docs/cli-reference/commands/device.md +8 -0
  26. package/docs/cli-reference/commands/docs/index.md +1 -1
  27. package/docs/cli-reference/commands/docs/list.md +1 -1
  28. package/docs/cli-reference/commands/docs/search.md +1 -1
  29. package/docs/cli-reference/commands/docs/show.md +1 -1
  30. package/docs/cli-reference/commands/feedback/submit.md +3 -0
  31. package/docs/cli-reference/commands/install.md +6 -4
  32. package/docs/cli-reference/commands/next.md +1 -1
  33. package/docs/cli-reference/commands/order.md +1 -1
  34. package/docs/cli-reference/commands/pay.md +1 -0
  35. package/docs/cli-reference/commands/refund/cancel.md +1 -1
  36. package/docs/cli-reference/commands/refund/create.md +1 -1
  37. package/docs/cli-reference/commands/refund/get.md +1 -1
  38. package/docs/cli-reference/commands/refund/index.md +1 -1
  39. package/docs/cli-reference/commands/refund/list.md +1 -1
  40. package/docs/cli-reference/commands/refund/watch.md +1 -1
  41. package/docs/cli-reference/commands/services/action.md +1 -1
  42. package/docs/cli-reference/commands/services/checkout.md +7 -0
  43. package/docs/cli-reference/commands/services/events.md +1 -1
  44. package/docs/cli-reference/commands/services/get.md +1 -1
  45. package/docs/cli-reference/commands/services/index.md +1 -1
  46. package/docs/cli-reference/commands/services/invoke.md +1 -1
  47. package/docs/cli-reference/commands/services/list.md +1 -1
  48. package/docs/cli-reference/commands/services/next.md +1 -1
  49. package/docs/cli-reference/commands/services/quote.md +1 -1
  50. package/docs/cli-reference/commands/services/read-result.md +1 -1
  51. package/docs/cli-reference/commands/vault/access.md +3 -0
  52. package/package.json +2 -1
  53. package/skills/itpay/SKILL.md +5 -0
package/README.md CHANGED
@@ -53,6 +53,7 @@ Normative per-command contracts: [CLI Command Reference](docs/cli-reference/inde
53
53
  | `claude-code-desktop` | `claude-code` |
54
54
  | `claude-code-cli` | `terminal` |
55
55
  | `workbuddy` | `plain-chat` |
56
+ | `zcode` | `plain-chat` |
56
57
  | `kimi-code` | `terminal` |
57
58
  | `openclaw` | 必须显式提供 |
58
59
 
@@ -17,7 +17,7 @@ import { localizeCardURL, normalizeCardLocale } from "../render/locale.js";
17
17
  import { buildAgentChatHandoff } from "../render/markdown.js";
18
18
  import { formatMoney } from "../render/output.js";
19
19
  import { CommandContractError, writeCommandEnvelope } from "./guidance.js";
20
- import { buildCheckoutHandoff, shouldPrepareLocalCheckoutImage } from "./checkout_handoff.js";
20
+ import { buildCheckoutHandoff, isLinkOnlyBrowserAgent, shouldPrepareLocalCheckoutImage } from "./checkout_handoff.js";
21
21
  import { qualifyItPayCommand } from "../state/agent_type.js";
22
22
  export async function runBuy(backend, config, options) {
23
23
  const err = validateContext(options.host, options.target);
@@ -267,7 +267,7 @@ export function buildCheckoutQRPlan(input) {
267
267
  host: input.host,
268
268
  summary,
269
269
  url: input.qrPayload,
270
- ...(input.agentType?.trim().toLowerCase() === "workbuddy" && input.cardURL ? { linkOnlyURL: input.cardURL } : {}),
270
+ ...(isLinkOnlyBrowserAgent(input.agentType, platformKeyForHost(input.host)) && input.cardURL ? { linkOnlyURL: input.cardURL } : {}),
271
271
  preferredQRSources: [input.qrPNGURL ?? input.qrPayload],
272
272
  checkoutID: input.checkoutID,
273
273
  platform,
@@ -5,6 +5,12 @@ export function shouldPrepareLocalCheckoutImage(platform) {
5
5
  export function isWorkBuddyPlainChat(agentType, platform) {
6
6
  return agentType?.trim().toLowerCase() === "workbuddy" && platform === "plain_chat";
7
7
  }
8
+ export function isZCodePlainChat(agentType, platform) {
9
+ return agentType?.trim().toLowerCase() === "zcode" && platform === "plain_chat";
10
+ }
11
+ export function isLinkOnlyBrowserAgent(agentType, platform) {
12
+ return isWorkBuddyPlainChat(agentType, platform) || isZCodePlainChat(agentType, platform);
13
+ }
8
14
  export function buildWorkBuddyPresentFilesAction(url) {
9
15
  return {
10
16
  tool: "present_files",
@@ -23,7 +29,7 @@ export function buildCheckoutHandoff(input) {
23
29
  if (input.markdown)
24
30
  handoff.markdown = input.markdown;
25
31
  }
26
- else if (input.platform === "plain_chat" && input.qrImageURL && !workBuddyAction) {
32
+ else if (input.platform === "plain_chat" && input.qrImageURL && !isLinkOnlyBrowserAgent(input.agentType, input.platform)) {
27
33
  handoff.qr_image_url = input.qrImageURL;
28
34
  }
29
35
  else if (input.platform === "telegram" && input.qrImageURL) {
@@ -41,6 +47,9 @@ function checkoutHandoffInstruction(agentType, platform, amount) {
41
47
  if (isWorkBuddyPlainChat(agentType, platform)) {
42
48
  return `Backend 尚未确认付款。立即严格按 handoff.agent_action.tool 和 handoff.agent_action.arguments 原样执行一次,在右侧打开 handoff.url;确认工具调用成功后说明金额 ${amount},然后停止等待。若工具失败,只发送原始 handoff.url,报告未自动打开并停止。不要用 present_files 打开本地文件或二维码 PNG,不要下载或重建二维码,不要调用 pay,不要创建新 Checkout、Payment Intent 或 Execution。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
43
49
  }
50
+ if (isZCodePlainChat(agentType, platform)) {
51
+ return `Backend 尚未确认付款。立即用 ZCode 内置浏览器打开 handoff.url,让用户完成付款;确认已发起打开后说明金额 ${amount},然后停止等待。不要只粘贴文字链接,不要下载、解析或重建二维码,不要创建新 Checkout、Payment Intent 或 Execution。只有内置浏览器明确不可用时,才展示同一个可点击 handoff.url。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
52
+ }
44
53
  if (platform === "markdown") {
45
54
  return `Backend 尚未确认付款。把 handoff.markdown 原样发送到当前桌面对话,确认二维码、付款链接和金额 ${amount} 均已实际对用户可见,然后停止等待。不要创建新 Checkout、Payment Intent 或 Execution;只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
46
55
  }
@@ -7,6 +7,7 @@ export const INSTALL_AGENT_TYPES = [
7
7
  "claude-code-desktop",
8
8
  "claude-code-cli",
9
9
  "workbuddy",
10
+ "zcode",
10
11
  "kimi-code",
11
12
  "openclaw",
12
13
  ];
@@ -16,6 +17,7 @@ const INSTRUCTIONS = {
16
17
  "claude-code-desktop": "在 Claude Code Desktop 中始终传这个 Agent Type;付款时把返回的二维码和链接实际展示到当前对话。",
17
18
  "claude-code-cli": "在 Claude Code CLI 中始终传这个 Agent Type;付款交接只应显示在用户可见终端。",
18
19
  workbuddy: "在 WorkBuddy 中始终传这个 Agent Type。运行会持久化 ~/.itpay-v3 的 ItPay Bash 命令前,把 Shell 工具参数 dangerouslyDisableSandbox 设为 true;用户拒绝权限时停止,不要切换 Node、删除 identity 或处理 lock。Checkout 返回已渲染的付款 Card Link 和 present_files action;原样执行一次 action 打开 handoff.url,不要检查本地二维码文件。",
20
+ zcode: "在 ZCode 中始终传这个 Agent Type。Checkout 或授权返回 handoff.url 后,立即用 ZCode 内置浏览器打开;不要只粘贴文字链接或重建二维码。只有内置浏览器不可用时才展示同一个可点击链接。",
19
21
  "kimi-code": "保持 kimi-code Agent Type,使用标准 CLI 命令和终端付款交接;不要切换到其他平台身份。",
20
22
  openclaw: "保持 openclaw Agent Type;每个展示命令都从当前可信会话上下文显式传 --host,IM 入口同时传 --target。Telegram 使用返回的原生 message action,其他入口展示标准二维码和付款链接。",
21
23
  };
@@ -2,7 +2,7 @@
2
2
  // Checkout page; this command exists for controlled integration recovery.
3
3
  import { formatMoney } from "../render/output.js";
4
4
  import { writeCommandEnvelope } from "./guidance.js";
5
- import { buildWorkBuddyPresentFilesAction, isWorkBuddyPlainChat } from "./checkout_handoff.js";
5
+ import { buildWorkBuddyPresentFilesAction, isWorkBuddyPlainChat, isZCodePlainChat } from "./checkout_handoff.js";
6
6
  import { platformKeyForHost } from "../render/plan.js";
7
7
  export async function runPay(backend, options) {
8
8
  const intent = await backend.createPaymentIntent(options.checkoutID, {
@@ -20,12 +20,17 @@ function payEnvelope(intent, options) {
20
20
  const terminal = ["failed", "expired", "refunded"].includes(intent.status);
21
21
  const verified = intent.status === "verified" || intent.status === "partially_refunded";
22
22
  const handoff = {};
23
- const workBuddyAction = isWorkBuddyPlainChat(options.agentType, platformKeyForHost(options.host));
24
- if (!terminal && !verified && workBuddyAction) {
25
- const url = intent.action?.mobile_wallet_url ?? intent.action?.qr_image_url;
23
+ const platform = platformKeyForHost(options.host);
24
+ const workBuddyAction = isWorkBuddyPlainChat(options.agentType, platform);
25
+ const zcodeAction = isZCodePlainChat(options.agentType, platform);
26
+ if (!terminal && !verified && (workBuddyAction || zcodeAction)) {
27
+ const url = zcodeAction
28
+ ? intent.action?.qr_image_url ?? intent.action?.mobile_wallet_url
29
+ : intent.action?.mobile_wallet_url ?? intent.action?.qr_image_url;
26
30
  if (url) {
27
31
  handoff.url = url;
28
- handoff.agent_action = buildWorkBuddyPresentFilesAction(url);
32
+ if (workBuddyAction)
33
+ handoff.agent_action = buildWorkBuddyPresentFilesAction(url);
29
34
  }
30
35
  }
31
36
  else {
@@ -64,6 +69,9 @@ function payInstruction(options, verified, terminal, hasAction, amount) {
64
69
  if (isWorkBuddyPlainChat(options.agentType, platform)) {
65
70
  return `这是受控逃生入口。立即严格按 handoff.agent_action 原样执行一次,在右侧打开 handoff.url;确认工具调用成功后说明金额 ${amount} 并停止等待。若工具失败,只发送原始 handoff.url,报告未自动打开并停止。不要用 present_files 打开本地文件或二维码 PNG,不要立即查询、创建替代 Checkout 或 Payment Intent。`;
66
71
  }
72
+ if (isZCodePlainChat(options.agentType, platform)) {
73
+ return `这是受控逃生入口。立即用 ZCode 内置浏览器打开 handoff.url;确认已发起打开后说明金额 ${amount} 并停止等待。不要只粘贴文字链接,不要下载或重建二维码,不要立即查询、创建替代 Checkout 或 Payment Intent。只有内置浏览器明确不可用时,才展示同一个可点击 handoff.url。`;
74
+ }
67
75
  if (options.host === "codex" || options.host === "claude-code")
68
76
  return "这是受控逃生入口;把 handoff 中的二维码或钱包链接实际发到当前桌面对话,然后停止等待。";
69
77
  if (options.host === "terminal")
@@ -1,7 +1,7 @@
1
1
  import { platformKeyForHost } from "../render/plan.js";
2
2
  import { downloadBrandQRToTmp } from "../render/ide.js";
3
3
  import { renderTerminalQR } from "../render/qr.js";
4
- import { buildWorkBuddyPresentFilesAction, isWorkBuddyPlainChat } from "./checkout_handoff.js";
4
+ import { buildWorkBuddyPresentFilesAction, isWorkBuddyPlainChat, isZCodePlainChat } from "./checkout_handoff.js";
5
5
  const AUTH_ATTACH_REASON = "The human must be able to open or scan this official ItPay authorization handoff. " +
6
6
  "Render the local image in the current desktop chat; if that fails, show the unchanged official URL.";
7
7
  export async function buildVaultHandoff(input) {
@@ -14,6 +14,12 @@ export async function buildVaultHandoff(input) {
14
14
  instruction: "向用户说明这是查看已购内容的只读授权,然后严格按 handoff.agent_action 原样执行一次。打开成功后停止等待;失败时只发送同一个 handoff.url。用户完成后重新运行最初的读取命令,不要再次创建授权请求。",
15
15
  };
16
16
  }
17
+ if (isZCodePlainChat(input.agentType, platform)) {
18
+ return {
19
+ handoff,
20
+ instruction: "向用户说明这是查看已购内容的只读授权,然后立即用 ZCode 内置浏览器打开 handoff.url。确认已发起打开后停止等待;不要只粘贴文字链接,也不要下载或重建二维码。只有内置浏览器明确不可用时,才展示同一个可点击 handoff.url。用户完成后重新运行最初的读取命令,不要再次创建授权请求。",
21
+ };
22
+ }
17
23
  if (platform === "markdown") {
18
24
  const downloaded = input.imageAttachEnabled && input.qrPNGURL
19
25
  ? await downloadBrandQRToTmp(input.qrPNGURL, "auth", input.requestID, {
@@ -62,6 +62,8 @@ export function defaultHostForAgentType(agentType) {
62
62
  return "claude-code";
63
63
  if (normalized === "workbuddy")
64
64
  return "plain-chat";
65
+ if (normalized === "zcode")
66
+ return "plain-chat";
65
67
  if (normalized === "openclaw")
66
68
  return undefined;
67
69
  return "terminal";
@@ -12,8 +12,8 @@ import { DeviceAuthority } from "./device_authority.js";
12
12
  import { OperationJournal } from "./operation_journal.js";
13
13
  export const DEFAULT_BASE_URL = "https://app.itpay.ai";
14
14
  export const DEV_BASE_URL = "https://dev.itpay.ai";
15
- export const CLI_VERSION = "2.0.35";
16
- export const API_CONTRACT_REVISION = "sha256:a249912e4afb3267694081231334eb8a2ec653ceb9bbcbca91969b6977159e50";
15
+ export const CLI_VERSION = "2.0.37";
16
+ export const API_CONTRACT_REVISION = "sha256:b97025ee4e6f7539757d6aedbe193dd65b3a0f84f1111f678abb5e757065f888";
17
17
  const CART_SESSION_DEFAULT_DIR = ".itpay-v3";
18
18
  const CART_SESSION_FILENAME = "cart.json";
19
19
  const OPERATION_JOURNAL_FILENAME = "operations.json";
@@ -1,5 +1,5 @@
1
1
  import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, randomUUID, sign, } from "node:crypto";
2
- import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmdirSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
2
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, resolve } from "node:path";
5
5
  const PROTECTED_PATHS = ["/v1/carts", "/v1/service-executions", "/v1/agent-instances", "/v1/orders", "/v1/refunds", "/v1/me", "/v1/vault"];
@@ -292,10 +292,12 @@ async function withFileLock(path, run) {
292
292
  catch (error) {
293
293
  throw asDeviceStatePathError(error, "prepare_lock") ?? error;
294
294
  }
295
+ const ownerToken = randomUUID();
295
296
  let acquired = false;
296
297
  for (let attempt = 0; attempt < 200; attempt += 1) {
297
298
  try {
298
- mkdirSync(path, { mode: 0o700 });
299
+ writeFileSync(path, ownerToken, { encoding: "utf8", flag: "wx", mode: 0o600 });
300
+ chmodSync(path, 0o600);
299
301
  acquired = true;
300
302
  break;
301
303
  }
@@ -305,7 +307,7 @@ async function withFileLock(path, run) {
305
307
  throw asDeviceStateError(error, "acquire_lock") ?? error;
306
308
  try {
307
309
  if (Date.now() - statSync(path).mtimeMs > 30_000)
308
- removeLock(path, "remove_stale_lock");
310
+ moveLockAside(path, "stale", "remove_stale_lock");
309
311
  }
310
312
  catch (statError) {
311
313
  if (statError.code !== "ENOENT") {
@@ -321,19 +323,33 @@ async function withFileLock(path, run) {
321
323
  return await run();
322
324
  }
323
325
  finally {
324
- removeLock(path, "release_lock");
326
+ releaseLock(path, ownerToken);
325
327
  }
326
328
  }
327
- function removeLock(path, operation) {
329
+ function releaseLock(path, ownerToken) {
328
330
  try {
329
- if (statSync(path).isDirectory())
330
- rmdirSync(path);
331
- else
332
- unlinkSync(path);
331
+ if (readFileSync(path, "utf8") !== ownerToken)
332
+ return;
333
+ moveLockAside(path, "released", "release_lock");
333
334
  }
334
335
  catch (error) {
335
336
  if (error.code !== "ENOENT")
336
- throw asDeviceStateError(error, operation) ?? error;
337
+ throw asDeviceStateError(error, "release_lock") ?? error;
338
+ }
339
+ }
340
+ function moveLockAside(path, suffix, operation) {
341
+ try {
342
+ renameSync(path, `${path}.${suffix}`);
343
+ }
344
+ catch (error) {
345
+ const code = error.code;
346
+ if (code === "ENOENT")
347
+ return;
348
+ if (code === "EEXIST" || code === "ENOTEMPTY") {
349
+ renameSync(path, `${path}.${suffix}.${randomUUID()}`);
350
+ return;
351
+ }
352
+ throw asDeviceStateError(error, operation) ?? error;
337
353
  }
338
354
  }
339
355
  function asDeviceStateError(error, operation) {
@@ -1,80 +1,93 @@
1
- import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2
- import { dirname } from "node:path";
3
- import { randomUUID } from "node:crypto";
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { chmodSync, existsSync, linkSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { basename, resolve } from "node:path";
4
4
  export class OperationJournal {
5
- path;
6
- constructor(path) {
7
- this.path = path;
5
+ legacyPath;
6
+ constructor(legacyPath) {
7
+ this.legacyPath = legacyPath;
8
8
  }
9
9
  async getOrCreate(operationKey) {
10
- return withFileLock(`${this.path}.lock`, async () => {
11
- const state = this.read();
12
- const existing = state.operations[operationKey];
13
- if (existing)
14
- return existing.id;
15
- const id = `op_${randomUUID().replaceAll("-", "")}`;
16
- state.operations[operationKey] = { id, createdAt: new Date().toISOString() };
17
- atomicOwnerOnlyWrite(this.path, JSON.stringify(state, null, 2));
18
- return id;
19
- });
20
- }
21
- read() {
22
- if (existsSync(this.path)) {
10
+ const operationKeyHash = createHash("sha256").update(operationKey).digest("hex");
11
+ const recordDirectory = `${this.legacyPath}.d`;
12
+ const recordPath = resolve(recordDirectory, `${operationKeyHash}.json`);
13
+ const existing = readRecord(recordPath, operationKeyHash);
14
+ if (existing)
15
+ return existing.id;
16
+ mkdirSync(recordDirectory, { recursive: true, mode: 0o700 });
17
+ chmodSync(recordDirectory, 0o700);
18
+ const record = {
19
+ schemaVersion: "itpay.operation.v2",
20
+ operationKeyHash,
21
+ id: this.readLegacyID(operationKey) ?? `op_${randomUUID().replaceAll("-", "")}`,
22
+ createdAt: new Date().toISOString(),
23
+ };
24
+ const temporaryPath = resolve(recordDirectory, `.${basename(recordPath)}.${process.pid}.${randomUUID()}.tmp`);
25
+ try {
26
+ writeFileSync(temporaryPath, JSON.stringify(record, null, 2), { encoding: "utf8", flag: "wx", mode: 0o600 });
27
+ chmodSync(temporaryPath, 0o600);
23
28
  try {
24
- const parsed = JSON.parse(readFileSync(this.path, "utf8"));
25
- if (parsed.schemaVersion === "itpay.operations.v1" && parsed.operations)
26
- return parsed;
29
+ linkSync(temporaryPath, recordPath);
30
+ chmodSync(recordPath, 0o600);
31
+ return record.id;
27
32
  }
28
- catch {
29
- // A malformed local cache is replaced; server facts remain authoritative.
33
+ catch (error) {
34
+ if (error.code !== "EEXIST")
35
+ throw error;
36
+ const winner = readRecord(recordPath, operationKeyHash);
37
+ if (!winner)
38
+ throw new Error("ItPay operation journal record disappeared during publication");
39
+ return winner.id;
30
40
  }
31
41
  }
32
- return { schemaVersion: "itpay.operations.v1", operations: {} };
33
- }
34
- }
35
- function atomicOwnerOnlyWrite(path, value) {
36
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
37
- const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
38
- writeFileSync(temporary, value, { encoding: "utf8", mode: 0o600 });
39
- chmodSync(temporary, 0o600);
40
- renameSync(temporary, path);
41
- chmodSync(path, 0o600);
42
- }
43
- async function withFileLock(path, run) {
44
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
45
- let descriptor;
46
- for (let attempt = 0; attempt < 200; attempt += 1) {
47
- try {
48
- descriptor = openSync(path, "wx", 0o600);
49
- break;
50
- }
51
- catch (error) {
52
- if (error.code !== "EEXIST")
53
- throw error;
42
+ finally {
54
43
  try {
55
- if (Date.now() - statSync(path).mtimeMs > 30_000)
56
- unlinkSync(path);
44
+ unlinkSync(temporaryPath);
57
45
  }
58
- catch (statError) {
59
- if (statError.code !== "ENOENT")
60
- throw statError;
46
+ catch (error) {
47
+ if (error.code !== "ENOENT")
48
+ throw error;
61
49
  }
62
- await new Promise((resolve) => setTimeout(resolve, 25));
63
50
  }
64
51
  }
65
- if (descriptor === undefined)
66
- throw new Error("timed out waiting for ItPay operation journal lock");
67
- try {
68
- return await run();
69
- }
70
- finally {
71
- closeSync(descriptor);
52
+ readLegacyID(operationKey) {
53
+ if (!existsSync(this.legacyPath))
54
+ return undefined;
72
55
  try {
73
- unlinkSync(path);
56
+ const parsed = JSON.parse(readFileSync(this.legacyPath, "utf8"));
57
+ if (parsed.schemaVersion !== "itpay.operations.v1" || !parsed.operations)
58
+ return undefined;
59
+ const id = parsed.operations[operationKey]?.id;
60
+ return typeof id === "string" && isOperationID(id) ? id : undefined;
74
61
  }
75
- catch (error) {
76
- if (error.code !== "ENOENT")
77
- throw error;
62
+ catch {
63
+ return undefined;
78
64
  }
79
65
  }
80
66
  }
67
+ function readRecord(path, expectedHash) {
68
+ if (!existsSync(path))
69
+ return undefined;
70
+ let parsed;
71
+ try {
72
+ parsed = JSON.parse(readFileSync(path, "utf8"));
73
+ }
74
+ catch {
75
+ throw new Error("ItPay operation journal record is invalid");
76
+ }
77
+ if (!isOperationRecord(parsed, expectedHash)) {
78
+ throw new Error("ItPay operation journal record is invalid");
79
+ }
80
+ return parsed;
81
+ }
82
+ function isOperationRecord(value, expectedHash) {
83
+ if (!value || typeof value !== "object")
84
+ return false;
85
+ const record = value;
86
+ return record.schemaVersion === "itpay.operation.v2" &&
87
+ record.operationKeyHash === expectedHash &&
88
+ typeof record.id === "string" && isOperationID(record.id) &&
89
+ typeof record.createdAt === "string" && !Number.isNaN(Date.parse(record.createdAt));
90
+ }
91
+ function isOperationID(value) {
92
+ return /^op_[a-zA-Z0-9_-]+$/.test(value);
93
+ }
@@ -25,8 +25,9 @@
25
25
  ],
26
26
  "local_state_recovery": [
27
27
  "Device state updates use an atomic directory lock and owner-only atomic file replacement; no lock file is opened with fs.open or fs.openSync.",
28
+ "Operation idempotency uses one immutable owner-only record per operation key. Different commands and Agent processes do not share a global operation-journal lock; legacy operations.json is read only.",
28
29
  "On device_state_unwritable, use the reported operation and errno to diagnose the failing stage without exposing local paths; keep the same Agent Type and CLI/Node launcher, restore Host-approved persistent access to ~/.itpay-v3, then retry the original command.",
29
- "Do not manually create a lock, switch Node runtimes, delete identity, or rotate Agent Type as a retry strategy."
30
+ "Do not manually create or delete state locks, switch Node runtimes, delete identity, or rotate Agent Type as a retry strategy."
30
31
  ],
31
32
  "commands": [
32
33
  {
@@ -13,7 +13,7 @@
13
13
  {
14
14
  "intent": "list supported Agent Types",
15
15
  "command": "itpay install --json",
16
- "success_signal": "status is install_targets and exactly seven supported Agent Type definitions are returned"
16
+ "success_signal": "status is install_targets and exactly eight supported Agent Type definitions are returned"
17
17
  },
18
18
  {
19
19
  "intent": "read setup for the real runtime",
@@ -38,7 +38,7 @@
38
38
  "Production defaults to https://app.itpay.ai. Testing may set ITPAY_BACKEND_URL to the exact official URL https://dev.itpay.ai; no other Backend is allowed.",
39
39
  "If backend_contract_incompatible includes result.required_cli_version, stop all ItPay business commands and use only the exact distribution-specific update recovery returned by the CLI; never replace its version with latest.",
40
40
  "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.",
41
- "Use one exact type: codex-desktop, codex-cli, claude-code-desktop, claude-code-cli, workbuddy, kimi-code, or openclaw.",
41
+ "Use one exact type: codex-desktop, codex-cli, claude-code-desktop, claude-code-cli, workbuddy, zcode, kimi-code, or openclaw.",
42
42
  "For compatibility, a global --agent-type codex declaration is normalized immediately to codex-desktop; all registration, output, and returned commands use codex-desktop. Never generate the alias, and do not use codex as an install target.",
43
43
  "One local private key is reused, while Device registrations and quota lineage remain separate for app.itpay.ai and dev.itpay.ai.",
44
44
  "Within each official Backend registration, each Agent Type has one Agent Instance; all windows and chats of the same type reuse it.",
@@ -70,6 +70,7 @@
70
70
  "codex",
71
71
  "claude code",
72
72
  "workbuddy",
73
+ "zcode",
73
74
  "kimi code",
74
75
  "openclaw"
75
76
  ]
@@ -35,6 +35,11 @@
35
35
  "default_host": "plain-chat",
36
36
  "responsibility": "execute handoff.agent_action exactly once to open the fully rendered handoff.url with present_files; show the amount and stop"
37
37
  },
38
+ {
39
+ "agent_type": "zcode",
40
+ "default_host": "plain-chat",
41
+ "responsibility": "open handoff.url immediately in the ZCode built-in browser and stop; only show the same clickable URL when the built-in browser is unavailable"
42
+ },
38
43
  {
39
44
  "agent_type": "kimi-code",
40
45
  "default_host": "terminal",
@@ -64,6 +69,7 @@
64
69
  "Agent Type is stable identity; Host is presentation and may be explicitly overridden.",
65
70
  "A local QR path is not visible until a desktop Agent attaches or renders that file on the human-facing surface; WorkBuddy never receives a local QR path.",
66
71
  "For workbuddy with plain-chat, execute handoff.agent_action exactly once; it calls present_files with handoff.url as the only files element and opens the rendered Card Link in the right-side browser.",
72
+ "For zcode with plain-chat, open handoff.url in the built-in browser. Do not only paste text, download or rebuild a QR, or invent a private browser-tool payload; use the same clickable URL only as fallback.",
67
73
  "If the WorkBuddy action fails, send the original handoff.url, report that it did not auto-open, and stop without creating another payment resource.",
68
74
  "Do not claim the handoff was shown until both a usable payment image or QR and the Checkout URL are visible.",
69
75
  "IM Hosts that require a target must receive --target before Checkout creation. OpenClaw has no default Host; Kimi Code uses terminal.",
@@ -92,6 +98,7 @@
92
98
  "terminal",
93
99
  "desktop",
94
100
  "workbuddy",
101
+ "zcode",
95
102
  "kimi code",
96
103
  "openclaw",
97
104
  "telegram"
@@ -15,6 +15,7 @@
15
15
  | `claude-code-desktop` | `claude-code` | 返回桌面对话可展示的 Markdown 图片和官方链接,要求先展示再等待。 |
16
16
  | `claude-code-cli` | `terminal` | 在用户可见终端输出二维码和官方链接,不声称已在桌面对话展示。 |
17
17
  | `workbuddy` | `plain-chat` | 返回完整渲染的 HTML Card Link 和可原样执行的 `present_files` action;立即打开 Card Link,不返回或检查本地图片路径。 |
18
+ | `zcode` | `plain-chat` | 返回完整渲染的官方 URL;立即用 ZCode 内置浏览器打开,不只粘贴文字链接,也不返回或重建二维码图片。 |
18
19
  | `kimi-code` | `terminal` | 使用标准 CLI 引导,在用户可见终端渲染二维码和付款链接。 |
19
20
  | `openclaw` | 无;必须显式传入 | `--host telegram` 使用 OpenClaw 原生 `message` action;其他入口返回标准 HTTPS 二维码和付款链接。 |
20
21
 
@@ -27,11 +28,12 @@
27
28
  - 显式 `--host` 覆盖默认 Host,但不改变已登记的 Agent Type。
28
29
  - `--target` 只路由人类展示,不是身份,也不是 capability 业务输入。
29
30
  - Host 只影响 `instruction` 和 `handoff`,不得改变金额、订单、权限、quota 或交付状态。
30
- - 七种 Agent Type 使用同一命令输入和 JSON 外壳;OpenClaw 只在 `handoff` 的既有扩展位置增加平台 action,不改变交易字段。
31
- - 非展示命令在七种 Agent Type 下返回相同业务结果,只允许 `instruction` 措辞不同。只有 Host 客观无法展示某种媒介时,`handoff` 才按既有可选字段做最小裁剪。
31
+ - 所有正式 Agent Type 使用同一命令输入和 JSON 外壳;OpenClaw 只在 `handoff` 的既有扩展位置增加平台 action,不改变交易字段。
32
+ - 非展示命令在所有正式 Agent Type 下返回相同业务结果,只允许 `instruction` 措辞不同。只有 Host 客观无法展示某种媒介时,`handoff` 才按既有可选字段做最小裁剪。
32
33
  - `openclaw` 没有默认 Host。展示命令必须显式传 `--host`;缺少时在任何 Checkout 创建或状态迁移前返回 `host_required`。
33
34
  - OpenClaw 的 IM Host 必须提供 `--target`。缺失时在任何 Checkout 创建前返回 `target_required`。
34
35
  - `kimi-code` 是 CLI 型 Agent,复用 `terminal` Host 和现有 CLI 展示,不增加 Kimi 专属交易协议。
36
+ - `zcode` 使用 `plain-chat` Host 和官方 URL-only handoff。Agent 必须自行在 ZCode 内置浏览器打开 URL;只有内置浏览器确实不可用时才向用户展示同一个可点击链接。
35
37
  - session 失效时 CLI 只续期并重试原请求一次;再次失败立即返回。revoked v2 Device 不自动换身份。
36
38
  - 同一 Device 首次登记新的 Agent Type 时,CLI 只使用本地已登记且 Backend 仍接受的既有 Agent Instance 完成签名登记;被撤销的 Instance 会被跳过且不会恢复。若没有任何既有 Instance 可用,CLI 必须停止,不得重新登记 Device、旋转私钥或借用其他 Backend。
37
39
 
@@ -76,12 +78,15 @@ URL 内 credential 不得被提取、单独输出、记录或重建。
76
78
  | `codex-cli / terminal` | `url`;非 JSON 输出另外渲染终端二维码 |
77
79
  | `claude-code-cli / terminal` | `url`;非 JSON 输出另外渲染终端二维码 |
78
80
  | `workbuddy / plain-chat` | `url, agent_action`(`present_files(files=[url])`,打开完整渲染的 HTML Card Link) |
81
+ | `zcode / plain-chat` | `url`(立即用 ZCode 内置浏览器打开;不返回图片或平台私有 action) |
79
82
  | `kimi-code / terminal` | `url`;非 JSON 输出另外渲染终端二维码 |
80
83
  | `openclaw / telegram` | `url, qr_image_url, agent_action` |
81
84
  | `openclaw / other` | `url, qr_image_url` |
82
85
 
83
86
  WorkBuddy instruction 必须要求 Agent 原样执行一次 `handoff.agent_action`,即调用 `present_files(files=[handoff.url])` 在右侧打开 Backend 已渲染的 HTML Card。调用成功后说明金额并停止;调用失败时只发送原始 `handoff.url` 并如实报告未自动打开。禁止把 `present_files` 用于本地文件或二维码 PNG,也不能下载或重建二维码、调用 `pay` 或创建替代付款资源。显式 `--host` 仍覆盖默认展示方式。
84
87
 
88
+ ZCode instruction 必须明确要求 Agent 立即用内置浏览器打开 `handoff.url`,确认已发起打开后说明金额并停止。不得只把 URL 当作文字粘贴给用户,也不得下载、解析或重建二维码。只有内置浏览器明确不可用时,才把同一个 `handoff.url` 作为可点击链接展示。CLI 不猜测或输出 ZCode 私有工具调用 JSON。
89
+
85
90
  OpenClaw Telegram 的 `handoff.agent_action` 是可原样执行的原生 `message` tool action。`presentation` 只包含标准 `blocks.buttons`:`📱 手机点这儿支付` 使用扁平 `url`,`📋 已授权给我读` 使用扁平 `value=itp:grant_confirmed:<checkout_id>`;二维码单独使用 action 的 `media`。CLI `instruction` 必须要求 Agent 原样执行该 action,不得改写 Presentation、换用其他消息工具或声称普通文本回复等同于已发送按钮。收到授权 callback 后立即执行 `next.command` 查询同一 Checkout,再只跟随后端返回的同一 Execution grant 流程;callback 只携带 Checkout ID,不携带 display token,也不证明付款或 grant 已生效。OpenClaw `target` 使用原生 chat target(如 `5559456744` 或 `-1001234567890:topic:42`),不添加 `telegram:` 前缀。
86
91
 
87
92
  ### Purchased-content authorization
@@ -89,7 +94,7 @@ OpenClaw Telegram 的 `handoff.agent_action` 是可原样执行的原生 `messag
89
94
  `vault access` 使用相同字段集合,但不包含金额、Checkout ID、付款状态或付款
90
95
  查询命令。桌面 handoff 的 Markdown 标题和链接必须明确为“授权查看已购
91
96
  内容”;Terminal 显示授权二维码;WorkBuddy 用 `present_files` 打开完整
92
- `handoff.url`;OpenClaw 使用返回的图片/原生 action。
97
+ `handoff.url`;ZCode 用内置浏览器打开完整 `handoff.url`;OpenClaw 使用返回的图片/原生 action。
93
98
 
94
99
  授权 handoff 展示后 `next=null`。用户明确表示已完成时,Agent只重新执行
95
100
  产生授权要求的原始 `vault list`、`orders` 或 `vault read`,不得再次执行
@@ -135,7 +135,7 @@ itpay buy \
135
135
 
136
136
  ## 幂等与中断恢复
137
137
 
138
- - CLI 在本机 operation journal 中为 `checkout.create:<cart_id>` 保存稳定幂等键。
138
+ - CLI 在本机 `operations.json.d/`(dev 使用独立目录)中为 `checkout.create:<cart_id>` 保存一条不可变、owner-only 的稳定幂等记录;不同操作不共享全局锁。
139
139
  - HTTP 请求通过 `Idempotency-Key` 提交该键。
140
140
  - Checkout 响应丢失时,重跑同一命令会复用已保存的 canonical Cart 和同一幂等键。
141
141
  - 后端返回同一个待处理 Checkout,并轮换新的交接 token;不会创建第二笔订单。
@@ -168,6 +168,7 @@ itpay buy \
168
168
  | `claude-code-desktop` | `claude-code` | `url`、可用时 `qr_local_path` 和 `markdown` | 把 Markdown handoff 发到当前桌面对话,不能只输出本地路径。 |
169
169
  | `claude-code-cli` | `terminal` | `url` | 在用户可见终端展示;不能声称桌面对话已收到图片。 |
170
170
  | `workbuddy` | `plain-chat` | `url,agent_action` | 原样执行一次 `present_files(files=[url])`,在右侧打开完整渲染的 HTML Card Link,然后停止。 |
171
+ | `zcode` | `plain-chat` | `url` | 立即用 ZCode 内置浏览器打开 URL;只有浏览器不可用时才展示同一个可点击链接。 |
171
172
  | `kimi-code` | `terminal` | `url` | 使用标准 CLI 非 JSON 终端二维码和链接。 |
172
173
  | `openclaw` | 必须显式 | Telegram 返回 `url,qr_image_url,agent_action`;其他入口返回 `url,qr_image_url` | Telegram 执行原生 `message` action;其他入口直接展示图片和链接。 |
173
174
 
@@ -85,4 +85,4 @@ Quote 模式返回:
85
85
 
86
86
  ## Agent Type / Host
87
87
 
88
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种类型都写入真实 Agent Type。默认 Host 分别是 `codex`、`terminal`、`claude-code`、`terminal`、`plain-chat`。业务输出合同相同;此命令本身不显示二维码。
88
+ 所有正式支持的 Local Agent Type 都写入真实 Agent Type,并使用 Agent Type reference 声明的默认 Host。业务输出合同相同;此命令本身不显示二维码。
@@ -52,4 +52,4 @@ itpay cart clear [--local] [--json]
52
52
 
53
53
  ## Agent Type / Host
54
54
 
55
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 行为相同。
55
+ 所有正式支持的 Local Agent Type 行为相同。
@@ -29,4 +29,4 @@ itpay cart --help
29
29
 
30
30
  ## Agent Type / Host
31
31
 
32
- Cart 事实在 `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 下相同;`add` 创建 client context 时记录真实 Agent Type 和 Host。
32
+ Cart 事实在所有正式支持的 Local Agent Type 下相同;`add` 创建 client context 时记录真实 Agent Type 和 Host。
@@ -70,4 +70,4 @@ itpay buy --cart <cart_id> --json
70
70
 
71
71
  ## Agent Type / Host
72
72
 
73
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 的业务字段、instruction 和 next 相同。本命令不产生二维码或 Host handoff。
73
+ 所有正式支持的 Local Agent Type 的业务字段、instruction 和 next 相同。本命令不产生二维码或 Host handoff。
@@ -52,4 +52,4 @@ quote/checkout 已锁定时返回 `cart_item_locked`,要求继续已有 Checko
52
52
 
53
53
  ## Agent Type / Host
54
54
 
55
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 行为相同。
55
+ 所有正式支持的 Local Agent Type 行为相同。
@@ -64,4 +64,4 @@ canonical 输出不得包含 Catalog variant/offer、line input、Service quote
64
64
 
65
65
  ## Agent Type / Host
66
66
 
67
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 行为相同。
67
+ 所有正式支持的 Local Agent Type 行为相同。
@@ -25,4 +25,4 @@ itpay catalog --help
25
25
 
26
26
  ## Agent Type / Host
27
27
 
28
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种支持类型的目录事实完全相同。instruction 可按 Agent Type 调整措辞,但不得改变服务排序、价格或可用能力。
28
+ 所有正式支持的 Local Agent Type 的目录事实完全相同。instruction 可按 Agent Type 调整措辞,但不得改变服务排序、价格或可用能力。
@@ -44,4 +44,4 @@ itpay catalog list [--json]
44
44
 
45
45
  ## Agent Type / Host
46
46
 
47
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 使用同一产品内容;只允许排版不同。
47
+ 所有正式支持的 Local Agent Type 使用同一产品内容;只允许排版不同。
@@ -76,6 +76,7 @@ token 缺失或不匹配时使用本机句柄恢复。只有请求的 Checkout
76
76
  | `claude-code-desktop` | `url, qr_local_path, markdown`;原样发送 Markdown。 |
77
77
  | `claude-code-cli` | `url`;普通文本模式渲染终端二维码。 |
78
78
  | `workbuddy` | 返回 `url,agent_action`;原样执行一次 `present_files(files=[url])` 打开完整渲染的 HTML Card Link,不生成本地文件。 |
79
+ | `zcode` | 返回 `url`;立即用 ZCode 内置浏览器打开,不只粘贴文字链接,也不生成或重建二维码文件。 |
79
80
  | `kimi-code` | `url`;普通文本模式渲染标准终端二维码。 |
80
81
  | `openclaw` | Telegram 为 `url,qr_image_url,agent_action`;instruction 强制原样执行 action。`📋 已授权给我读` callback 触发同一 Checkout 查询,再由 Backend 决定是否进入 grant 读取;其他显式 Host 为 `url,qr_image_url`。 |
81
82
 
@@ -14,6 +14,14 @@ itpay --agent-type <agent_type> device recover --confirm-backend-reset --json
14
14
 
15
15
  命令只作用于当前官方 Backend 的 Device registration,并保留本地 Ed25519 私钥、Cart 和业务资源。默认是 `https://app.itpay.ai`;显式测试可使用准确的 `ITPAY_BACKEND_URL=https://dev.itpay.ai`。该命令不访问 Backend、不自动创建新身份;返回的只读 `services list` 会保留同一 Backend,是重新登记入口。
16
16
 
17
+ 同一台电脑上的多个 Local Agent Type 共享本地 Device key,但各自使用独立 Agent
18
+ Instance。CLI 只在原子更新 Device state 时使用短期本地锁;释放和 stale recovery
19
+ 通过 rename 完成,不依赖 Host 删除文件,因此 WorkBuddy 等 sandbox 的
20
+ safe-delete/trash shim 不应阻断正常命令。该锁不在 Backend,不会让另一台电脑或
21
+ 另一个 Buyer 等待。遇到本地锁错误时不得删除 `~/.itpay-v3/device`、切换 Agent
22
+ Type 或执行 `device recover`;应保留身份并重试原命令一次,持续失败时报告
23
+ `device_state_unwritable` 或 lock timeout。
24
+
17
25
  缺少确认参数返回 `backend_reset_confirmation_required`。普通 session 失效由 CLI 自动续期;revoked、quota、权限或未知 Backend 故障不得使用本命令。所有 Agent Type 使用相同输入和输出合同。
18
26
 
19
27
  ## 参数
@@ -27,4 +27,4 @@ itpay docs --help
27
27
 
28
28
  ## Agent Type / Host
29
29
 
30
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 使用同一文档源;topic 内容可以包含各类型专属 section
30
+ 所有正式支持的 Local Agent Type 使用同一文档源;topic 内容可以包含各类型专属 section。完整类型列表以 `itpay install --json` 和 Agent Type reference 为准。
@@ -50,4 +50,4 @@ itpay docs show <topic> --json
50
50
 
51
51
  ## Agent Type / Host
52
52
 
53
- 七种正式 Agent Type 返回相同结果。本命令没有 Host 渲染、设备登记或本地业务状态写入。
53
+ 所有正式 Agent Type 返回相同结果。本命令没有 Host 渲染、设备登记或本地业务状态写入。
@@ -68,4 +68,4 @@ itpay docs search <query> [--json]
68
68
 
69
69
  ## Agent Type / Host
70
70
 
71
- 七种正式 Agent Type 行为相同。本命令不产生 Host handoff。
71
+ 所有正式 Agent Type 行为相同。本命令不产生 Host handoff。
@@ -67,4 +67,4 @@ topic 不存在返回 `doc_not_found`:
67
67
 
68
68
  ## Agent Type / Host
69
69
 
70
- 七种正式 Agent Type 使用同一 topic。topic 若包含多种 Host 指导,Agent 只采用与自身 Agent Type 和当前 Host 匹配的部分。
70
+ 所有正式 Agent Type 使用同一 topic。topic 若包含多种 Host 指导,Agent 只采用与自身 Agent Type 和当前 Host 匹配的部分。
@@ -73,6 +73,9 @@ Markdown:
73
73
 
74
74
  没有用户评论时省略 Summary,但仍保存 Context。只写用户明确内容和已知安全上下文;禁止 Token、Session、联系方式、内部身份、
75
75
  Provider 响应、Vault payload、完整命令输出、stack trace 或环境变量。
76
+ CLI 不读取或上传 Host 对话、WorkBuddy/ZCode sandbox 日志或其他本地 Agent
77
+ 记录。用户自愿提供本地证据时,Agent 也只能提炼与本单有关的安全摘要,不能把原始
78
+ 日志作为 Feedback note 上传。
76
79
 
77
80
  ## 成功 JSON
78
81
 
@@ -18,10 +18,10 @@ itpay install [target] [--json]
18
18
 
19
19
  | 参数 | 必填 | 说明 |
20
20
  | --- | --- | --- |
21
- | `target` | 否 | 七种正式 Agent Type 之一;省略或传 `list` 时列出全部。Host 名称不是合法 target。 |
21
+ | `target` | 否 | 八种正式 Agent Type 之一;省略或传 `list` 时列出全部。Host 名称不是合法 target。 |
22
22
  | `--json` | 否 | 返回标准命令 envelope;推荐 Agent 使用。 |
23
23
 
24
- 正式 target:`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw`。
24
+ 正式 target:`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`zcode`、`kimi-code`、`openclaw`。
25
25
 
26
26
  ## 指定 Agent Type 输出
27
27
 
@@ -77,7 +77,7 @@ OpenClaw 额外明确没有默认入口:
77
77
 
78
78
  ## 列表输出
79
79
 
80
- 省略 target 或传 `list` 时返回七组运行时定义,不重复每种类型的完整 instruction:
80
+ 省略 target 或传 `list` 时返回八组运行时定义,不重复每种类型的完整 instruction:
81
81
 
82
82
  ```json
83
83
  {
@@ -89,6 +89,7 @@ OpenClaw 额外明确没有默认入口:
89
89
  { "agent_type": "claude-code-desktop", "default_host": "claude-code" },
90
90
  { "agent_type": "claude-code-cli", "default_host": "terminal" },
91
91
  { "agent_type": "workbuddy", "default_host": "plain-chat" },
92
+ { "agent_type": "zcode", "default_host": "plain-chat" },
92
93
  { "agent_type": "kimi-code", "default_host": "terminal" },
93
94
  {
94
95
  "agent_type": "openclaw",
@@ -120,6 +121,7 @@ OpenClaw 额外明确没有默认入口:
120
121
  | `claude-code-desktop` | `claude-code` | 桌面对话必须实际展示二维码和付款链接。 |
121
122
  | `claude-code-cli` | `terminal` | 只在用户可见终端展示付款交接。 |
122
123
  | `workbuddy` | `plain-chat` | 会持久化 `~/.itpay-v3` 的 Bash 调用先设 Shell 工具参数 `dangerouslyDisableSandbox=true`;拒绝权限时停止。Checkout 原样执行返回的 `present_files` action,打开渲染后的 Card Link。 |
124
+ | `zcode` | `plain-chat` | Checkout 或授权返回后立即用 ZCode 内置浏览器打开 `handoff.url`;只有浏览器不可用时才展示同一个可点击链接。 |
123
125
  | `kimi-code` | `terminal` | 使用标准 CLI 流程和终端展示,不增加 Kimi 专属命令。 |
124
126
  | `openclaw` | 无 | 必须显式提供当前入口;Telegram 使用原生 action,其他入口展示标准二维码和链接。 |
125
127
 
@@ -138,7 +140,7 @@ OpenClaw 额外明确没有默认入口:
138
140
  "code": "unsupported_agent_type",
139
141
  "message": "unsupported install target: codex"
140
142
  },
141
- "instruction": "target 只接受:codex-desktop, codex-cli, claude-code-desktop, claude-code-cli, workbuddy, kimi-code, openclaw。",
143
+ "instruction": "target 只接受:codex-desktop, codex-cli, claude-code-desktop, claude-code-cli, workbuddy, zcode, kimi-code, openclaw。",
142
144
  "next": null,
143
145
  "recovery": [
144
146
  {
@@ -86,4 +86,4 @@ Checkout 句柄返回:
86
86
 
87
87
  ## Agent Type / Host
88
88
 
89
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 的状态、instruction 和下一步完全相同。本命令不产生二维码或 Host handoff。
89
+ 所有正式支持的 Local Agent Type 的状态、instruction 和下一步完全相同。本命令不产生二维码或 Host handoff。
@@ -91,4 +91,4 @@ itpay services list --json
91
91
 
92
92
  ## Agent Type / Host
93
93
 
94
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 返回完全相同的订单事实、instruction 和 next。`order` 是状态读取命令,不构造二维码、Markdown handoff 或 Host renderer 数据。
94
+ 所有正式支持的 Local Agent Type 返回完全相同的订单事实、instruction 和 next。`order` 是状态读取命令,不构造二维码、Markdown handoff 或 Host renderer 数据。
@@ -103,6 +103,7 @@ API 安全合同要求后端验证 display token 是该 Checkout 当前有效的
103
103
  | `claude-code-desktop` | `claude-code` | 把安全 handoff 发到当前桌面对话。 |
104
104
  | `claude-code-cli` | `terminal` | 只在用户可见终端展示渠道动作。 |
105
105
  | `workbuddy` | `plain-chat` | 受控逃生入口返回 `handoff.url` 和可原样执行的 `present_files` action;打开一次后停止,不立即查询或创建替代付款。 |
106
+ | `zcode` | `plain-chat` | 受控逃生入口返回 `handoff.url`;同时存在渠道动作时优先使用浏览器可打开的 `qr_image_url`,只有它缺失时才回退到 `mobile_wallet_url`。立即用内置浏览器打开并停止,浏览器不可用时才展示同一个链接。 |
106
107
  | `kimi-code` | `terminal` | 使用标准 CLI 终端展示渠道动作。 |
107
108
  | `openclaw` | 必须显式提供 | 按显式 Host 返回安全渠道动作;IM Host 仍要求真实 target。 |
108
109
 
@@ -61,4 +61,4 @@ itpay refund cancel <refund_request_id> [--reason <reason>] [--json]
61
61
 
62
62
  ## Agent Type / Host
63
63
 
64
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 的业务字段、instruction 和 recovery 相同;该命令没有二维码或宿主渲染差异。
64
+ 所有正式支持的 Local Agent Type 的业务字段、instruction 和 recovery 相同;该命令没有二维码或宿主渲染差异。
@@ -84,4 +84,4 @@ CLI 使用 Device Authority 或已有 Buyer bearer,并用订单+原因生成
84
84
 
85
85
  ## Agent Type / Host
86
86
 
87
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 使用相同 Device Authority、退款政策和输出。未绑定订单时不得改用新 Device ID、Buyer ID 或开发者权限绕过 Owner 鉴权。
87
+ 所有正式支持的 Local Agent Type 使用相同 Device Authority、退款政策和输出。未绑定订单时不得改用新 Device ID、Buyer ID 或开发者权限绕过 Owner 鉴权。
@@ -64,4 +64,4 @@ CLI 不因为退款终态自行修改订单或 grant。
64
64
 
65
65
  ## Agent Type / Host
66
66
 
67
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 返回完全相同的退款事实、instruction 和 next。Host 不改变 Refund Owner 状态或访问锁。
67
+ 所有正式支持的 Local Agent Type 返回完全相同的退款事实、instruction 和 next。Host 不改变 Refund Owner 状态或访问锁。
@@ -40,4 +40,4 @@ itpay refund --help
40
40
 
41
41
  ## Agent Type / Host
42
42
 
43
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种类型使用同一签名 Device Authority 和退款状态机;Host 不影响退款资格。
43
+ 所有正式支持的 Local Agent Type 使用同一签名 Device Authority 和退款状态机;Host 不影响退款资格。
@@ -67,4 +67,4 @@ itpay refund list --order <order_id> [--json]
67
67
 
68
68
  ## Agent Type / Host
69
69
 
70
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 的业务字段、instruction 和 next 完全相同;本命令没有 Host 渲染差异。
70
+ 所有正式支持的 Local Agent Type 的业务字段、instruction 和 next 完全相同;本命令没有 Host 渲染差异。
@@ -72,4 +72,4 @@ Timeout 只表示本次 CLI 等待结束,不表示退款失败:
72
72
 
73
73
  ## Agent Type / Host
74
74
 
75
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 返回相同退款事实。Desktop 不会把每次无变化轮询发送到用户对话;Host 不改变 timeout 或退款状态。
75
+ 所有正式支持的 Local Agent Type 返回相同退款事实。Desktop 不会把每次无变化轮询发送到用户对话;Host 不改变 timeout 或退款状态。
@@ -47,4 +47,4 @@ rank 不存在、属于旧结果集或其他 Execution、action 不允许、stat
47
47
 
48
48
  ## Agent Type / Host
49
49
 
50
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 行为相同。需要人确认时 instruction 必须明确“先询问用户”,不能因 Desktop Host 自动代替用户选择。
50
+ 所有正式支持的 Local Agent Type 行为相同。需要人确认时 instruction 必须明确“先询问用户”,不能因 Desktop Host 自动代替用户选择。
@@ -83,6 +83,7 @@ itpay services checkout <service_execution_id> --resume
83
83
  | `claude-code-desktop` | `handoff={url,qr_local_path,markdown}`;把 `handoff.markdown` 原样发送到当前桌面对话。 |
84
84
  | `claude-code-cli` | `handoff={url}`;普通文本模式在用户可见终端渲染二维码。 |
85
85
  | `workbuddy` | `handoff={url,agent_action}`;原样执行一次 `present_files(files=[url])` 打开完整渲染的 HTML Card Link,然后停止;不得检查或生成本地文件。 |
86
+ | `zcode` | `handoff={url}`;立即用 ZCode 内置浏览器打开,然后停止;只有浏览器不可用时才展示同一个可点击链接。 |
86
87
  | `kimi-code` | `handoff={url}`;复用标准 CLI 终端展示。 |
87
88
  | `openclaw` | 必须显式传 Host;Telegram 还必须传 OpenClaw 原生 Target,并返回必须原样执行的 `message` action;其他入口返回标准 `url,qr_image_url`。 |
88
89
 
@@ -92,6 +93,12 @@ WorkBuddy 的准确 instruction 语义必须完整包含:
92
93
  Backend 尚未确认付款。立即严格按 handoff.agent_action.tool 和 handoff.agent_action.arguments 原样执行一次,在右侧打开 handoff.url;确认工具调用成功后说明金额,然后停止等待。若工具失败,只发送原始 handoff.url,报告未自动打开并停止。不要用 present_files 打开本地文件或二维码 PNG,不要下载或重建二维码,不要调用 pay,不要创建新 Checkout、Payment Intent 或 Execution。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。
93
94
  ```
94
95
 
96
+ ZCode 的准确 instruction 语义必须完整包含:
97
+
98
+ ```text
99
+ Backend 尚未确认付款。立即用 ZCode 内置浏览器打开 handoff.url,让用户完成付款;确认已发起打开后说明金额,然后停止等待。不要只粘贴文字链接,不要下载、解析或重建二维码,不要创建新 Checkout、Payment Intent 或 Execution。只有内置浏览器明确不可用时,才展示同一个可点击 handoff.url。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。
100
+ ```
101
+
95
102
  `--locale` 默认 `zh-CN`,可显式使用 `--locale en`。语言只影响 Card 渲染,不改变 Checkout、付款或恢复状态。
96
103
 
97
104
  OpenClaw 的 Host/Target 校验必须发生在读取 execution 后、创建 Checkout 前;校验失败不能调用 Checkout 创建接口。Telegram `agent_action.arguments.presentation` 只使用 `blocks.buttons`,其中 `📱 手机点这儿支付` 使用扁平 `url`,`📋 已授权给我读` 使用 `value=itp:grant_confirmed:<checkout_id>`。准确 instruction 必须要求 Agent 立即用原生 `message` tool 原样执行 `handoff.agent_action`,不得改写 Presentation、换工具或用普通回复冒充按钮已发送。收到该 callback 表示用户声明已在收银台授权读取;Agent 立即执行 `next.command` 查询同一 Checkout,再只按 Backend 返回的 `next.command` 进入同一 Execution grant 流程。callback 不包含 display token,也不证明付款或 grant active;Backend 未返回 `grant_active` 前不得读取结果。
@@ -72,4 +72,4 @@ CLI 只投影:`sequence`、`type`、`status`、`phase`、可选 `capability_id
72
72
 
73
73
  ## Agent Type / Host
74
74
 
75
- 七种正式 Agent Type 的事件字段、鉴权和 redaction 完全相同;Host 不影响可见性,也不产生 handoff。
75
+ 所有正式 Agent Type 的事件字段、鉴权和 redaction 完全相同;Host 不影响可见性,也不产生 handoff。
@@ -65,4 +65,4 @@ execution 不存在或不属于当前身份时保留不透明 `not_found`,只
65
65
 
66
66
  ## Agent Type / Host
67
67
 
68
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 的状态、timeline、instruction 和 next 相同。本命令没有二维码或 Host handoff。
68
+ 所有正式支持的 Local Agent Type 的状态、timeline、instruction 和 next 相同。本命令没有二维码或 Host handoff。
@@ -44,4 +44,4 @@ itpay services --help
44
44
 
45
45
  ## Agent Type / Host
46
46
 
47
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 共享状态机;Agent Type 只影响身份归属和 Host instruction,不允许影响 quota 规则或服务能力。
47
+ 所有正式支持的 Local Agent Type 共享状态机;Agent Type 只影响身份归属和 Host instruction,不允许影响 quota 规则或服务能力。
@@ -152,4 +152,4 @@ Provider 已收到请求时,Backend 返回同一 Execution 的权威调用和
152
152
 
153
153
  ## Agent Type / Host
154
154
 
155
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 的 safe result 一致。instruction 可以适配对话表述,但不得隐藏 quota、价格或 schema 错误。
155
+ 所有正式支持的 Local Agent Type 的 safe result 一致。instruction 可以适配对话表述,但不得隐藏 quota、价格或 schema 错误。
@@ -60,4 +60,4 @@ itpay services list [--limit <number>] [--json]
60
60
 
61
61
  ## Agent Type / Host
62
62
 
63
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 返回相同列表格式;Agent instance 权限决定可见范围。本命令没有 Host handoff。
63
+ 所有正式支持的 Local Agent Type 返回相同列表格式;Agent instance 权限决定可见范围。本命令没有 Host handoff。
@@ -209,4 +209,4 @@ itpay services get <service_execution_id> --json
209
209
 
210
210
  ## Agent Type / Host
211
211
 
212
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 返回完全相同的状态、safe payload、instruction 和 next。本命令不渲染二维码,也不包含 Host handoff。
212
+ 所有正式支持的 Local Agent Type 返回完全相同的状态、safe payload、instruction 和 next。本命令不渲染二维码,也不包含 Host handoff。
@@ -62,4 +62,4 @@ itpay services quote <service_execution_id> --capability <capability_id>
62
62
 
63
63
  ## Agent Type / Host
64
64
 
65
- `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 返回相同 Quote 事实、instruction 和 next。本命令不显示二维码;Agent Type 只作为设备与审计上下文,不改变价格或候选规则。
65
+ 所有正式支持的 Local Agent Type 返回相同 Quote 事实、instruction 和 next。本命令不显示二维码;Agent Type 只作为设备与审计上下文,不改变价格或候选规则。
@@ -97,4 +97,4 @@ itpay services next <id> --json
97
97
 
98
98
  ## Agent Type / Host
99
99
 
100
- 同一 Buyer account 下已登记的 `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 可按政策领取同一订单授权;每个类型仍需自己的有效 Device Authority。七种类型返回相同字段、TTL 和错误,不因 Host 扩大 grant scope。
100
+ 同一 Buyer account 下已登记的正式 Local Agent Type 可按政策领取同一订单授权;每个类型仍需自己的有效 Device Authority。所有类型返回相同字段、TTL 和错误,不因 Host 扩大 grant scope。
@@ -52,9 +52,12 @@ Agent不得提取、单独输出、记录或重建其中的 credential;但不
52
52
  | `codex-cli / terminal` | `url`;文本模式同时渲染终端二维码 |
53
53
  | `claude-code-cli / terminal` | `url`;文本模式同时渲染终端二维码 |
54
54
  | `workbuddy / plain-chat` | `url, agent_action` |
55
+ | `zcode / plain-chat` | `url`;立即用内置浏览器打开,浏览器不可用时才展示可点击链接 |
55
56
  | `kimi-code / terminal` | `url`;文本模式同时渲染终端二维码 |
56
57
  | `openclaw / telegram` | `url, qr_image_url, agent_action` |
57
58
  | `openclaw / other` | `url, qr_image_url` |
58
59
 
59
60
  桌面二维码下载失败时保留 `handoff.url`,instruction 必须要求如实说明图片
60
61
  未显示并发送同一个 URL;不得创建替代请求。
62
+
63
+ ZCode 不接收图片路径或二维码 URL。instruction 必须要求 Agent立即用内置浏览器打开完整 `handoff.url`,然后停止等待;不得只粘贴文字链接或重建二维码。仅当内置浏览器明确不可用时才展示同一个可点击链接。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itpay/cli",
3
- "version": "2.0.35",
3
+ "version": "2.0.37",
4
4
  "description": "The ItPay CLI for services, orders, and human-authorized purchased content.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,6 +30,7 @@
30
30
  "claude-code",
31
31
  "codex",
32
32
  "openclaw",
33
+ "zcode",
33
34
  "trae"
34
35
  ],
35
36
  "license": "MIT",
@@ -51,6 +51,11 @@ itpay docs search <keyword> --json
51
51
 
52
52
  The current Backend response always overrides general documentation.
53
53
 
54
+ When a handoff returns an official URL, open it yourself on the current
55
+ platform whenever possible. Only show the same clickable URL when no browser
56
+ or native action is available; never ask the human to run a command or rebuild
57
+ a QR code.
58
+
54
59
  ## Serve The Human
55
60
 
56
61
  - Ask the human only to choose, authorize, pay, provide required contact