@itpay/cli 2.0.32 → 2.0.33
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/dist/src/client/backend.js +3 -0
- package/dist/src/commands/feedback.js +143 -0
- package/dist/src/commands/guidance.js +6 -0
- package/dist/src/commands/order.js +2 -2
- package/dist/src/commands/services.js +29 -10
- package/dist/src/main.js +45 -0
- package/dist/src/state/config.js +1 -1
- package/docs/agent/buyer/orders-refunds.json +4 -0
- package/docs/agent/buyer/quickstart.json +8 -1
- package/docs/agent/buyer/service-feedback.json +69 -0
- package/docs/cli-reference/commands/feedback/index.md +29 -0
- package/docs/cli-reference/commands/feedback/submit.md +123 -0
- package/docs/cli-reference/index.md +5 -0
- package/package.json +1 -1
- package/skills/itpay/SKILL.md +8 -1
|
@@ -58,6 +58,9 @@ export class BackendClient {
|
|
|
58
58
|
getOrderDeliveryAccess(orderID) {
|
|
59
59
|
return this.http.get(`/v1/orders/${encodeURIComponent(orderID)}/delivery-access`);
|
|
60
60
|
}
|
|
61
|
+
submitServiceFeedback(orderID, input) {
|
|
62
|
+
return this.http.post(`/v1/orders/${encodeURIComponent(orderID)}/feedback`, input);
|
|
63
|
+
}
|
|
61
64
|
listAccountOrders(limit, status, bearer, cursor) {
|
|
62
65
|
const qs = new URLSearchParams({ limit: String(limit) });
|
|
63
66
|
if (status) {
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { CLI_VERSION } from "../state/config.js";
|
|
2
|
+
import { CommandContractError, writeCommandEnvelope } from "./guidance.js";
|
|
3
|
+
const MAX_FEEDBACK_NOTE_CODE_POINTS = 2000;
|
|
4
|
+
export async function runFeedbackSubmit(backend, orderID, options) {
|
|
5
|
+
const normalizedOrderID = orderID?.trim() ?? "";
|
|
6
|
+
if (!normalizedOrderID) {
|
|
7
|
+
throw new CommandContractError("order_required", "--order is required", "先恢复用户所说的原订单;Agent 自己取得订单,不要求用户提供内部 ID。本次未提交反馈。", [{ command: "itpay orders --json", reason: "列出当前授权账号的订单摘要" }]);
|
|
8
|
+
}
|
|
9
|
+
const rating = normalizeFeedbackRating(options.rating);
|
|
10
|
+
const itemRank = normalizeItemRank(options.itemRank);
|
|
11
|
+
const userNote = normalizeUserNote(options.note ?? "");
|
|
12
|
+
const order = await backend.getOrder(normalizedOrderID);
|
|
13
|
+
const choices = feedbackItemChoices(order);
|
|
14
|
+
if (choices.length === 0) {
|
|
15
|
+
throw new CommandContractError("feedback_unavailable", "this order has no service item available for feedback", "告诉用户这笔订单当前没有可评价的服务项目并停止;不要猜测项目 ID、切换身份或创建其他反馈。", []);
|
|
16
|
+
}
|
|
17
|
+
if (choices.length > 1 && itemRank === undefined) {
|
|
18
|
+
writeCommandEnvelope({
|
|
19
|
+
status: "feedback_item_selection_required",
|
|
20
|
+
result: {
|
|
21
|
+
...(order.order_code ? { order_code: order.order_code } : {}),
|
|
22
|
+
items: choices.map(({ rank, title, subject }) => ({ rank, title, ...(subject ? { subject } : {}) })),
|
|
23
|
+
},
|
|
24
|
+
instruction: "用服务名称和主题让用户选择要评价哪一项;不要展示内部 ID。用户选择后,Agent 使用同一订单、评分和留言并加入所选 item rank 自己执行提交。",
|
|
25
|
+
next: null,
|
|
26
|
+
recovery: [],
|
|
27
|
+
}, outputOptions(options));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const choice = itemRank === undefined
|
|
31
|
+
? choices[0]
|
|
32
|
+
: choices.find((candidate) => candidate.rank === itemRank);
|
|
33
|
+
if (!choice) {
|
|
34
|
+
throw new CommandContractError("feedback_item_invalid", `item rank ${itemRank} is not available for feedback`, "只使用当前订单返回的服务项目编号;不要猜测内部 ID。本次未提交反馈。", []);
|
|
35
|
+
}
|
|
36
|
+
const note = formatFeedbackNote({
|
|
37
|
+
userNote,
|
|
38
|
+
outcome: order.status,
|
|
39
|
+
serviceTitle: choice.title,
|
|
40
|
+
environment: options.environment,
|
|
41
|
+
...(options.agentType ? { agentType: options.agentType } : {}),
|
|
42
|
+
});
|
|
43
|
+
if (codePointLength(note) > MAX_FEEDBACK_NOTE_CODE_POINTS) {
|
|
44
|
+
throw new CommandContractError("feedback_note_too_long", `formatted feedback note exceeds ${MAX_FEEDBACK_NOTE_CODE_POINTS} Unicode code points`, "请用户缩短反馈内容;不要截断、改写或拆分提交。本次未提交反馈。", []);
|
|
45
|
+
}
|
|
46
|
+
const response = await backend.submitServiceFeedback(normalizedOrderID, {
|
|
47
|
+
order_item_id: choice.item.order_item_id,
|
|
48
|
+
rating,
|
|
49
|
+
note,
|
|
50
|
+
});
|
|
51
|
+
writeCommandEnvelope({
|
|
52
|
+
status: "feedback_submitted",
|
|
53
|
+
result: {
|
|
54
|
+
...(order.order_code ? { order_code: order.order_code } : {}),
|
|
55
|
+
service_title: choice.title,
|
|
56
|
+
rating: response.feedback.rating,
|
|
57
|
+
feedback_status: response.feedback.status,
|
|
58
|
+
},
|
|
59
|
+
instruction: "告诉用户反馈已经记录并表示感谢,然后停止。不要承诺回复、处理时间、退款或结果变更。",
|
|
60
|
+
next: null,
|
|
61
|
+
recovery: [],
|
|
62
|
+
}, outputOptions(options));
|
|
63
|
+
}
|
|
64
|
+
export function normalizeFeedbackRating(value) {
|
|
65
|
+
const normalized = value?.trim().toLowerCase() ?? "";
|
|
66
|
+
const numeric = normalized.match(/^([1-5])(?:\s*(?:\/\s*5|分|星|stars?))?$/u);
|
|
67
|
+
if (numeric)
|
|
68
|
+
return Number(numeric[1]);
|
|
69
|
+
const chinese = normalized.match(/^([一二三四五])(?:分|星)?$/u)?.[1];
|
|
70
|
+
if (chinese)
|
|
71
|
+
return { 一: 1, 二: 2, 三: 3, 四: 4, 五: 5 }[chinese];
|
|
72
|
+
throw new CommandContractError("feedback_rating_invalid", "--rating must be an explicit score from 1 to 5", "请用户明确给出 1–5 分;不要从好评、差评或情绪推断评分。本次未提交反馈。", []);
|
|
73
|
+
}
|
|
74
|
+
function normalizeItemRank(value) {
|
|
75
|
+
if (value === undefined)
|
|
76
|
+
return undefined;
|
|
77
|
+
const match = value.trim().match(/^#?([1-9]\d*)$/u);
|
|
78
|
+
if (!match) {
|
|
79
|
+
throw new CommandContractError("feedback_item_invalid", "--item-rank must be a positive integer", "使用当前订单返回的正整数服务项目编号;本次未提交反馈。", []);
|
|
80
|
+
}
|
|
81
|
+
const rank = Number(match[1]);
|
|
82
|
+
if (!Number.isSafeInteger(rank)) {
|
|
83
|
+
throw new CommandContractError("feedback_item_invalid", "--item-rank is outside the supported integer range", "使用当前订单返回的服务项目编号;本次未提交反馈。", []);
|
|
84
|
+
}
|
|
85
|
+
return rank;
|
|
86
|
+
}
|
|
87
|
+
function feedbackItemChoices(order) {
|
|
88
|
+
return order.items.flatMap((item, index) => {
|
|
89
|
+
if (!item.order_item_id?.trim())
|
|
90
|
+
return [];
|
|
91
|
+
const subject = feedbackSubject(item);
|
|
92
|
+
return [{
|
|
93
|
+
rank: index + 1,
|
|
94
|
+
item,
|
|
95
|
+
title: safeLine(item.title) || "未命名服务",
|
|
96
|
+
...(subject ? { subject } : {}),
|
|
97
|
+
}];
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function feedbackSubject(item) {
|
|
101
|
+
for (const field of ["company_name_or_credit_no", "company_name", "company", "target", "keyword"]) {
|
|
102
|
+
const value = item.input?.[field];
|
|
103
|
+
if (typeof value === "string") {
|
|
104
|
+
const safe = safeLine(value);
|
|
105
|
+
if (safe)
|
|
106
|
+
return safe.slice(0, 160);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
function normalizeUserNote(value) {
|
|
112
|
+
return value
|
|
113
|
+
.replaceAll("\r\n", "\n")
|
|
114
|
+
.replaceAll("\r", "\n")
|
|
115
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/gu, " ")
|
|
116
|
+
.trim();
|
|
117
|
+
}
|
|
118
|
+
function formatFeedbackNote(input) {
|
|
119
|
+
const summary = input.userNote
|
|
120
|
+
? `## Summary\n${input.userNote.split("\n").map((line) => `> ${line}`).join("\n")}\n\n`
|
|
121
|
+
: "";
|
|
122
|
+
return `${summary}## Context\n` + [
|
|
123
|
+
"- Source: user-confirmed via agent",
|
|
124
|
+
`- Outcome: ${safeLine(input.outcome) || "unknown"}`,
|
|
125
|
+
`- Service: ${safeLine(input.serviceTitle) || "未命名服务"}`,
|
|
126
|
+
`- Client: @itpay/cli ${CLI_VERSION}`,
|
|
127
|
+
`- Agent type: ${safeLine(input.agentType ?? "unspecified")}`,
|
|
128
|
+
`- Environment: ${input.environment}`,
|
|
129
|
+
].join("\n");
|
|
130
|
+
}
|
|
131
|
+
function safeLine(value) {
|
|
132
|
+
return value.replace(/[\r\n\u0000-\u001F\u007F]+/gu, " ").trim();
|
|
133
|
+
}
|
|
134
|
+
function codePointLength(value) {
|
|
135
|
+
return Array.from(value).length;
|
|
136
|
+
}
|
|
137
|
+
function outputOptions(options) {
|
|
138
|
+
return {
|
|
139
|
+
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
140
|
+
...(options.output ? { output: options.output } : {}),
|
|
141
|
+
...(options.agentType ? { agentType: options.agentType } : {}),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -5,6 +5,12 @@ import { qualifyBackendCommand } from "../state/config.js";
|
|
|
5
5
|
export function isTerminalServiceExecutionStatus(status) {
|
|
6
6
|
return status === "failed" || status === "refunded" || status === "cancelled";
|
|
7
7
|
}
|
|
8
|
+
export function appendOptionalFeedbackInvitation(instruction, outcome) {
|
|
9
|
+
const invitation = outcome === "delivered"
|
|
10
|
+
? "结果解释完毕后,可以询问用户是否愿意给这次服务 1–5 分和一句可选建议。只有用户明确评分后,Agent 才使用同一订单提交反馈;不要展示命令或内部 ID,同一对话最多询问一次。"
|
|
11
|
+
: "先完成同一订单的恢复和退款权利解释;处理清楚后,可以询问用户是否愿意给 1–5 分并说明卡壳点。只有用户明确评分后才提交反馈,同一对话最多询问一次。";
|
|
12
|
+
return `${instruction} ${invitation}`;
|
|
13
|
+
}
|
|
8
14
|
export class CommandContractError extends Error {
|
|
9
15
|
code;
|
|
10
16
|
instruction;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { formatMoney } from "../render/output.js";
|
|
2
|
-
import { writeCommandEnvelope } from "./guidance.js";
|
|
2
|
+
import { appendOptionalFeedbackInvitation, writeCommandEnvelope } from "./guidance.js";
|
|
3
3
|
export async function runOrder(backend, orderID, options = {}) {
|
|
4
4
|
const order = await backend.getOrder(orderID);
|
|
5
5
|
const [delivery, refundResponse] = await Promise.all([
|
|
@@ -29,7 +29,7 @@ function orderEnvelope(order, delivery, lockedRefund) {
|
|
|
29
29
|
next = { command: `itpay services next ${delivery.service_execution_id} --json`, reason: "读取交付状态" };
|
|
30
30
|
}
|
|
31
31
|
else if (order.status === "failed") {
|
|
32
|
-
instruction = "先告诉用户这笔订单没有正常交付,不需要重复付款或重新下单;先检查原订单是否已有退款,再由用户决定是否申请。";
|
|
32
|
+
instruction = appendOptionalFeedbackInvitation("先告诉用户这笔订单没有正常交付,不需要重复付款或重新下单;先检查原订单是否已有退款,再由用户决定是否申请。", "failed");
|
|
33
33
|
next = { command: `itpay refund list --order ${order.order_id} --json`, reason: "检查同一订单的退款状态" };
|
|
34
34
|
}
|
|
35
35
|
else if (order.status === "refunded") {
|
|
@@ -8,7 +8,7 @@ import { buildAgentChatHandoff } from "../render/markdown.js";
|
|
|
8
8
|
import { platformKeyForHost } from "../render/plan.js";
|
|
9
9
|
import { renderTerminalQR } from "../render/qr.js";
|
|
10
10
|
import { buildCheckoutQRPlan } from "./buy.js";
|
|
11
|
-
import { CommandContractError, isTerminalServiceExecutionStatus, writeCommandEnvelope, } from "./guidance.js";
|
|
11
|
+
import { appendOptionalFeedbackInvitation, CommandContractError, isTerminalServiceExecutionStatus, writeCommandEnvelope, } from "./guidance.js";
|
|
12
12
|
const serviceActionStatuses = new Set(["pending", "approved", "rejected", "expired", "cancelled"]);
|
|
13
13
|
export async function runServicesStart(backend, serviceID, options = {}) {
|
|
14
14
|
const host = options.host ?? "terminal";
|
|
@@ -622,7 +622,16 @@ export async function runServicesList(backend, options = {}) {
|
|
|
622
622
|
});
|
|
623
623
|
}
|
|
624
624
|
export async function runServicesReadResult(backend, serviceExecutionID, options = {}) {
|
|
625
|
-
const
|
|
625
|
+
const response = await backend.getGrantedServiceResult(serviceExecutionID);
|
|
626
|
+
let orderID;
|
|
627
|
+
try {
|
|
628
|
+
const model = await backend.getServiceExecution(serviceExecutionID);
|
|
629
|
+
orderID = (model.current_delivery ?? model.delivery_bindings.at(-1))?.order_id;
|
|
630
|
+
}
|
|
631
|
+
catch {
|
|
632
|
+
// Feedback context is optional and must never block an authorized result.
|
|
633
|
+
}
|
|
634
|
+
const envelope = grantedResultEnvelope(response, orderID);
|
|
626
635
|
writeCommandEnvelope(envelope, {
|
|
627
636
|
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
628
637
|
...(options.output ? { output: options.output } : {}),
|
|
@@ -669,7 +678,7 @@ function servicesNextEnvelope(model) {
|
|
|
669
678
|
instruction: execution.status === "refunded"
|
|
670
679
|
? "告诉用户这笔服务已经退款并永久结束。Agent 不重放服务步骤、不创建付款页面或尝试读取旧交付。"
|
|
671
680
|
: paidFailure
|
|
672
|
-
? "告诉用户:付款和订单已经记录,但本次服务没有正常完成,不需要再次付款或重新下单。然后从同一订单检查退款状态;Agent 不重放服务步骤、创建付款页面或再次调用数据来源,也不把技术故障归咎于用户。"
|
|
681
|
+
? appendOptionalFeedbackInvitation("告诉用户:付款和订单已经记录,但本次服务没有正常完成,不需要再次付款或重新下单。然后从同一订单检查退款状态;Agent 不重放服务步骤、创建付款页面或再次调用数据来源,也不把技术故障归咎于用户。", "failed")
|
|
673
682
|
: "告诉用户本次服务已经结束且没有可继续的交付。Agent 不重放服务步骤或创建付款页面。",
|
|
674
683
|
next: null,
|
|
675
684
|
recovery: [
|
|
@@ -730,14 +739,21 @@ function servicesNextEnvelope(model) {
|
|
|
730
739
|
result: {
|
|
731
740
|
service_execution_id: execution.service_execution_id,
|
|
732
741
|
...(delivery?.capability_id ? { capability_id: delivery.capability_id } : {}),
|
|
742
|
+
...(delivery?.order_id ? { order_id: delivery.order_id } : {}),
|
|
733
743
|
delivery_mode: deliveryMode,
|
|
734
744
|
items,
|
|
735
745
|
},
|
|
736
|
-
instruction:
|
|
737
|
-
?
|
|
738
|
-
?
|
|
739
|
-
|
|
740
|
-
|
|
746
|
+
instruction: delivery?.order_id
|
|
747
|
+
? appendOptionalFeedbackInvitation(items.length > 0
|
|
748
|
+
? selection
|
|
749
|
+
? "搜索已完成。用编号、名称和可公开字段向用户说明结果,然后停止。只有用户明确选择候选并要求继续时才执行 next.command;不要提及 safe_payload。"
|
|
750
|
+
: "这一步的结果已经可用。用普通语言解释可公开字段并停止;不要提及 Graph、safe_payload 或内部 ID。"
|
|
751
|
+
: "告诉用户本次查询得到 0 个结果并停止。Agent 不读取其他交付、不重放当前查询、修改输入或创建新查询。", "delivered")
|
|
752
|
+
: items.length > 0
|
|
753
|
+
? selection
|
|
754
|
+
? "搜索已完成。用编号、名称和可公开字段向用户说明结果,然后停止。只有用户明确选择候选并要求继续时才执行 next.command;不要提及 safe_payload。"
|
|
755
|
+
: "这一步的结果已经可用。用普通语言解释可公开字段并停止;不要提及 Graph、safe_payload 或内部 ID。"
|
|
756
|
+
: "告诉用户本次查询得到 0 个结果并停止。Agent 不读取其他交付、不重放当前查询、修改输入或创建新查询。",
|
|
741
757
|
next: selection ? {
|
|
742
758
|
command: `itpay services action ${execution.service_execution_id} --action select_candidate --actor-type human --status approved --candidate <rank> --json`,
|
|
743
759
|
reason: "仅在用户明确选择后锁定来源候选",
|
|
@@ -860,16 +876,19 @@ function serviceDeliveryMode(model) {
|
|
|
860
876
|
function normalizeGrantStatus(status) {
|
|
861
877
|
return !status || status === "missing" ? "none" : status;
|
|
862
878
|
}
|
|
863
|
-
function grantedResultEnvelope(response) {
|
|
879
|
+
function grantedResultEnvelope(response, orderID) {
|
|
864
880
|
return {
|
|
865
881
|
status: "granted_result_ready",
|
|
866
882
|
result: {
|
|
867
883
|
service_execution_id: response.service_execution_id,
|
|
884
|
+
...(orderID ? { order_id: orderID } : {}),
|
|
868
885
|
...(response.expires_at ? { grant_expires_at: response.expires_at } : {}),
|
|
869
886
|
granted_fields: Object.keys(response.result),
|
|
870
887
|
payload: response.result,
|
|
871
888
|
},
|
|
872
|
-
instruction:
|
|
889
|
+
instruction: orderID
|
|
890
|
+
? appendOptionalFeedbackInvitation("结果来自当前有效 Vault Grant;只使用本次授权字段,过期后停止读取并重新请求用户同意。", "delivered")
|
|
891
|
+
: "结果来自当前有效 Vault Grant;只使用本次授权字段,过期后停止读取并重新请求用户同意。",
|
|
873
892
|
next: null,
|
|
874
893
|
recovery: [],
|
|
875
894
|
};
|
package/dist/src/main.js
CHANGED
|
@@ -16,6 +16,7 @@ import { runCheckoutPresentation } from "./commands/checkout.js";
|
|
|
16
16
|
import { runPay } from "./commands/pay.js";
|
|
17
17
|
import { runOrder } from "./commands/order.js";
|
|
18
18
|
import { runListOrders } from "./commands/orders.js";
|
|
19
|
+
import { runFeedbackSubmit } from "./commands/feedback.js";
|
|
19
20
|
import { runCancelRefund, runGetRefund, runListRefunds, runRefund, runWatchRefund } from "./commands/refund.js";
|
|
20
21
|
import { runCartAdd, runCartAddQuoteServer, runCartAddServer, runCartAbandonServer, runCartClear, runCartNext, runCartRemove, runCartRemoveServer, runCartShow, runCartShowServer, } from "./commands/cart.js";
|
|
21
22
|
import { CommandContractError, errorRecoveryActions, printErrorRecovery, writeCommandEnvelope } from "./commands/guidance.js";
|
|
@@ -42,6 +43,7 @@ Common human intents:
|
|
|
42
43
|
Previously purchased item vault list
|
|
43
44
|
Purchase history orders
|
|
44
45
|
Delivery or refund problem resume the known Order or Refund
|
|
46
|
+
Rate a purchased service feedback submit
|
|
45
47
|
|
|
46
48
|
The Agent runs commands. Ask the human only to choose, authorize, pay, provide
|
|
47
49
|
required contact details, or confirm a refund. Never expose commands or internal IDs.
|
|
@@ -961,6 +963,49 @@ program
|
|
|
961
963
|
});
|
|
962
964
|
}
|
|
963
965
|
});
|
|
966
|
+
const feedback = program.command("feedback").description("Rate one service item from an existing order");
|
|
967
|
+
feedback
|
|
968
|
+
.command("submit")
|
|
969
|
+
.description("Submit a human-confirmed rating and optional comment")
|
|
970
|
+
.option("--order <order_id>")
|
|
971
|
+
.option("--rating <rating>")
|
|
972
|
+
.option("--note <text>")
|
|
973
|
+
.option("--item-rank <rank>")
|
|
974
|
+
.option("--json", "output JSON instead of terminal text")
|
|
975
|
+
.action(async (options) => {
|
|
976
|
+
const config = loadConfig();
|
|
977
|
+
const jsonOutput = Boolean(options.json);
|
|
978
|
+
try {
|
|
979
|
+
await runFeedbackSubmit(newBackendClient(config), options.order, {
|
|
980
|
+
...(options.rating !== undefined ? { rating: options.rating } : {}),
|
|
981
|
+
...(options.note !== undefined ? { note: options.note } : {}),
|
|
982
|
+
...(options.itemRank !== undefined ? { itemRank: options.itemRank } : {}),
|
|
983
|
+
environment: config.environment,
|
|
984
|
+
...(config.agentType ? { agentType: config.agentType } : {}),
|
|
985
|
+
jsonOutput,
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
catch (error) {
|
|
989
|
+
if (error instanceof HttpTransportError) {
|
|
990
|
+
reportCLIError(new CommandContractError("feedback_submission_unknown", error.message, "告诉用户反馈是否记录目前无法确认并停止。不要自动重试;只有用户明确要求再次提交时才可更新同一反馈。", []), { jsonOutput, code: "feedback_submission_unknown", instruction: "", recovery: [] });
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
if (error instanceof HttpError && error.status === 404) {
|
|
994
|
+
reportCLIError(new CommandContractError("feedback_not_available_for_agent", "feedback is not available to this Agent for the selected order", "告诉用户当前 Agent 不能代这笔原订单提交反馈;可以使用官方订单页或原 Local Agent。不要切换身份、Backend、Device 或 MCP/Vault 线路绕过。", []), { jsonOutput, code: "feedback_not_available_for_agent", instruction: "", recovery: [] });
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
if (error instanceof HttpError && error.status >= 400 && error.status < 500) {
|
|
998
|
+
reportCLIError(new CommandContractError("feedback_rejected", error.message, "告诉用户这次反馈没有被记录;只有用户修正明确的评分或内容后才可再次提交。", []), { jsonOutput, code: "feedback_rejected", instruction: "", recovery: [] });
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
reportCLIError(error, {
|
|
1002
|
+
jsonOutput,
|
|
1003
|
+
code: "feedback_submit_failed",
|
|
1004
|
+
instruction: "告诉用户反馈没有确认记录并停止;不要自动重试、切换身份或影响原订单。",
|
|
1005
|
+
recovery: [],
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
964
1009
|
const refund = program
|
|
965
1010
|
.command("refund")
|
|
966
1011
|
.enablePositionalOptions()
|
package/dist/src/state/config.js
CHANGED
|
@@ -12,7 +12,7 @@ import { DeviceAuthority } from "./device_authority.js";
|
|
|
12
12
|
import { OperationJournal } from "./operation_journal.js";
|
|
13
13
|
export const DEFAULT_BASE_URL = "https://app.itpay.ai";
|
|
14
14
|
export const DEV_BASE_URL = "https://dev.itpay.ai";
|
|
15
|
-
export const CLI_VERSION = "2.0.
|
|
15
|
+
export const CLI_VERSION = "2.0.33";
|
|
16
16
|
export const API_CONTRACT_REVISION = "sha256:95a6077248c820f92511ef6d41635881072ad399c18f347ee282253edb83e55f";
|
|
17
17
|
const CART_SESSION_DEFAULT_DIR = ".itpay-v3";
|
|
18
18
|
const CART_SESSION_FILENAME = "cart.json";
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"purpose": "Choose the correct first action from the human's words and continue through one authoritative CLI envelope at a time.",
|
|
8
8
|
"when_to_use": [
|
|
9
9
|
"The agent has just installed this CLI version.",
|
|
10
|
-
"The user asks to buy something, read previous content, inspect orders, or
|
|
10
|
+
"The user asks to buy something, read previous content, inspect orders, handle a refund, or rate a purchased service."
|
|
11
11
|
],
|
|
12
12
|
"required_state": {
|
|
13
13
|
"needs": [
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"previous_content": "Use vault list, optionally with the subject as --query.",
|
|
26
26
|
"purchase_history": "Use orders.",
|
|
27
27
|
"refund_or_paid_problem": "Resume the known Order or Refund before creating anything new.",
|
|
28
|
+
"service_feedback": "Finish the original order outcome first; after the human explicitly gives a 1-5 rating, use feedback submit.",
|
|
28
29
|
"ambiguous": "Ask whether the human wants an earlier purchase or a new query before calling ItPay."
|
|
29
30
|
},
|
|
30
31
|
"commands": [{
|
|
@@ -74,6 +75,10 @@
|
|
|
74
75
|
"condition": "Need order or refund recovery",
|
|
75
76
|
"topic": "orders-refunds"
|
|
76
77
|
},
|
|
78
|
+
{
|
|
79
|
+
"condition": "Need to rate a purchased service or report an experience",
|
|
80
|
+
"topic": "service-feedback"
|
|
81
|
+
},
|
|
77
82
|
{
|
|
78
83
|
"condition": "Need install or identity details",
|
|
79
84
|
"topic": "install-and-setup"
|
|
@@ -90,6 +95,8 @@
|
|
|
90
95
|
"recovery",
|
|
91
96
|
"delivery",
|
|
92
97
|
"refund",
|
|
98
|
+
"feedback",
|
|
99
|
+
"rating",
|
|
93
100
|
"target",
|
|
94
101
|
"input"
|
|
95
102
|
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "itp.agent_doc.v1",
|
|
3
|
+
"role": "buyer",
|
|
4
|
+
"product_scope": "itpay is the single public CLI entry point. Service feedback records a human-confirmed rating for one existing order item; it never replaces delivery recovery, refund policy, or customer care.",
|
|
5
|
+
"topic": "service-feedback",
|
|
6
|
+
"title": "Rate A Purchased Service Or Report A Blocker",
|
|
7
|
+
"purpose": "Help the human finish the original order outcome first, then record an explicit 1-5 rating and optional comment without exposing commands or internal identifiers.",
|
|
8
|
+
"when_to_use": [
|
|
9
|
+
"The human asks to rate a service, leave feedback, report a blocker, or says how many stars the experience deserves.",
|
|
10
|
+
"A paid result has actually been delivered and the CLI instruction invites optional feedback.",
|
|
11
|
+
"A paid service failed and its order, delivery, and refund path have already been explained or recovered."
|
|
12
|
+
],
|
|
13
|
+
"intent_routing": {
|
|
14
|
+
"known_order": "Ask only for a missing 1-5 rating, then run feedback submit yourself.",
|
|
15
|
+
"unknown_order": "Use orders to show human-readable candidates; never ask the human for an order or item ID.",
|
|
16
|
+
"multiple_items": "Show the numbered service titles and safe subjects returned by the CLI, ask which item, then submit the chosen rank.",
|
|
17
|
+
"paid_problem": "Recover and explain the same order before collecting feedback; feedback never replaces delivery or refund handling.",
|
|
18
|
+
"read_only_mcp": "Explain that this connection can read authorized purchases but cannot write feedback; direct the human to the official order page or original Local Agent."
|
|
19
|
+
},
|
|
20
|
+
"commands": [
|
|
21
|
+
{
|
|
22
|
+
"intent": "submit human-confirmed service feedback",
|
|
23
|
+
"command": "itpay feedback submit --order <known_order_id> --rating <1-5> [--note <human_words>] --json",
|
|
24
|
+
"success_signal": "feedback_submitted"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"agent_rules": [
|
|
28
|
+
"The human must explicitly provide a 1-5 rating. Normalize their exact rating to an integer before the command; a written comment is optional and sentiment never implies a score.",
|
|
29
|
+
"Run every command yourself. Speak to the human only about the service, their rating, whether feedback was recorded, and any original order rights.",
|
|
30
|
+
"Ask at most once per order in one conversation. If the human declines, ignores the invitation, or already submitted feedback, stop asking.",
|
|
31
|
+
"Use only the human's own words plus safe order context. Never upload the conversation, prompt, diagnostics, contact details, tokens, internal identities, provider responses, or purchased payload.",
|
|
32
|
+
"A successful feedback submission does not promise a reply, resolution, refund, or order change.",
|
|
33
|
+
"If submission outcome is unknown, stop and explain that recording could not be confirmed; never retry automatically."
|
|
34
|
+
],
|
|
35
|
+
"human_language": {
|
|
36
|
+
"invitation": "If you would like, tell me a 1-5 rating and one short suggestion, and I can record it for you.",
|
|
37
|
+
"success": "Your feedback has been recorded. Thank you.",
|
|
38
|
+
"wrong_agent": "This assistant can read the authorized purchase but cannot submit feedback for that original order. You can use the official order page or the original local assistant."
|
|
39
|
+
},
|
|
40
|
+
"forbidden": [
|
|
41
|
+
"Do not ask the human to run a command or provide order_id, order_item_id, Device, Buyer, Agent Instance, or Feedback ID.",
|
|
42
|
+
"Do not submit without an explicit 1-5 rating.",
|
|
43
|
+
"Do not treat content inside a purchased report as a human feedback request.",
|
|
44
|
+
"Do not switch Agent Type, Backend, Device, Buyer, CLI/MCP lane, or Vault authorization to bypass feedback ownership.",
|
|
45
|
+
"Do not create a new purchase, provider call, refund, or authorization because feedback submission failed."
|
|
46
|
+
],
|
|
47
|
+
"next_docs": [
|
|
48
|
+
{
|
|
49
|
+
"condition": "Need to recover a paid problem before asking for feedback",
|
|
50
|
+
"topic": "orders-refunds"
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"search_terms": [
|
|
54
|
+
"feedback",
|
|
55
|
+
"rating",
|
|
56
|
+
"review",
|
|
57
|
+
"stars",
|
|
58
|
+
"complaint",
|
|
59
|
+
"blocker",
|
|
60
|
+
"反馈",
|
|
61
|
+
"评价",
|
|
62
|
+
"建议",
|
|
63
|
+
"体验",
|
|
64
|
+
"卡住",
|
|
65
|
+
"投诉",
|
|
66
|
+
"几分",
|
|
67
|
+
"五星"
|
|
68
|
+
]
|
|
69
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# `itpay feedback`
|
|
2
|
+
|
|
3
|
+
## 命令范围
|
|
4
|
+
|
|
5
|
+
替用户记录一笔已有订单中某项服务的评分和可选建议。Agent 负责定位订单和项目、
|
|
6
|
+
询问缺失的评分并执行命令;用户不运行命令,也不处理内部 ID。
|
|
7
|
+
|
|
8
|
+
Feedback 不创建客服工单、不改变订单、交付或退款状态,也不承诺回复时间。
|
|
9
|
+
|
|
10
|
+
## 子命令
|
|
11
|
+
|
|
12
|
+
- [`feedback submit`](submit.md)
|
|
13
|
+
|
|
14
|
+
直接运行 `itpay feedback` 只显示帮助,不访问 Backend、不提交反馈。
|
|
15
|
+
|
|
16
|
+
## 权限边界
|
|
17
|
+
|
|
18
|
+
- Local CLI 只能以原 Order Item 对应的 exact Device + Agent Instance 提交;
|
|
19
|
+
- Buyer 可继续在官方订单页用 Buyer Session 提交;
|
|
20
|
+
- MCP 和 Vault 临时读取授权保持只读,不能提交反馈;
|
|
21
|
+
- 无权限时不得切换 Agent Type、Backend、Device 或登录账号绕过。
|
|
22
|
+
|
|
23
|
+
## Agent 服务口径
|
|
24
|
+
|
|
25
|
+
- 用户必须明确给出 1–5 分;文字建议可选;
|
|
26
|
+
- 先完成交付、故障恢复和退款权利解释,再邀请反馈;
|
|
27
|
+
- 用户拒绝、忽略或已经反馈后,同一对话不再询问;
|
|
28
|
+
- 成功后只告诉用户反馈已经记录并表示感谢;
|
|
29
|
+
- 不自动上传聊天、Prompt、日志、Token、联系方式或已购内容。
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# `itpay feedback submit`
|
|
2
|
+
|
|
3
|
+
## 范围与意义
|
|
4
|
+
|
|
5
|
+
把用户明确给出的评分和可选建议记录到一笔已有 Order 的具体服务项目。CLI 读取
|
|
6
|
+
同一 Order 选择真实 `order_item_id`,再调用现有 Feedback Owner。用户不需要知道
|
|
7
|
+
Order ID、Order Item ID、Device 或 Agent Instance。
|
|
8
|
+
|
|
9
|
+
## 语法与参数
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
itpay feedback submit \
|
|
13
|
+
--order <order_id> \
|
|
14
|
+
--rating <1-5> \
|
|
15
|
+
[--note <text>] \
|
|
16
|
+
[--item-rank <positive_integer>] \
|
|
17
|
+
[--json]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
| 参数 | 必填 | 说明 |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| `--order <order_id>` | 是 | Agent 从当前订单流程取得,不要求用户提供。 |
|
|
23
|
+
| `--rating <1-5>` | 是 | 用户明确给出的 1–5 评分;CLI 归一常见精确写法。 |
|
|
24
|
+
| `--note <text>` | 否 | 用户明确表达的建议或卡壳点。 |
|
|
25
|
+
| `--item-rank <n>` | 多项目订单条件必填 | 当前 Order items 的 1-based rank。 |
|
|
26
|
+
| `--json` | 否 | 输出一个稳定 JSON envelope;Agent 应使用。 |
|
|
27
|
+
|
|
28
|
+
rating、rank 和 note 长度在任何 Feedback POST 前验证。rating 接受 `5`、`5/5`、
|
|
29
|
+
`5分`、`5星`、`5 stars` 和中文 `一` 至 `五` 的精确写法,统一保存为整数;
|
|
30
|
+
`2.5`、`6`、`很好` 等含糊或越界值拒绝,不能猜测。完整结构化 note 最长 2000
|
|
31
|
+
Unicode code points;超长时不截断用户原话,而是请用户缩短。
|
|
32
|
+
|
|
33
|
+
## 项目选择
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
0 个可反馈 item -> feedback_unavailable,不提交
|
|
37
|
+
1 个可反馈 item -> 自动选择
|
|
38
|
+
多个 item 且无 rank -> feedback_item_selection_required,不提交
|
|
39
|
+
rank 无效 -> feedback_item_invalid,不提交
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
多项目选择只展示 rank、服务标题和安全主题,不展示 `order_item_id`。该状态固定
|
|
43
|
+
`next:null`,避免把用户 note 拼进 shell command。用户选定后,Agent 使用同一
|
|
44
|
+
订单、评分和留言并加入 `--item-rank` 自己执行。
|
|
45
|
+
|
|
46
|
+
## 保存格式
|
|
47
|
+
|
|
48
|
+
Backend 继续保存现有 `rating` 和 `note`。CLI 的 note 是可直接在 Admin 阅读的短
|
|
49
|
+
Markdown:
|
|
50
|
+
|
|
51
|
+
```markdown
|
|
52
|
+
## Summary
|
|
53
|
+
> 支付后等了很久才拿到结果。
|
|
54
|
+
|
|
55
|
+
## Context
|
|
56
|
+
- Source: user-confirmed via agent
|
|
57
|
+
- Outcome: delivered
|
|
58
|
+
- Service: 企业综合报告
|
|
59
|
+
- Client: @itpay/cli <version>
|
|
60
|
+
- Agent type: workbuddy
|
|
61
|
+
- Environment: development
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
只写用户明确内容和已知安全上下文;禁止 Token、Session、联系方式、内部身份、
|
|
65
|
+
Provider 响应、Vault payload、完整命令输出、stack trace 或环境变量。
|
|
66
|
+
|
|
67
|
+
## 成功 JSON
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"status": "feedback_submitted",
|
|
72
|
+
"result": {
|
|
73
|
+
"order_code": "IP-…",
|
|
74
|
+
"service_title": "企业综合报告",
|
|
75
|
+
"rating": 5,
|
|
76
|
+
"feedback_status": "new"
|
|
77
|
+
},
|
|
78
|
+
"instruction": "告诉用户反馈已经记录并表示感谢,然后停止。不要承诺回复、处理时间、退款或结果变更。",
|
|
79
|
+
"next": null,
|
|
80
|
+
"recovery": []
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
正常结果不返回 `feedback_id`、`order_item_id`、Device、Agent Instance 或 Buyer。
|
|
85
|
+
同一 submitter 再次由用户明确提交会更新现有记录并重新进入 `new`,不是创建第二条。
|
|
86
|
+
|
|
87
|
+
## 多项目 JSON
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"status": "feedback_item_selection_required",
|
|
92
|
+
"result": {
|
|
93
|
+
"order_code": "IP-…",
|
|
94
|
+
"items": [
|
|
95
|
+
{ "rank": 1, "title": "企业名称建议", "subject": "京东" },
|
|
96
|
+
{ "rank": 2, "title": "企业综合报告", "subject": "北京京东世纪贸易有限公司" }
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
"instruction": "用服务名称和主题让用户选择要评价哪一项;不要展示内部 ID。用户选择后,Agent 使用同一订单、评分和留言并加入所选 item rank 自己执行提交。",
|
|
100
|
+
"next": null,
|
|
101
|
+
"recovery": []
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 错误合同
|
|
106
|
+
|
|
107
|
+
| 状态/错误 | 行为 |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| `feedback_rating_invalid` | 询问明确的 1–5 评分;没有 HTTP,不猜测。 |
|
|
110
|
+
| `feedback_note_too_long` | 请用户缩短;不截断、不提交。 |
|
|
111
|
+
| `feedback_unavailable` | 该 Order 当前没有可反馈项目;不猜 ID。 |
|
|
112
|
+
| `feedback_item_invalid` | 重新使用返回的项目 rank;不提交。 |
|
|
113
|
+
| `feedback_not_available_for_agent` | 当前 Agent 不能代该订单提交;指向官方订单页或原 Local Agent,不切身份绕过。 |
|
|
114
|
+
| `feedback_rejected` | 输入未被记录;只有用户修正明确输入后才重试。 |
|
|
115
|
+
| `feedback_submission_unknown` | 无法确认是否记录;停止自动重试,只有用户明确要求后才再次提交。 |
|
|
116
|
+
|
|
117
|
+
Feedback POST 不做自动 transport retry。虽然 Backend 使用 upsert,重放仍会更新记录
|
|
118
|
+
并追加事件,CLI 不能把它当成无副作用查询。
|
|
119
|
+
|
|
120
|
+
## Agent Type / Host
|
|
121
|
+
|
|
122
|
+
所有 Local Agent Type 使用相同命令、Device Authority 和 JSON。Host 不改变权限。
|
|
123
|
+
Remote MCP 保持只读且没有 Feedback tool。
|
|
@@ -63,6 +63,11 @@ Commander 自动提供的 `itpay help [command]` 与 `itpay <group> help [subcom
|
|
|
63
63
|
- [`itpay refund watch`](commands/refund/watch.md)
|
|
64
64
|
- [`itpay refund cancel`](commands/refund/cancel.md)
|
|
65
65
|
|
|
66
|
+
### 服务反馈
|
|
67
|
+
|
|
68
|
+
- [`itpay feedback`](commands/feedback/index.md)
|
|
69
|
+
- [`itpay feedback submit`](commands/feedback/submit.md)
|
|
70
|
+
|
|
66
71
|
### 跨平台已购内容
|
|
67
72
|
|
|
68
73
|
- [`itpay vault`](commands/vault/index.md)
|
package/package.json
CHANGED
package/skills/itpay/SKILL.md
CHANGED
|
@@ -3,7 +3,8 @@ name: itpay
|
|
|
3
3
|
description: >
|
|
4
4
|
Use ItPay when a human wants to discover or buy a service, view something
|
|
5
5
|
they previously purchased, inspect order or delivery history, or request
|
|
6
|
-
and track a refund. Seller workflows are not
|
|
6
|
+
and track a refund, or rate a purchased service. Seller workflows are not
|
|
7
|
+
yet available.
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
# ItPay
|
|
@@ -21,6 +22,7 @@ for the human; never ask them to run commands or learn internal concepts.
|
|
|
21
22
|
| Find a previous result by subject | `itpay vault list --query <subject> --json` |
|
|
22
23
|
| Inspect purchase history | `itpay orders --json` |
|
|
23
24
|
| Track or request a refund | Resume the known Order or Refund returned by ItPay |
|
|
25
|
+
| Rate a purchased service or report a blocker | Resume the known Order; submit only after the human gives a 1–5 rating |
|
|
24
26
|
|
|
25
27
|
Words such as "my", "previous", "bought", "history", "report", "以前",
|
|
26
28
|
"之前", "买过", "查过", "历史", and "已购内容" usually mean an existing
|
|
@@ -59,6 +61,9 @@ The current Backend response always overrides general documentation.
|
|
|
59
61
|
If delivery fails, recover that same order before discussing a refund.
|
|
60
62
|
- Explain refund eligibility as a policy route, not a promise. Only ItPay's
|
|
61
63
|
final refund state proves success.
|
|
64
|
+
- Finish delivery or failure recovery before inviting feedback. Ask at most
|
|
65
|
+
once per order; require an explicit 1–5 rating, run the feedback command
|
|
66
|
+
yourself, and promise only that the feedback was recorded.
|
|
62
67
|
- Describe Vault/artifact/grant as "已购内容", the actual report title, or
|
|
63
68
|
"临时只读授权". Do not expose Provider, Buyer, Device, Execution, capability,
|
|
64
69
|
token, or internal identifiers.
|
|
@@ -90,3 +95,5 @@ The current Backend response always overrides general documentation.
|
|
|
90
95
|
separate attempt.
|
|
91
96
|
- Never claim a handoff, payment, authorization, delivery, or refund succeeded
|
|
92
97
|
without the corresponding ItPay state.
|
|
98
|
+
- Never infer a rating or silently upload chat, prompts, logs, contact details,
|
|
99
|
+
purchased content, credentials, or internal identifiers as feedback.
|