@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.
- package/README.md +25 -19
- package/bin/itp +38 -32
- package/docs/agent/buyer/account-portal.json +22 -6
- package/docs/agent/buyer/cart-checkout.json +9 -9
- package/docs/agent/buyer/catalog-search.json +35 -8
- package/docs/agent/buyer/human-claim-ui.json +20 -6
- package/docs/agent/buyer/payment-qr.json +9 -7
- package/docs/agent/buyer/payment-wait.json +4 -3
- package/docs/agent/buyer/product-recommendation.json +1 -1
- package/docs/agent/buyer/qr-refresh.json +18 -5
- package/docs/agent/buyer/quickstart.json +10 -6
- package/docs/agent/buyer/recovery.json +25 -6
- package/docs/agent/buyer/safety-policy.json +17 -3
- package/docs/agent/buyer/secure-delivery.json +24 -6
- package/docs/agent/buyer/vault-agent-read.json +29 -10
- package/lib/buyer.js +99 -73
- package/lib/client-context.js +126 -0
- package/lib/render-human.js +10 -75
- package/package.json +1 -1
- package/skills/itpay-buyer/SKILL.md +51 -34
|
@@ -11,23 +11,29 @@
|
|
|
11
11
|
"Checkout or delivery is pending."
|
|
12
12
|
],
|
|
13
13
|
"required_state": {
|
|
14
|
-
"needs": [
|
|
15
|
-
|
|
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": [
|
|
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": [
|
|
14
|
-
|
|
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": [
|
|
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": [
|
|
14
|
-
|
|
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": [
|
|
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": [
|
|
14
|
-
|
|
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": [
|
|
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,
|
|
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
|
|
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(``);
|
|
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
|
-
|
|
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 ["
|
|
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
|
|
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
|
|
1605
|
-
if (
|
|
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
|
|
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("
|
|
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 };
|