@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.
- package/README.md +26 -20
- 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 +12 -8
- 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 +14 -8
- 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 +131 -82
- 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 +58 -36
|
@@ -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
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
|
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": [
|
|
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
|
@@ -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,
|
|
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;
|
|
@@ -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
|
|
1615
|
+
instruction,
|
|
1645
1616
|
openclaw_message: {
|
|
1646
1617
|
command: "openclaw message send",
|
|
1647
|
-
command_args:
|
|
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
|
|
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("
|
|
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
|
}
|