@itpay/cli 0.2.12 → 0.2.14

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.
@@ -24,7 +24,7 @@
24
24
  "commands": [
25
25
  {
26
26
  "intent": "verify whether this agent/device already has a server-valid buyer session",
27
- "command": "itp status --refresh --json",
27
+ "command": "itp status --refresh --host <client> --json",
28
28
  "success_signal": "response.status is idle when authenticated, or response.next.command tells the agent how to start human auth; if response.recoverable_context.found is true, compare it with the current user intent before continuing or ignoring it"
29
29
  },
30
30
  {
@@ -34,23 +34,26 @@
34
34
  },
35
35
  {
36
36
  "intent": "search the public catalog for the current user request",
37
- "command": "itp buyer catalog search --query \"<user request>\" --json",
37
+ "command": "itp buyer catalog search --query \"<user request>\" --host <client> --json",
38
38
  "success_signal": "response.products contains public services and purchase options; response.catalog_guidance tells the agent how to compare and when to ask the human"
39
39
  },
40
40
  {
41
41
  "intent": "search standardized API services with category/facet filters when intent is clear",
42
- "command": "itp buyer catalog search --query \"企业工商信息 查询\" --category business_data_api --provider itpay_enterprise_data --service-type ai_api --json",
42
+ "command": "itp buyer catalog search --query \"企业工商信息 查询\" --category business_data_api --provider itpay_enterprise_data --service-type ai_api --host <client> --json",
43
43
  "success_signal": "response.products are narrowed by category/facet before recommendation"
44
44
  },
45
45
  {
46
46
  "intent": "buy a selected variant with the high-level cart-first flow",
47
- "command": "itp buy <variant_id> --email <buyer_email> --phone <buyer_phone> --display agent --no-wait-payment --json",
48
- "success_signal": "response.status becomes waiting_human_auth or payment_handoff_required; in OpenClaw Telegram, if agent_instruction is present, send response.agent_instruction.openclaw_message.command_args before any payment status check"
47
+ "command": "itp buy <variant_id> --email <buyer_email> --phone <buyer_phone> --display agent --no-wait-payment --host <client> --json",
48
+ "success_signal": "response.status becomes waiting_human_auth or payment_handoff_required; in OpenClaw Telegram, if agent_action is present, execute response.agent_action.command or response.agent_action.argv before any normal reply or payment status check"
49
49
  }
50
50
  ],
51
51
  "agent_rules": [
52
52
  "Use --json for every ItPay command.",
53
- "Use `itp status --refresh --json` before account, order, refund, or repeat-purchase decisions. Plain local memory is not identity.",
53
+ "Every non-doc ItPay command must include the current client context: --host codex, --host claude-code, --host terminal, --host plain-chat, or for OpenClaw/Telegram use --host telegram --target <inbound_meta.chat_id>.",
54
+ "Do not rely on the CLI to guess Telegram, Discord, WhatsApp, Codex, or Claude Code. If ItPay returns client_context_required or client_target_required, rerun the same command with the required --host/--target fields before continuing.",
55
+ "For Telegram group chat or @mention flows, pass the group/private chat target from OpenClaw inbound metadata, for example --target telegram:-1001234567890. ItPay only echoes the target into agent_action; OpenClaw owns group allowlist/mention policy.",
56
+ "Use `itp status --refresh --host <client> --json` before account, order, refund, or repeat-purchase decisions. Plain local memory is not identity.",
54
57
  "If `status --refresh` returns `recoverable_context.found=true`, do not blindly resume or blindly ignore it. Decide whether the old task is related to the current user intent; continue related tasks, ignore clearly unrelated tasks, and ask the human when unsure.",
55
58
  "If the catalog is unclear, read catalog-search docs and use buyer shelf manifest/snapshot as the shopping-list view before guessing keywords.",
56
59
  "Search and explain purchase options before buying unless the user already named a purchase option ID.",
@@ -63,12 +66,15 @@
63
66
  "When a buyer command returns buyer_session.status=buyer_session_saved, the CLI has stored the buyer account session for this agent device. You may continue with checkout/payment or `buyer vault` commands without asking the human for a token.",
64
67
  "Vault grants are only for reading approved delivered artifacts. They are not buyer session credentials and do not authorize refunds or account/order management.",
65
68
  "Use high-level itp buy when possible, but in agent app clients split payment into two visible steps: first return and send the human handoff instruction, then only check status after the human responds or uses a platform button.",
66
- "In OpenClaw Telegram, if response.agent_instruction is present, call openclaw message send with response.agent_instruction.openclaw_message.command_args. It includes the QR media, exact message, and native button presentation.",
69
+ "If response.agent_action is present, it is the mandatory execution path for the current client. Execute response.agent_action.command or response.agent_action.argv before any normal prose reply.",
70
+ "In OpenClaw Telegram, response.agent_action is already the exact openclaw message send command. It includes target, QR media, exact message, and native button presentation.",
71
+ "If response.agent_action cannot be executed, stop and tell the human this agent lacks OpenClaw native message-send capability. Do not fall back to Markdown or handwritten prose.",
72
+ "response.agent_instruction is natural-language reinforcement for the agent; it does not replace response.agent_action.",
67
73
  "If response.status is payment_handoff_required, do not run payment wait from next.command; next is the human-visible reply. Only after the human asks to check payment should you run after_human_response.check_payment_command.",
68
74
  "Show the returned payment QR exactly as provided before starting payment wait.",
69
75
  "Payment truth comes only from payment_intent.verified.",
70
76
  "Secure delivery goes to the human first; report redacted delivery status only.",
71
- "For OpenClaw Telegram, root agent_instruction is the execution path. Do not use handwritten prose when agent_instruction.openclaw_message.command_args is present."
77
+ "For OpenClaw Telegram, root agent_action is the execution path. Do not use handwritten prose when agent_action is present."
72
78
  ],
73
79
  "forbidden": [
74
80
  "Do not call ops commands from the buyer path.",
@@ -11,23 +11,29 @@
11
11
  "Checkout or delivery is pending."
12
12
  ],
13
13
  "required_state": {
14
- "needs": ["last known checkout_id, payment_intent_id, or variant_id"],
15
- "must_not_need": ["ops token", "provider raw payload", "claim token"]
14
+ "needs": [
15
+ "last known checkout_id, payment_intent_id, or variant_id"
16
+ ],
17
+ "must_not_need": [
18
+ "ops token",
19
+ "provider raw payload",
20
+ "claim token"
21
+ ]
16
22
  },
17
23
  "commands": [
18
24
  {
19
25
  "intent": "resume checkout state",
20
- "command": "itp buyer checkout resume <checkout_id> --json",
26
+ "command": "itp buyer checkout resume <checkout_id> --host <client> --json",
21
27
  "success_signal": "response.status and agent_next_actions describe the safe next action"
22
28
  },
23
29
  {
24
30
  "intent": "wait again on the same payment intent",
25
- "command": "itp buyer payment wait <payment_intent_id> --timeout 1 --json",
31
+ "command": "itp buyer payment wait <payment_intent_id> --timeout 1 --host <client> --json",
26
32
  "success_signal": "response.payment_event.event_type is payment_intent.verified or still waiting"
27
33
  },
28
34
  {
29
35
  "intent": "refresh display QR only",
30
- "command": "itp buyer payment refresh-qr <payment_intent_id> --reason order-not-found --json",
36
+ "command": "itp buyer payment refresh-qr <payment_intent_id> --reason order-not-found --host <client> --json",
31
37
  "success_signal": "response.payment_intent.qr_image_url is present"
32
38
  }
33
39
  ],
@@ -62,5 +68,18 @@
62
68
  "command": "itp docs show secure-delivery --role buyer --json"
63
69
  }
64
70
  ],
65
- "search_terms": ["recover", "resume", "timeout", "lost", "retry", "stuck", "恢复", "重试", "卡住", "没返回", "断了", "找不到"]
71
+ "search_terms": [
72
+ "recover",
73
+ "resume",
74
+ "timeout",
75
+ "lost",
76
+ "retry",
77
+ "stuck",
78
+ "恢复",
79
+ "重试",
80
+ "卡住",
81
+ "没返回",
82
+ "断了",
83
+ "找不到"
84
+ ]
66
85
  }
@@ -10,8 +10,13 @@
10
10
  "The agent is deciding between buyer and ops commands."
11
11
  ],
12
12
  "required_state": {
13
- "needs": ["role=buyer"],
14
- "must_not_need": ["ops token", "raw secret access"]
13
+ "needs": [
14
+ "role=buyer"
15
+ ],
16
+ "must_not_need": [
17
+ "ops token",
18
+ "raw secret access"
19
+ ]
15
20
  },
16
21
  "commands": [
17
22
  {
@@ -52,5 +57,14 @@
52
57
  "command": "itp docs show secure-delivery --role buyer --json"
53
58
  }
54
59
  ],
55
- "search_terms": ["policy", "forbidden", "security", "secret", "ops", "安全", "禁止", "权限"]
60
+ "search_terms": [
61
+ "policy",
62
+ "forbidden",
63
+ "security",
64
+ "secret",
65
+ "ops",
66
+ "安全",
67
+ "禁止",
68
+ "权限"
69
+ ]
56
70
  }
@@ -10,23 +10,30 @@
10
10
  "The user asks where the purchased key/content is."
11
11
  ],
12
12
  "required_state": {
13
- "needs": ["checkout_id"],
14
- "must_not_need": ["claim token", "claim link", "raw key", "storage ref"]
13
+ "needs": [
14
+ "checkout_id"
15
+ ],
16
+ "must_not_need": [
17
+ "claim token",
18
+ "claim link",
19
+ "raw key",
20
+ "storage ref"
21
+ ]
15
22
  },
16
23
  "commands": [
17
24
  {
18
25
  "intent": "check redacted delivery status",
19
- "command": "itp buyer checkout status <checkout_id> --json",
26
+ "command": "itp buyer checkout status <checkout_id> --host <client> --json",
20
27
  "success_signal": "delivery.sensitive_content_redacted == true"
21
28
  },
22
29
  {
23
30
  "intent": "list agent-safe delivery state",
24
- "command": "itp buyer deliveries list --checkout <checkout_id> --json",
31
+ "command": "itp buyer deliveries list --checkout <checkout_id> --host <client> --json",
25
32
  "success_signal": "response.deliveries contains no raw content or claim token"
26
33
  },
27
34
  {
28
35
  "intent": "after the human says they granted agent access, discover the current agent's grants",
29
- "command": "itp buyer vault grants list --checkout <checkout_id> --json",
36
+ "command": "itp buyer vault grants list --checkout <checkout_id> --host <client> --json",
30
37
  "success_signal": "response.agent_readable_grants contains only grants for this exact agent device"
31
38
  }
32
39
  ],
@@ -68,5 +75,16 @@
68
75
  "command": "itp docs show recovery --role buyer --json"
69
76
  }
70
77
  ],
71
- "search_terms": ["delivery", "claim", "email", "key", "redeem", "secure delivery", "交付", "邮件", "领取", "密钥"]
78
+ "search_terms": [
79
+ "delivery",
80
+ "claim",
81
+ "email",
82
+ "key",
83
+ "redeem",
84
+ "secure delivery",
85
+ "交付",
86
+ "邮件",
87
+ "领取",
88
+ "密钥"
89
+ ]
72
90
  }
@@ -10,35 +10,45 @@
10
10
  "A checkout/order has delivered a vault artifact and the human granted agent-readable access."
11
11
  ],
12
12
  "required_state": {
13
- "needs": ["checkout_id or authenticated buyer account session", "current agent device binding", "order_id or checkout_id or vault_artifact_id"],
14
- "must_not_need": ["human portal token", "claim token", "passkey credential", "raw protected payload", "grant id copied by the human"]
13
+ "needs": [
14
+ "checkout_id or authenticated buyer account session",
15
+ "current agent device binding",
16
+ "order_id or checkout_id or vault_artifact_id"
17
+ ],
18
+ "must_not_need": [
19
+ "human portal token",
20
+ "claim token",
21
+ "passkey credential",
22
+ "raw protected payload",
23
+ "grant id copied by the human"
24
+ ]
15
25
  },
16
26
  "commands": [
17
27
  {
18
28
  "intent": "check whether this agent has active human-approved grants for a checkout",
19
- "command": "itp buyer vault grants list --checkout <checkout_id> --json",
29
+ "command": "itp buyer vault grants list --checkout <checkout_id> --host <client> --json",
20
30
  "success_signal": "response.agent_readable_grants is an array; count > 0 means the human has granted this exact agent access"
21
31
  },
22
32
  {
23
33
  "intent": "check whether this agent has active human-approved grants for one order or artifact",
24
- "command": "itp buyer vault grants list --order <order_id> --artifact <vault_artifact_id> --json",
34
+ "command": "itp buyer vault grants list --order <order_id> --artifact <vault_artifact_id> --host <client> --json",
25
35
  "success_signal": "only grants scoped to this logged-in agent device are returned"
26
36
  },
27
37
  {
28
38
  "intent": "read a discovered grant view",
29
- "command": "itp buyer vault grants read <agent_read_grant_id> --json",
39
+ "command": "itp buyer vault grants read <agent_read_grant_id> --host <client> --json",
30
40
  "success_signal": "response.grant contains selected_fields, structured_view, or summary according to the human-approved scope"
31
41
  },
32
42
  {
33
43
  "intent": "convenience read by order/artifact without asking the human for grant id",
34
- "command": "itp buyer vault read --order <order_id> --artifact <vault_artifact_id> --json",
44
+ "command": "itp buyer vault read --order <order_id> --artifact <vault_artifact_id> --host <client> --json",
35
45
  "success_signal": "the CLI discovers the active grant and reads its scoped view"
36
46
  }
37
47
  ],
38
48
  "agent_rules": [
39
49
  "Only read through `buyer vault` commands after the human has explicitly approved the agent grant in the ItPay portal.",
40
- "Prefer `itp buyer vault grants list --checkout <checkout_id> --json`; the CLI can automatically restore the buyer agent session from the checkout auth handoff when possible.",
41
- "If the response includes buyer_session.status=buyer_session_saved, continue directly to `buyer vault grants read <agent_read_grant_id> --json`; do not ask the human for session tokens, auth IDs, or grant IDs.",
50
+ "Prefer `itp buyer vault grants list --checkout <checkout_id> --host <client> --json`; the CLI can automatically restore the buyer agent session from the checkout auth handoff when possible.",
51
+ "If the response includes buyer_session.status=buyer_session_saved, continue directly to `buyer vault grants read <agent_read_grant_id> --host <client> --json`; do not ask the human for session tokens, auth IDs, or grant IDs.",
42
52
  "Do not ask the human to copy or paste `agent_read_grant_id`; discover it with `buyer vault grants list`.",
43
53
  "If no grant is returned, tell the human to open their ItPay account portal, reveal with Passkey, choose fields, and confirm one-key agent authorization.",
44
54
  "If no grant is returned, explain likely causes: the human has not authorized yet, selected the wrong order/page, authorization has not synced yet, checkout/order/artifact does not match, buyer session expired, or the grant expired/revoked.",
@@ -46,7 +56,7 @@
46
56
  "Use only fields returned in the grant view. Do not infer that unreturned fields are accessible.",
47
57
  "When a grant view is returned, tell the user which fields are readable. If expires_at, ttl, or valid_until is present, tell the user the time limit; if no expiry is returned, say the response did not include an explicit expiry.",
48
58
  "If the returned fields are not enough for the user's task, ask the human to reopen Give to Agent and add the needed fields. Do not ask them to paste the missing raw content.",
49
- "If the command still returns 401 or buyer_session_invalid after using `--checkout`, run `itp buyer checkout status <checkout_id> --json`, then retry `itp buyer vault grants list --checkout <checkout_id> --json`. Ask the human to reauthorize only if the checkout/auth handoff has expired."
59
+ "If the command still returns 401 or buyer_session_invalid after using `--checkout`, run `itp buyer checkout status <checkout_id> --host <client> --json`, then retry `itp buyer vault grants list --checkout <checkout_id> --host <client> --json`. Ask the human to reauthorize only if the checkout/auth handoff has expired."
50
60
  ],
51
61
  "forbidden": [
52
62
  "Do not open, click through, scrape, or automate the human web UI yourself.",
@@ -72,5 +82,14 @@
72
82
  "command": "itp docs show safety-policy --role buyer --json"
73
83
  }
74
84
  ],
75
- "search_terms": ["vault", "agent read", "passkey grant", "one key agent", "授权给 agent", "可读授权", "字段授权", "查看报告"]
85
+ "search_terms": [
86
+ "vault",
87
+ "agent read",
88
+ "passkey grant",
89
+ "one key agent",
90
+ "授权给 agent",
91
+ "可读授权",
92
+ "字段授权",
93
+ "查看报告"
94
+ ]
76
95
  }
package/lib/buyer.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  appendURLQuery, booleanFlag, cliCommand, cryptoRandom, csvValues, deleteSessionCredential, intFlag, output, positional, readConfig, readCredentials,
3
3
  readSessionToken, readState, safeErrorMessage, sleep, splitCSV, stripInternalBuyerFields, updateRun, writeConfig,
4
- writeCredentials, writeSessionCredentials, writeState
4
+ writeCredentials, writeSessionCredentials, writeState, shellQuote
5
5
  } from "./env.js";
6
+ import { clientCommandArgs, clientHost, clientTarget } from "./client-context.js";
6
7
  import { coreApi, coreApiBase } from "./http.js";
7
- import { buildHumanActionRenderPlan, humanOutputFromRenderPlan, renderHumanAction, renderItPayPaymentAction, shouldReturnAfterAgentTextQR, writeWaitHeartbeat } from "./render-human.js";
8
+ import { buildHumanActionRenderPlan, renderHumanAction, renderItPayPaymentAction, shouldReturnAfterAgentTextQR, writeWaitHeartbeat } from "./render-human.js";
8
9
 
9
10
  async function buyerBuy(flags) {
10
11
  rejectBuyerSandboxFlag(flags);
@@ -395,7 +396,7 @@ async function buyer(command, rest, flags) {
395
396
  agent_next_actions: event.agent_next_actions || intent.agent_next_actions || paymentAgentNextActions(intent, event),
396
397
  next: event.event_type === "payment_intent.verified"
397
398
  ? { command: intent.checkout_id ? cliCommand("buyer", "checkout", "status", intent.checkout_id, "--json") : undefined, safe_for_agent: true }
398
- : { command: paymentStatusCheckCommand(paymentIntentID), safe_for_agent: true }
399
+ : { command: paymentStatusCheckCommand(paymentIntentID, flags), safe_for_agent: true }
399
400
  }));
400
401
  return;
401
402
  }
@@ -1093,49 +1094,14 @@ function paymentAgentNextActions(intent = {}, event = {}) {
1093
1094
 
1094
1095
  function paymentHandoffFields(intent = {}, flags = {}) {
1095
1096
  return {
1096
- payment_handoff: paymentHandoff(intent),
1097
1097
  render_plan: buildHumanActionRenderPlan(intent.human_action || {}, intent, flags),
1098
- after_human_response: paymentHandoffAfterHumanResponse(intent)
1098
+ after_human_response: paymentHandoffAfterHumanResponse(intent, flags)
1099
1099
  };
1100
1100
  }
1101
1101
 
1102
- function paymentHandoff(intent = {}) {
1103
- const action = intent.human_action || {};
1104
- const paymentIntentID = intent.payment_intent_id || action.id || "";
1105
- const entryURL = intent.payment_entry_url || intent.payment_url || action.url || "";
1106
- const qrPNGURL = action.qr_png_url || intent.qr_png_url || intent.qr?.png_url || "";
1107
- const preferredQRURL = action.preferred_qr_url || qrPNGURL || action.qr_image_url || intent.qr_image_url || intent.qr?.image_url || "";
1108
- const localQRPath = action.local_qr_path || intent.local_qr_path || "";
1109
- const mobileWalletURL = action.mobile_wallet_url || intent.mobile_wallet_url || "";
1110
- return {
1111
- type: "payment_qr_handoff",
1112
- payment_intent_id: paymentIntentID || null,
1113
- checkout_id: intent.checkout_id || null,
1114
- primary: localQRPath ? "local_qr_path" : (qrPNGURL ? "qr_png_url" : (preferredQRURL ? "preferred_qr_url" : "payment_entry_url")),
1115
- local_qr_path: localQRPath || null,
1116
- qr_png_url: qrPNGURL || null,
1117
- preferred_qr_url: preferredQRURL || null,
1118
- payment_entry_url: entryURL || null,
1119
- mobile_wallet_url: mobileWalletURL || null,
1120
- markdown: paymentHandoffMarkdown({ localQRPath, qrPNGURL, preferredQRURL, entryURL, mobileWalletURL }),
1121
- safe_for_agent: true,
1122
- instruction: "Render the QR/link to the human first. After the human responds or uses a platform button, query the same payment_intent_id. Only payment_intent.verified proves payment."
1123
- };
1124
- }
1125
-
1126
- function paymentHandoffMarkdown({ localQRPath = "", qrPNGURL = "", preferredQRURL = "", entryURL = "", mobileWalletURL = "" } = {}) {
1127
- const imageURL = localQRPath || qrPNGURL || preferredQRURL;
1128
- const lines = ["请扫码付款:"];
1129
- if (imageURL) lines.push(`![ItPay payment QR](${imageURL})`);
1130
- if (entryURL) lines.push(`[打开付款页面](${entryURL})`);
1131
- if (mobileWalletURL) lines.push(`[手机钱包打开](${mobileWalletURL})`);
1132
- lines.push("付款后回复“我已付款”,我会查询真实支付状态。");
1133
- return lines.join("\n\n");
1134
- }
1135
-
1136
- function paymentStatusCheckCommand(paymentIntentID) {
1102
+ function paymentStatusCheckCommand(paymentIntentID, flags = {}) {
1137
1103
  if (!paymentIntentID) return "";
1138
- return cliCommand("buyer", "payment", "wait", paymentIntentID, "--timeout", "1", "--json");
1104
+ return cliCommand("buyer", "payment", "wait", paymentIntentID, "--timeout", "1", ...clientCommandArgs(flags), "--json");
1139
1105
  }
1140
1106
 
1141
1107
  function shouldReturnPaymentHandoffBeforeWait(flags = {}) {
@@ -1185,36 +1151,26 @@ async function optionalBuyerPaymentContext(intent = {}, flags = {}) {
1185
1151
  }
1186
1152
 
1187
1153
  function isTelegramBuyerHost(flags = {}) {
1188
- const raw = String(
1189
- process.env.ITP_HOST ||
1190
- flags.host ||
1191
- flags.channel ||
1192
- process.env.OPENCLAW_CURRENT_CHANNEL_PROVIDER ||
1193
- process.env.OPENCLAW_CHANNEL ||
1194
- process.env.CURRENT_CHANNEL_PROVIDER ||
1195
- process.env.AGENT_CHANNEL ||
1196
- ""
1197
- ).toLowerCase().replaceAll("-", "_");
1198
- return raw === "telegram" || raw === "tg";
1154
+ return clientHost(flags) === "telegram";
1199
1155
  }
1200
1156
 
1201
1157
  function paymentHandoffAgentNextActions() {
1202
- return ["show_human_visible_markdown"];
1158
+ return ["send_agent_instruction_to_human"];
1203
1159
  }
1204
1160
 
1205
1161
  function paymentHandoffNext() {
1206
1162
  return {
1207
1163
  type: "reply_to_human",
1208
1164
  safe_for_agent: false,
1209
- instruction: "Send human_visible_markdown to the human now. Do not run payment wait until this message is visible to the human."
1165
+ instruction: "Send agent_instruction to the human now. Do not run payment wait until this message is visible to the human."
1210
1166
  };
1211
1167
  }
1212
1168
 
1213
- function paymentHandoffAfterHumanResponse(intent = {}) {
1169
+ function paymentHandoffAfterHumanResponse(intent = {}, flags = {}) {
1214
1170
  const paymentIntentID = intent.payment_intent_id || intent.human_action?.id || "";
1215
1171
  return {
1216
- check_payment_command: paymentStatusCheckCommand(paymentIntentID),
1217
- refresh_qr_command: paymentIntentID ? cliCommand("buyer", "payment", "refresh-qr", paymentIntentID, "--reason", "order-not-found", "--json") : "",
1172
+ check_payment_command: paymentStatusCheckCommand(paymentIntentID, flags),
1173
+ refresh_qr_command: paymentIntentID ? cliCommand("buyer", "payment", "refresh-qr", paymentIntentID, "--reason", "order-not-found", ...clientCommandArgs(flags), "--json") : "",
1218
1174
  safe_for_agent: true,
1219
1175
  instruction: "Use check_payment_command after the human says they paid or a platform button requests a status check. User text is not proof; only payment_intent.verified is."
1220
1176
  };
@@ -1586,8 +1542,8 @@ function isBuyerDeliveryComplete(result) {
1586
1542
  checkout.agent_next_actions?.includes("stop_check_email");
1587
1543
  }
1588
1544
 
1589
- function buyerRunOutput(value = {}) {
1590
- const body = compactBuyerOutput(value);
1545
+ function buyerRunOutput(value = {}, flags = {}) {
1546
+ const body = compactBuyerOutput(value, flags);
1591
1547
  return normalizeBuyerMoneyFields(stripInternalBuyerFields({
1592
1548
  schema_version: "itp.buyer.v1",
1593
1549
  ...body,
@@ -1600,21 +1556,11 @@ function buyerRunOutput(value = {}) {
1600
1556
  }));
1601
1557
  }
1602
1558
 
1603
- function compactBuyerOutput(value = {}) {
1604
- const telegramInstruction = telegramBuyerInstruction(value);
1605
- if (telegramInstruction) return telegramInstruction;
1559
+ function compactBuyerOutput(value = {}, flags = {}) {
1560
+ const clientInstruction = clientBuyerInstruction(value, flags);
1561
+ if (clientInstruction) return clientInstruction;
1606
1562
  const result = {};
1607
1563
  if (value.status !== undefined) result.status = value.status;
1608
- if (value.payment_handoff?.markdown) {
1609
- result.must_reply_to_human_before_next_command = true;
1610
- result.human_visible_markdown = value.payment_handoff.markdown;
1611
- }
1612
- const humanOutput = humanOutputFromRenderPlan(value.render_plan);
1613
- if (humanOutput) {
1614
- result.human_output_required = true;
1615
- result.must_send_human_output_before_next_command = true;
1616
- result.human_output = humanOutput;
1617
- }
1618
1564
  for (const [key, item] of Object.entries(value)) {
1619
1565
  if (["docs", "status"].includes(key)) continue;
1620
1566
  result[key] = compactBuyerField(key, item);
@@ -1622,7 +1568,20 @@ function compactBuyerOutput(value = {}) {
1622
1568
  return result;
1623
1569
  }
1624
1570
 
1625
- function telegramBuyerInstruction(value = {}) {
1571
+ function clientBuyerInstruction(value = {}, flags = {}) {
1572
+ const plan = value.render_plan || {};
1573
+ const selected = plan.selected || {};
1574
+ if (!plan.kind || !selected.platform) return null;
1575
+ if (selected.platform === "telegram" && selected.openclaw_message) return telegramBuyerInstruction(value, flags);
1576
+ if ((selected.platform === "codex_app" || selected.platform === "claude_code") && selected.markdown) {
1577
+ return markdownBuyerInstruction(value, flags, selected.platform, selected.markdown);
1578
+ }
1579
+ if (selected.platform === "plain_chat") return plainChatBuyerInstruction(value, flags, selected);
1580
+ if (selected.platform === "terminal") return terminalBuyerInstruction(value, flags);
1581
+ return null;
1582
+ }
1583
+
1584
+ function telegramBuyerInstruction(value = {}, flags = {}) {
1626
1585
  const plan = value.render_plan || {};
1627
1586
  const selected = plan.selected || {};
1628
1587
  if (selected.platform !== "telegram" || !selected.openclaw_message) return null;
@@ -1633,33 +1592,110 @@ function telegramBuyerInstruction(value = {}) {
1633
1592
  const hasHumanEntry = Boolean(media || presentation.blocks?.some((block) => block.type === "buttons" && block.buttons?.some((button) => button.url)));
1634
1593
  const text = telegramBuyerInstructionText(plan.kind, summary, hasHumanEntry);
1635
1594
  const callbacks = rawMessage.callbacks || {};
1595
+ const commandArgs = compactObject({
1596
+ channel: "telegram",
1597
+ target: clientTarget(flags) || undefined,
1598
+ message: text,
1599
+ media: media || undefined,
1600
+ presentation
1601
+ });
1602
+ const action = telegramAgentAction(commandArgs);
1603
+ const instruction = "必须先执行顶层 agent_action.command 或 agent_action.argv,把二维码、说明和 Telegram 原生按钮发给用户。不要手写 Markdown 表格,不要只贴链接,不要先运行 wait/status;如果不能执行 openclaw message send,就停止并说明工具缺失。";
1636
1604
  return compactObject({
1637
1605
  status: value.status,
1606
+ instruction,
1638
1607
  must_send_to_human_before_next_command: true,
1608
+ must_execute_agent_action_before_reply: true,
1639
1609
  order_summary: summary,
1610
+ agent_action: action,
1640
1611
  agent_instruction: {
1641
1612
  type: plan.kind === "auth_qr" ? "send_telegram_auth_handoff" : "send_telegram_payment_handoff",
1642
1613
  platform: "telegram",
1643
1614
  proof_rule: plan.proof_rule,
1644
- instruction: "必须使用 openclaw_message.command_args 发送给用户。不要用自然语言伪造按钮,不要先运行 wait/status。只有 payment_intent.verified 才算付款成功。",
1615
+ instruction,
1645
1616
  openclaw_message: {
1646
1617
  command: "openclaw message send",
1647
- command_args: compactObject({
1648
- channel: "telegram",
1649
- message: text,
1650
- media: media || undefined,
1651
- presentation
1652
- }),
1618
+ command_args: commandArgs,
1653
1619
  telegram_rule: "Telegram 原生按钮必须通过 openclaw message send --presentation 发送;presentation.blocks[].type=\"buttons\" 才会生成 Inline Keyboard。",
1654
1620
  callbacks
1655
1621
  }
1656
1622
  },
1657
1623
  after_human_response: telegramAfterHumanResponse(value, callbacks),
1624
+ agent_next_actions: ["execute_agent_action"],
1625
+ next: {
1626
+ type: "reply_to_human",
1627
+ safe_for_agent: false,
1628
+ instruction: "先执行 agent_action.command/argv;用户点击按钮或回复后,再运行 after_human_response 里的命令。"
1629
+ }
1630
+ });
1631
+ }
1632
+
1633
+ function telegramAgentAction(commandArgs = {}) {
1634
+ const argv = ["openclaw", "message", "send"];
1635
+ if (commandArgs.channel) argv.push("--channel", commandArgs.channel);
1636
+ if (commandArgs.target) argv.push("--target", commandArgs.target);
1637
+ if (commandArgs.message) argv.push("--message", commandArgs.message);
1638
+ if (commandArgs.media) argv.push("--media", commandArgs.media);
1639
+ if (commandArgs.presentation) argv.push("--presentation", JSON.stringify(commandArgs.presentation));
1640
+ return {
1641
+ type: "openclaw_message_send",
1642
+ tool: "shell",
1643
+ instruction: "现在立刻执行这个 shell 命令发送 Telegram 原生消息;不要改写为普通聊天回复。执行成功后再简短告诉用户已发出。",
1644
+ command: argv.map((item) => shellQuote(String(item))).join(" "),
1645
+ argv,
1646
+ if_unavailable: "如果当前 agent 不能执行 openclaw message send,停止并告诉用户:当前 agent 缺少 Telegram 原生按钮发送工具;不要降级成 Markdown 表格或普通链接。"
1647
+ };
1648
+ }
1649
+
1650
+ function markdownBuyerInstruction(value = {}, flags = {}, platform = "codex_app", markdown = "") {
1651
+ const plan = value.render_plan || {};
1652
+ const summary = buyerOrderSummary(value, plan.kind);
1653
+ return handoffInstructionOutput(value, flags, {
1654
+ type: plan.kind === "auth_qr" ? "send_markdown_auth_handoff" : "send_markdown_payment_handoff",
1655
+ platform,
1656
+ proof_rule: plan.proof_rule,
1657
+ instruction: "必须先把 markdown 原样发给用户;不要先运行 wait/status。只有 payment_intent.verified 才算付款成功。",
1658
+ markdown
1659
+ }, summary);
1660
+ }
1661
+
1662
+ function plainChatBuyerInstruction(value = {}, flags = {}, selected = {}) {
1663
+ const plan = value.render_plan || {};
1664
+ const summary = buyerOrderSummary(value, plan.kind);
1665
+ return handoffInstructionOutput(value, flags, {
1666
+ type: plan.kind === "auth_qr" ? "send_plain_auth_handoff" : "send_plain_payment_handoff",
1667
+ platform: "plain_chat",
1668
+ proof_rule: plan.proof_rule,
1669
+ instruction: "把 message 和 links 发给用户后停下,等用户回复再查状态。",
1670
+ message: selected.text,
1671
+ links: selected.links || []
1672
+ }, summary);
1673
+ }
1674
+
1675
+ function terminalBuyerInstruction(value = {}, flags = {}) {
1676
+ const plan = value.render_plan || {};
1677
+ const summary = buyerOrderSummary(value, plan.kind);
1678
+ return handoffInstructionOutput(value, flags, {
1679
+ type: plan.kind === "auth_qr" ? "terminal_auth_handoff" : "terminal_payment_handoff",
1680
+ platform: "terminal",
1681
+ proof_rule: plan.proof_rule,
1682
+ instruction: "CLI 已负责终端展示;用户回复后再运行 after_human_response 里的命令。",
1683
+ print_terminal_qr: true
1684
+ }, summary);
1685
+ }
1686
+
1687
+ function handoffInstructionOutput(value = {}, flags = {}, agentInstruction = {}, summary = {}) {
1688
+ return compactObject({
1689
+ status: value.status,
1690
+ must_send_to_human_before_next_command: true,
1691
+ order_summary: summary,
1692
+ agent_instruction: agentInstruction,
1693
+ after_human_response: genericAfterHumanResponse(value, flags, summary),
1658
1694
  agent_next_actions: ["send_agent_instruction_to_human"],
1659
1695
  next: {
1660
1696
  type: "reply_to_human",
1661
1697
  safe_for_agent: false,
1662
- instruction: "先把 agent_instruction.openclaw_message.command_args 发送给用户;用户点击按钮或回复后,再运行 after_human_response 里的命令。"
1698
+ instruction: "先把 agent_instruction 发送给用户;用户点击按钮或回复后,再运行 after_human_response 里的命令。"
1663
1699
  }
1664
1700
  });
1665
1701
  }
@@ -1758,6 +1794,19 @@ function telegramAfterHumanResponse(value = {}, callbacks = {}) {
1758
1794
  });
1759
1795
  }
1760
1796
 
1797
+ function genericAfterHumanResponse(value = {}, flags = {}, summary = {}) {
1798
+ const paymentIntentID = summary.payment_intent_id;
1799
+ const checkoutID = summary.checkout_id;
1800
+ return compactObject({
1801
+ check_status_command: value.after_human_response?.check_payment_command ||
1802
+ (paymentIntentID ? paymentStatusCheckCommand(paymentIntentID, flags) : undefined) ||
1803
+ (checkoutID ? cliCommand("buyer", "checkout", "resume", checkoutID, ...clientCommandArgs(flags), "--json") : undefined),
1804
+ refresh_qr_command: value.after_human_response?.refresh_qr_command,
1805
+ help_command: cliCommand("docs", "show", "payment-qr", "--role", "buyer", "--json"),
1806
+ instruction: "用户点击查询按钮或回复已完成后,运行 check_status_command。用户口头说已付款不是证明。"
1807
+ });
1808
+ }
1809
+
1761
1810
  function compactBuyerField(key, value) {
1762
1811
  if (key === "selection") return compactSelection(value);
1763
1812
  if (key === "selections") return Array.isArray(value) ? value.map(compactSelection) : value;
@@ -1931,7 +1980,7 @@ function buyerDocsFor(value = {}) {
1931
1980
  topics.add("cart-checkout");
1932
1981
  topics.add("payment-qr");
1933
1982
  }
1934
- if (status.includes("payment_handoff") || status.includes("waiting_user_payment") || actions.includes("show_human_visible_markdown") || actions.includes("wait_payment") || value.payment_intent?.human_action || value.payment_intent?.qr_image_url) {
1983
+ if (status.includes("payment_handoff") || status.includes("waiting_user_payment") || actions.includes("send_agent_instruction_to_human") || actions.includes("wait_payment") || value.payment_intent?.human_action || value.payment_intent?.qr_image_url) {
1935
1984
  topics.add("payment-qr");
1936
1985
  topics.add("payment-wait");
1937
1986
  }