@itpay/cli 0.2.12 → 0.2.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.
@@ -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
@@ -3,8 +3,9 @@ import {
3
3
  readSessionToken, readState, safeErrorMessage, sleep, splitCSV, stripInternalBuyerFields, updateRun, writeConfig,
4
4
  writeCredentials, writeSessionCredentials, writeState
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;
@@ -1646,6 +1605,7 @@ function telegramBuyerInstruction(value = {}) {
1646
1605
  command: "openclaw message send",
1647
1606
  command_args: compactObject({
1648
1607
  channel: "telegram",
1608
+ target: clientTarget(flags) || undefined,
1649
1609
  message: text,
1650
1610
  media: media || undefined,
1651
1611
  presentation
@@ -1664,6 +1624,59 @@ function telegramBuyerInstruction(value = {}) {
1664
1624
  });
1665
1625
  }
1666
1626
 
1627
+ function markdownBuyerInstruction(value = {}, flags = {}, platform = "codex_app", markdown = "") {
1628
+ const plan = value.render_plan || {};
1629
+ const summary = buyerOrderSummary(value, plan.kind);
1630
+ return handoffInstructionOutput(value, flags, {
1631
+ type: plan.kind === "auth_qr" ? "send_markdown_auth_handoff" : "send_markdown_payment_handoff",
1632
+ platform,
1633
+ proof_rule: plan.proof_rule,
1634
+ instruction: "必须先把 markdown 原样发给用户;不要先运行 wait/status。只有 payment_intent.verified 才算付款成功。",
1635
+ markdown
1636
+ }, summary);
1637
+ }
1638
+
1639
+ function plainChatBuyerInstruction(value = {}, flags = {}, selected = {}) {
1640
+ const plan = value.render_plan || {};
1641
+ const summary = buyerOrderSummary(value, plan.kind);
1642
+ return handoffInstructionOutput(value, flags, {
1643
+ type: plan.kind === "auth_qr" ? "send_plain_auth_handoff" : "send_plain_payment_handoff",
1644
+ platform: "plain_chat",
1645
+ proof_rule: plan.proof_rule,
1646
+ instruction: "把 message 和 links 发给用户后停下,等用户回复再查状态。",
1647
+ message: selected.text,
1648
+ links: selected.links || []
1649
+ }, summary);
1650
+ }
1651
+
1652
+ function terminalBuyerInstruction(value = {}, flags = {}) {
1653
+ const plan = value.render_plan || {};
1654
+ const summary = buyerOrderSummary(value, plan.kind);
1655
+ return handoffInstructionOutput(value, flags, {
1656
+ type: plan.kind === "auth_qr" ? "terminal_auth_handoff" : "terminal_payment_handoff",
1657
+ platform: "terminal",
1658
+ proof_rule: plan.proof_rule,
1659
+ instruction: "CLI 已负责终端展示;用户回复后再运行 after_human_response 里的命令。",
1660
+ print_terminal_qr: true
1661
+ }, summary);
1662
+ }
1663
+
1664
+ function handoffInstructionOutput(value = {}, flags = {}, agentInstruction = {}, summary = {}) {
1665
+ return compactObject({
1666
+ status: value.status,
1667
+ must_send_to_human_before_next_command: true,
1668
+ order_summary: summary,
1669
+ agent_instruction: agentInstruction,
1670
+ after_human_response: genericAfterHumanResponse(value, flags, summary),
1671
+ agent_next_actions: ["send_agent_instruction_to_human"],
1672
+ next: {
1673
+ type: "reply_to_human",
1674
+ safe_for_agent: false,
1675
+ instruction: "先把 agent_instruction 发送给用户;用户点击按钮或回复后,再运行 after_human_response 里的命令。"
1676
+ }
1677
+ });
1678
+ }
1679
+
1667
1680
  function buyerOrderSummary(value = {}, kind = "") {
1668
1681
  const cart = value.cart || {};
1669
1682
  const checkout = value.checkout || {};
@@ -1758,6 +1771,19 @@ function telegramAfterHumanResponse(value = {}, callbacks = {}) {
1758
1771
  });
1759
1772
  }
1760
1773
 
1774
+ function genericAfterHumanResponse(value = {}, flags = {}, summary = {}) {
1775
+ const paymentIntentID = summary.payment_intent_id;
1776
+ const checkoutID = summary.checkout_id;
1777
+ return compactObject({
1778
+ check_status_command: value.after_human_response?.check_payment_command ||
1779
+ (paymentIntentID ? paymentStatusCheckCommand(paymentIntentID, flags) : undefined) ||
1780
+ (checkoutID ? cliCommand("buyer", "checkout", "resume", checkoutID, ...clientCommandArgs(flags), "--json") : undefined),
1781
+ refresh_qr_command: value.after_human_response?.refresh_qr_command,
1782
+ help_command: cliCommand("docs", "show", "payment-qr", "--role", "buyer", "--json"),
1783
+ instruction: "用户点击查询按钮或回复已完成后,运行 check_status_command。用户口头说已付款不是证明。"
1784
+ });
1785
+ }
1786
+
1761
1787
  function compactBuyerField(key, value) {
1762
1788
  if (key === "selection") return compactSelection(value);
1763
1789
  if (key === "selections") return Array.isArray(value) ? value.map(compactSelection) : value;
@@ -1931,7 +1957,7 @@ function buyerDocsFor(value = {}) {
1931
1957
  topics.add("cart-checkout");
1932
1958
  topics.add("payment-qr");
1933
1959
  }
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) {
1960
+ 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
1961
  topics.add("payment-qr");
1936
1962
  topics.add("payment-wait");
1937
1963
  }
@@ -0,0 +1,126 @@
1
+ const SUPPORTED_HOSTS = new Set(["codex", "claude-code", "telegram", "discord", "whatsapp", "terminal", "plain-chat"]);
2
+ const RUNTIME_TARGETS = new Set(["codex", "claude-code", "openclaw", "generic"]);
3
+ const EXEMPT_GROUPS = new Set(["", "--help", "-h", "help", "version", "docs", "skill", "doctor", "install"]);
4
+
5
+ function clientContextGate(args = []) {
6
+ const group = args[0] || "";
7
+ if (EXEMPT_GROUPS.has(group)) return null;
8
+ const flags = parseArgFlags(args.slice(1));
9
+ const host = clientHost(flags);
10
+ if (!host) return clientContextRequired();
11
+ if (!SUPPORTED_HOSTS.has(host)) return unsupportedClientContext(host);
12
+ if (requiresTarget(host) && !clientTarget(flags)) return clientTargetRequired(host);
13
+ return null;
14
+ }
15
+
16
+ function clientHost(flags = {}) {
17
+ const raw = firstValue(
18
+ flags.host,
19
+ flags.client,
20
+ flags.channel,
21
+ argFlag("host"),
22
+ argFlag("client"),
23
+ argFlag("channel")
24
+ );
25
+ const host = normalizeHost(raw);
26
+ if (host === "openclaw" && flags.channel) return normalizeHost(flags.channel);
27
+ return host;
28
+ }
29
+
30
+ function clientTarget(flags = {}) {
31
+ const target = firstValue(flags.chat_target, flags.reply_target, argFlag("chat-target"), argFlag("reply-target"));
32
+ if (target) return target;
33
+ const flagTarget = flags.target || argFlag("target");
34
+ return flagTarget && !RUNTIME_TARGETS.has(normalizeHost(flagTarget)) ? String(flagTarget) : "";
35
+ }
36
+
37
+ function clientCommandArgs(flags = {}) {
38
+ const host = clientHost(flags);
39
+ const target = clientTarget(flags);
40
+ return [
41
+ ...(host ? ["--host", host] : []),
42
+ ...(target ? ["--target", target] : [])
43
+ ];
44
+ }
45
+
46
+ function requiresTarget(host) {
47
+ return host === "telegram" || host === "discord" || host === "whatsapp";
48
+ }
49
+
50
+ function clientContextRequired() {
51
+ return {
52
+ schema_version: "itp.client_context.v1",
53
+ status: "client_context_required",
54
+ must_rerun: true,
55
+ instruction: "Rerun the same itp command with --host <client>. ItPay will not guess the current chat/app client.",
56
+ allowed_hosts: Array.from(SUPPORTED_HOSTS),
57
+ examples: {
58
+ codex: "itp ... --host codex --json",
59
+ claude_code: "itp ... --host claude-code --json",
60
+ telegram: "itp ... --host telegram --target telegram:<chat_id> --json",
61
+ terminal: "itp ... --host terminal --json"
62
+ }
63
+ };
64
+ }
65
+
66
+ function clientTargetRequired(host) {
67
+ return {
68
+ schema_version: "itp.client_context.v1",
69
+ status: "client_target_required",
70
+ must_rerun: true,
71
+ host,
72
+ instruction: "Rerun the same itp command with the current chat target. For OpenClaw Telegram group/private chat, pass inbound_meta.chat_id as --target.",
73
+ examples: {
74
+ telegram_private: "itp ... --host telegram --target telegram:5559456744 --json",
75
+ telegram_group: "itp ... --host telegram --target telegram:-1001234567890 --json",
76
+ discord: "itp ... --host discord --target discord:<channel_id> --json"
77
+ }
78
+ };
79
+ }
80
+
81
+ function unsupportedClientContext(host) {
82
+ return {
83
+ schema_version: "itp.client_context.v1",
84
+ status: "unsupported_client_context",
85
+ must_rerun: true,
86
+ host,
87
+ instruction: "Use one supported --host value so ItPay can return a single executable human-output instruction.",
88
+ allowed_hosts: Array.from(SUPPORTED_HOSTS)
89
+ };
90
+ }
91
+
92
+ function normalizeHost(value = "") {
93
+ const normalized = String(value || "").trim().toLowerCase().replaceAll("_", "-");
94
+ if (["tg", "openclaw-telegram"].includes(normalized)) return "telegram";
95
+ if (["codex-app", "codex-cli"].includes(normalized)) return "codex";
96
+ if (["claude", "claudecode", "claude-code-app"].includes(normalized)) return "claude-code";
97
+ if (["plain", "plain-chat", "chat"].includes(normalized)) return "plain-chat";
98
+ return normalized;
99
+ }
100
+
101
+ function parseArgFlags(args = []) {
102
+ const flags = {};
103
+ for (let i = 0; i < args.length; i += 1) {
104
+ const arg = args[i];
105
+ if (!String(arg).startsWith("--")) continue;
106
+ const key = String(arg).slice(2).replaceAll("-", "_");
107
+ const next = args[i + 1];
108
+ if (!next || String(next).startsWith("--")) {
109
+ flags[key] = true;
110
+ } else {
111
+ flags[key] = next;
112
+ i += 1;
113
+ }
114
+ }
115
+ return flags;
116
+ }
117
+
118
+ function argFlag(name) {
119
+ return parseArgFlags(process.argv.slice(2))[String(name).replaceAll("-", "_")] || "";
120
+ }
121
+
122
+ function firstValue(...values) {
123
+ return values.find((value) => value !== undefined && value !== null && value !== false && String(value).trim() !== "") || "";
124
+ }
125
+
126
+ export { clientCommandArgs, clientContextGate, clientHost, clientTarget };