@itpay/cli 2.1.41 → 2.1.44

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.
Files changed (148) hide show
  1. package/README.md +12 -5
  2. package/assets/sell-preview/index.html +6 -0
  3. package/assets/sell-preview/preview.js +9 -0
  4. package/bin/darwin-amd64/itpay-sell +0 -0
  5. package/bin/darwin-arm64/itpay-sell +0 -0
  6. package/bin/linux-amd64/itpay-sell +0 -0
  7. package/bin/linux-arm64/itpay-sell +0 -0
  8. package/bin/windows-amd64/itpay-sell.exe +0 -0
  9. package/dist/src/client/backend.js +38 -2
  10. package/dist/src/client/http.js +2 -0
  11. package/dist/src/commands/aftercare.js +12 -0
  12. package/dist/src/commands/buy.js +17 -58
  13. package/dist/src/commands/cart.js +17 -15
  14. package/dist/src/commands/catalog.js +39 -14
  15. package/dist/src/commands/checkout.js +146 -5
  16. package/dist/src/commands/checkout_handoff.js +11 -61
  17. package/dist/src/commands/docs.js +7 -18
  18. package/dist/src/commands/guidance.js +1 -134
  19. package/dist/src/commands/install.js +6 -3
  20. package/dist/src/commands/order.js +34 -3
  21. package/dist/src/commands/orders.js +4 -9
  22. package/dist/src/commands/pay.js +1 -1
  23. package/dist/src/commands/presentation.js +153 -0
  24. package/dist/src/commands/rail_catalog.js +1 -0
  25. package/dist/src/commands/readyz.js +1 -1
  26. package/dist/src/commands/refund.js +13 -57
  27. package/dist/src/commands/services/actions.js +133 -0
  28. package/dist/src/commands/services/commerce.js +197 -0
  29. package/dist/src/commands/services/lifecycle.js +418 -0
  30. package/dist/src/commands/services/results.js +130 -0
  31. package/dist/src/commands/services.js +14 -1068
  32. package/dist/src/commands/skill.js +8 -5
  33. package/dist/src/commands/vault.js +27 -20
  34. package/dist/src/commands/vault_handoff.js +18 -0
  35. package/dist/src/main.js +332 -46
  36. package/dist/src/render/browser.js +65 -0
  37. package/dist/src/render/checkout_handoff.js +101 -0
  38. package/dist/src/render/checkout_plan.js +47 -0
  39. package/dist/src/render/markdown.js +0 -3
  40. package/dist/src/render/qr.js +1 -1
  41. package/dist/src/sell/commands.js +80 -0
  42. package/dist/src/sell/contract.js +86 -0
  43. package/dist/src/sell/local.js +260 -0
  44. package/dist/src/sell/mcp.js +49 -0
  45. package/dist/src/sell/preview.js +70 -0
  46. package/dist/src/sell/sync.js +145 -0
  47. package/dist/src/services/adapter.js +1 -0
  48. package/dist/src/services/presentation.js +198 -0
  49. package/dist/src/services/rail/actions.js +51 -0
  50. package/dist/src/services/rail/adapter.js +36 -0
  51. package/dist/src/services/rail/aftercare.js +45 -0
  52. package/dist/src/services/rail/catalog.js +261 -0
  53. package/dist/src/services/rail/checkout.js +19 -0
  54. package/dist/src/services/rail/fulfillment.js +76 -0
  55. package/dist/src/services/rail/guide.js +110 -0
  56. package/dist/src/services/rail/invocation.js +54 -0
  57. package/dist/src/services/rail/page.js +76 -0
  58. package/dist/src/services/rail/pagination.js +6 -0
  59. package/dist/src/services/rail/query.js +71 -0
  60. package/dist/src/services/rail/readers.js +107 -0
  61. package/dist/src/services/rail/reading.js +125 -0
  62. package/dist/src/services/rail/recovery.js +51 -0
  63. package/dist/src/services/rail/results.js +378 -0
  64. package/dist/src/services/rail/review.js +268 -0
  65. package/dist/src/services/rail/saved.js +49 -0
  66. package/dist/src/services/rail/wait.js +11 -0
  67. package/dist/src/services/registry.js +31 -0
  68. package/dist/src/services/schema.js +21 -0
  69. package/dist/src/services/shared/envelope.js +184 -0
  70. package/dist/src/services/shared/input.js +39 -0
  71. package/dist/src/services/shared/pagination.js +28 -0
  72. package/dist/src/services/shared/platform.js +365 -0
  73. package/dist/src/services/shared/refund.js +40 -0
  74. package/dist/src/services/shared/workflow.js +28 -0
  75. package/dist/src/state/account_auth.js +354 -0
  76. package/dist/src/state/cart_session.js +4 -1
  77. package/dist/src/state/client_context.js +72 -0
  78. package/dist/src/state/config.js +44 -14
  79. package/dist/src/state/device_authority.js +402 -72
  80. package/dist/src/state/task_journal.js +90 -0
  81. package/docs/agent/buyer/cart-checkout.json +8 -6
  82. package/docs/agent/buyer/catalog-list.json +29 -9
  83. package/docs/agent/buyer/identity-and-sessions.json +26 -9
  84. package/docs/agent/buyer/install-and-setup.json +17 -15
  85. package/docs/agent/buyer/orders-refunds.json +25 -9
  86. package/docs/agent/buyer/payment-flow.json +6 -4
  87. package/docs/agent/buyer/purchased-content.json +18 -4
  88. package/docs/agent/buyer/quickstart.json +26 -17
  89. package/docs/agent/buyer/rail-booking.json +176 -0
  90. package/docs/agent/buyer/rail-fast-checkout.json +48 -0
  91. package/docs/agent/buyer/render-hosts.json +23 -12
  92. package/docs/agent/buyer/service-feedback.json +4 -3
  93. package/docs/cli-reference/agent-types.md +7 -2
  94. package/docs/cli-reference/commands/aftercare.md +15 -0
  95. package/docs/cli-reference/commands/auth.md +96 -0
  96. package/docs/cli-reference/commands/buy.md +2 -1
  97. package/docs/cli-reference/commands/cart/add.md +5 -1
  98. package/docs/cli-reference/commands/cart/clear.md +1 -1
  99. package/docs/cli-reference/commands/cart/index.md +1 -1
  100. package/docs/cli-reference/commands/cart/next.md +5 -1
  101. package/docs/cli-reference/commands/cart/remove.md +1 -1
  102. package/docs/cli-reference/commands/cart/show.md +1 -1
  103. package/docs/cli-reference/commands/catalog/index.md +1 -1
  104. package/docs/cli-reference/commands/catalog/list.md +2 -2
  105. package/docs/cli-reference/commands/checkout.md +12 -2
  106. package/docs/cli-reference/commands/device.md +18 -11
  107. package/docs/cli-reference/commands/docs/index.md +1 -1
  108. package/docs/cli-reference/commands/docs/list.md +1 -1
  109. package/docs/cli-reference/commands/docs/search.md +1 -1
  110. package/docs/cli-reference/commands/docs/show.md +1 -1
  111. package/docs/cli-reference/commands/install.md +8 -6
  112. package/docs/cli-reference/commands/next.md +1 -1
  113. package/docs/cli-reference/commands/order.md +1 -1
  114. package/docs/cli-reference/commands/pay.md +1 -1
  115. package/docs/cli-reference/commands/readyz.md +11 -11
  116. package/docs/cli-reference/commands/refund/cancel.md +1 -1
  117. package/docs/cli-reference/commands/refund/create.md +1 -1
  118. package/docs/cli-reference/commands/refund/get.md +2 -2
  119. package/docs/cli-reference/commands/refund/index.md +1 -1
  120. package/docs/cli-reference/commands/refund/list.md +1 -1
  121. package/docs/cli-reference/commands/refund/watch.md +1 -1
  122. package/docs/cli-reference/commands/sell.md +1364 -0
  123. package/docs/cli-reference/commands/services/action.md +24 -3
  124. package/docs/cli-reference/commands/services/checkout.md +17 -2
  125. package/docs/cli-reference/commands/services/events.md +1 -1
  126. package/docs/cli-reference/commands/services/get.md +4 -2
  127. package/docs/cli-reference/commands/services/index.md +4 -3
  128. package/docs/cli-reference/commands/services/invoke.md +6 -4
  129. package/docs/cli-reference/commands/services/list.md +1 -1
  130. package/docs/cli-reference/commands/services/next.md +78 -12
  131. package/docs/cli-reference/commands/services/page.md +71 -0
  132. package/docs/cli-reference/commands/services/quote.md +4 -4
  133. package/docs/cli-reference/commands/services/read-result.md +17 -6
  134. package/docs/cli-reference/commands/services/run.md +152 -0
  135. package/docs/cli-reference/commands/services/start.md +5 -3
  136. package/docs/cli-reference/commands/vault/access.md +5 -1
  137. package/docs/cli-reference/conventions.md +4 -6
  138. package/docs/cli-reference/index.md +10 -1
  139. package/docs/sell.md +84 -0
  140. package/package.json +20 -12
  141. package/skills/itpay/SKILL.md +112 -102
  142. package/docs/skill-bundle-rollout/01-mcp-authentication.md +0 -134
  143. package/docs/skill-bundle-rollout/02-platform-bundle-repositories.md +0 -210
  144. package/docs/skill-bundle-rollout/03-platform-publishing.md +0 -161
  145. package/docs/skill-bundle-rollout/04-first-wave-platforms.md +0 -69
  146. package/docs/skill-bundle-rollout/05-sync-operations.md +0 -62
  147. package/docs/skill-bundle-rollout/README.md +0 -151
  148. /package/bin/{itp → itp.mjs} +0 -0
@@ -0,0 +1,28 @@
1
+ import { CommandContractError, writeCommandEnvelope } from "./envelope.js";
2
+ // Bound the final qualified output by whole saved rows; the cursor follows the emitted rows.
3
+ export function writeSavedPage(envelope, options, key, offset, continuation, updateMetadata) {
4
+ const result = envelope.result ?? {};
5
+ const rows = result[key];
6
+ if (!Array.isArray(rows))
7
+ throw new Error("saved page has no rows");
8
+ const bound = (offset === 0 ? 12 : 32) * 1024;
9
+ const render = (output) => writeCommandEnvelope(envelope, { ...options, output });
10
+ if (!options.all) {
11
+ for (;;) {
12
+ let text = "";
13
+ render(value => { text += value; });
14
+ if (Buffer.byteLength(text, "utf8") <= bound)
15
+ break;
16
+ if (rows.length <= 1) {
17
+ throw new CommandContractError("saved_row_too_large", "saved row exceeds the readable page limit", "这条已保存记录超过当前页面大小;事实仍保留,请读取该条详情。", rows[0]?.detail?.command ? [rows[0].detail] : []);
18
+ }
19
+ rows.pop();
20
+ result.truncated_reason = "cli_output_byte_budget";
21
+ result.count = rows.length;
22
+ updateMetadata(result, rows.length);
23
+ result.next_offset = offset + rows.length;
24
+ envelope.next = continuation(offset + rows.length);
25
+ }
26
+ }
27
+ writeCommandEnvelope(envelope, options);
28
+ }
@@ -0,0 +1,365 @@
1
+ import { taskJournalPath } from "../../state/config.js";
2
+ import { TaskJournal } from "../../state/task_journal.js";
3
+ import { buildCheckoutHandoff } from "../../render/checkout_handoff.js";
4
+ import { buildAgentChatHandoff } from "../../render/markdown.js";
5
+ import { platformKeyForHost } from "../../render/plan.js";
6
+ import { appendFeedbackPostmortemInstruction, CommandContractError, isTerminalServiceExecutionStatus, shellArgument, plainValueLines } from "./envelope.js";
7
+ // AUTH04: record the pause point of each Service Execution so a later
8
+ // `itpay auth login` can resume the SAME task. Ids/stage/resume command only —
9
+ // no chat content, PII or credentials. Best-effort: journaling never breaks
10
+ // a command.
11
+ export function journalTaskState(serviceExecutionID, envelope, env) {
12
+ try {
13
+ const journal = new TaskJournal(taskJournalPath(env));
14
+ const serviceID = typeof envelope.result?.service_id === "string" ? envelope.result.service_id : undefined;
15
+ journal.observe(serviceExecutionID, envelope.status, serviceID ? `itpay services run ${serviceID} --execution ${serviceExecutionID} --json` : undefined);
16
+ }
17
+ catch {
18
+ /* local journal is advisory; never fail the envelope */
19
+ }
20
+ }
21
+ export const serviceActionStatuses = new Set(["pending", "approved", "rejected", "expired", "cancelled"]);
22
+ export function requiredInputFields(schema) {
23
+ const required = schema?.required;
24
+ return Array.isArray(required) ? required.filter((field) => typeof field === "string") : [];
25
+ }
26
+ export function serviceResultPlainLines(result) {
27
+ return plainValueLines(result);
28
+ }
29
+ export function queryText(input) {
30
+ const value = Object.values(input).find((item) => typeof item === "string" && item.trim() !== "");
31
+ return typeof value === "string" ? value : JSON.stringify(input);
32
+ }
33
+ export function missingRequiredInput(schema, input) {
34
+ return requiredInputFields(schema).filter((field) => {
35
+ if (!(field in input) || input[field] === null || input[field] === undefined)
36
+ return true;
37
+ return typeof input[field] === "string" && String(input[field]).trim() === "";
38
+ });
39
+ }
40
+ export function checkoutCommand(serviceExecutionID, capability, input, fillMissing = true) {
41
+ const lockedInput = { ...input };
42
+ if (fillMissing) {
43
+ for (const field of missingRequiredInput(capability.input_schema, lockedInput))
44
+ lockedInput[field] = "<value>";
45
+ }
46
+ return `itpay services checkout ${serviceExecutionID} --capability ${capability.capability_id}${formatInputOptions(lockedInput)}${capability.delivery_email_required ? " --email <email>" : ""} --json`;
47
+ }
48
+ export function capabilityPrice(capability) {
49
+ if (capability.pricing_method !== undefined)
50
+ return "动态报价(最终金额以锁定报价为准)";
51
+ return capability.price_amount_minor !== undefined && capability.price_currency
52
+ ? formatMoney(capability.price_amount_minor, capability.price_currency)
53
+ : "当前发布价格";
54
+ }
55
+ export function purchaseConfirmationInstruction(context, price, deliveryEmailRequired, deliveryEmailPurpose, candidateTitle = "") {
56
+ const emailPurpose = deliveryEmailPurposeText(deliveryEmailPurpose);
57
+ if (context === "quota_exhausted") {
58
+ return deliveryEmailRequired
59
+ ? `免费额度已用完,本次没有发送到数据来源,也没有创建付款页面。只向用户说明:继续当前请求需要支付 ${price},并提供${emailPurpose};请确认是否购买并提供邮箱。然后停止等待。用户明确同意并提供真实邮箱前,Agent 不执行 next.command,也不创建或尝试其他购买路径。`
60
+ : `免费额度已用完,本次没有发送到数据来源,也没有创建付款页面。只向用户说明:“继续当前请求需要支付 ${price},是否购买?”然后停止等待。用户明确同意前,Agent 不执行 next.command,也不创建或尝试其他购买路径。`;
61
+ }
62
+ const selected = candidateTitle ? `已选择 ${candidateTitle}。` : "当前候选已经确认。";
63
+ return deliveryEmailRequired
64
+ ? `${selected}后续服务尚未购买。只向用户说明:继续购买需要支付 ${price},并提供${emailPurpose};请确认是否购买并提供邮箱。然后停止。用户明确同意并提供真实邮箱前,Agent 不执行 next.command,也不创建新的服务或付款页面。`
65
+ : `${selected}后续服务尚未购买。只向用户说明:“继续购买后续服务需要支付 ${price},是否购买?”然后停止。用户明确同意前,Agent 不执行 next.command,也不创建新的服务或付款页面。`;
66
+ }
67
+ export function deliveryEmailPurposeText(purpose) {
68
+ switch (purpose) {
69
+ case "receipt":
70
+ return "用于发送订单收据的真实邮箱";
71
+ case "claim":
72
+ return "用于发送交付认领链接的真实邮箱";
73
+ case "receipt_and_claim":
74
+ return "用于发送订单收据和交付认领链接的真实邮箱";
75
+ default:
76
+ return "服务端声明用途的真实邮箱";
77
+ }
78
+ }
79
+ export function paidContinuation(model, action, input) {
80
+ if (!action.capability_id)
81
+ return null;
82
+ const capability = model.capabilities.find((item) => item.capability_id === action.capability_id && item.requires_payment);
83
+ if (!capability)
84
+ return null;
85
+ const price = capabilityPrice(capability);
86
+ const stateBacked = model.execution.status === "quota_exhausted" || model.execution.status === "human_action_approved";
87
+ return {
88
+ capability,
89
+ price,
90
+ checkout: {
91
+ capability_id: capability.capability_id,
92
+ ...(capability.price_amount_minor !== undefined && capability.price_currency ? {
93
+ price: { amount_minor: capability.price_amount_minor, currency: capability.price_currency },
94
+ } : {}),
95
+ delivery_email_required: capability.delivery_email_required,
96
+ ...(capability.delivery_email_purpose ? { delivery_email_purpose: capability.delivery_email_purpose } : {}),
97
+ },
98
+ next: {
99
+ command: checkoutCommand(model.execution.service_execution_id, capability, input, !stateBacked),
100
+ reason: `仅在用户明确同意支付 ${price}${capability.delivery_email_required ? " 并提供真实邮箱" : ""}后执行;否则停止`,
101
+ },
102
+ };
103
+ }
104
+ export function quoteCommand(serviceExecutionID, capability, input) {
105
+ const lockedInput = { ...input };
106
+ for (const field of missingRequiredInput(capability.input_schema, lockedInput))
107
+ lockedInput[field] = "<value>";
108
+ return `itpay services quote ${serviceExecutionID} --capability ${capability.capability_id}${formatInputOptions(lockedInput)}${capability.delivery_email_required ? " --email <email>" : ""} --json`;
109
+ }
110
+ export function stableInput(input) {
111
+ return JSON.stringify(Object.fromEntries(Object.entries(input).sort(([left], [right]) => left.localeCompare(right))));
112
+ }
113
+ export function formatInputOptions(input) {
114
+ return Object.entries(input)
115
+ .sort(([left], [right]) => left.localeCompare(right))
116
+ .map(([key, value]) => String(value) === "<value>"
117
+ ? ` --input ${key}=<value>`
118
+ : ` --input ${shellArgument(`${key}=${typeof value === "object" && value !== null ? JSON.stringify(value) : String(value)}`)}`)
119
+ .join("");
120
+ }
121
+ export function actionInputError(serviceExecutionID, message, code = "service_action_invalid") {
122
+ return new CommandContractError(code, message, code === "candidate_not_found"
123
+ ? "当前 rank 不存在或当前候选集不可用。不要新建 Execution,不要重新 invoke,不要构造候选 ID;只恢复同一 Execution 当前仍然有效的候选。"
124
+ : "使用当前 safe result 中的合法 action 和 candidate rank;需要人确认时先询问用户。", [{ command: `itpay services next ${serviceExecutionID} --json`, reason: "重新读取同一 Execution 的当前可选动作" }]);
125
+ }
126
+ export function terminalExecutionEnvelope(model) {
127
+ const execution = model.execution;
128
+ const currentDelivery = model.current_delivery ?? model.delivery_bindings.at(-1);
129
+ if (!isTerminalServiceExecutionStatus(execution.status) ||
130
+ (model.workflow_entry && (currentDelivery || serviceDeliveryMode(model) === "agent_visible_result") && ["completed", "delivery_completed"].includes(execution.status))) {
131
+ return null;
132
+ }
133
+ const paid = model.checkout_bindings.some((binding) => binding.status === "payment_verified") || Boolean(currentDelivery?.order_id);
134
+ const paidFailure = execution.status === "failed" && paid;
135
+ return {
136
+ status: execution.status,
137
+ result: {
138
+ service_execution_id: execution.service_execution_id,
139
+ service_id: execution.service_id,
140
+ phase: execution.phase,
141
+ ...(currentDelivery?.order_id ? { order_id: currentDelivery.order_id, aftercare: { optional: true, command: `itpay feedback submit --order ${shellArgument(currentDelivery.order_id)} --json`, reason: "告知原单结果后按需复盘,不猜评分或上传隐私" } } : {}),
142
+ },
143
+ instruction: execution.status === "refunded"
144
+ ? "告诉用户这笔服务已经退款并永久结束。Agent 不重放服务步骤、不创建付款页面或尝试读取旧交付。"
145
+ : paidFailure
146
+ ? appendFeedbackPostmortemInstruction("告诉用户:付款和订单已经记录,但本次服务没有正常完成,不需要再次付款或重新下单。然后从同一订单检查退款状态;Agent 不重放服务步骤、创建付款页面或再次调用数据来源,也不把技术故障归咎于用户。", "failed")
147
+ : "告诉用户本次服务已经结束且没有可继续的交付。Agent 不重放服务步骤或创建付款页面。",
148
+ next: null,
149
+ recovery: [
150
+ ...(paidFailure
151
+ ? [{
152
+ command: currentDelivery?.order_id
153
+ ? `itpay order ${currentDelivery.order_id} --json`
154
+ : "itpay orders --json",
155
+ reason: "恢复同一笔已付款订单及其退款状态",
156
+ }]
157
+ : []),
158
+ {
159
+ command: `itpay services events ${execution.service_execution_id} --json`,
160
+ reason: "仅在需要诊断终止原因时读取事件",
161
+ },
162
+ ],
163
+ };
164
+ }
165
+ export function serviceAllowedActionCommand(model, action) {
166
+ const executionID = model.execution.service_execution_id;
167
+ const capability = action.capability_id
168
+ ? model.capabilities.find((item) => item.capability_id === action.capability_id)
169
+ : undefined;
170
+ switch (action.type) {
171
+ case "invoke_capability": {
172
+ if (!capability)
173
+ return null;
174
+ const input = Object.fromEntries(requiredInputFields(capability.input_schema).map((field) => [field, "<value>"]));
175
+ return {
176
+ command: `itpay services invoke ${executionID} --capability ${capability.capability_id}${formatInputOptions(input)} --json`,
177
+ reason: "执行当前允许的 Agent-visible capability",
178
+ };
179
+ }
180
+ case "select_candidate":
181
+ return {
182
+ command: `itpay services action ${executionID} --action select_candidate --actor-type human --status approved --candidate <rank> --json`,
183
+ reason: "仅在用户明确选择后提交当前候选 rank",
184
+ };
185
+ case "prepare_quote": {
186
+ return paidContinuation(model, action, {})?.next ?? null;
187
+ }
188
+ case "resume_checkout":
189
+ return { command: `itpay services checkout ${executionID} --resume --json`, reason: "恢复同一 Checkout,不创建第二笔" };
190
+ case "wait":
191
+ return { command: `itpay services next ${executionID} --json`, reason: "等待 durable execution 推进" };
192
+ case "view_delivery":
193
+ return { command: `itpay services next ${executionID} --json`, reason: "读取当前交付模式" };
194
+ default:
195
+ return null;
196
+ }
197
+ }
198
+ export function serviceDeliveryMode(model) {
199
+ const delivery = model.current_delivery ?? model.delivery_bindings.at(-1);
200
+ const entry = model.capabilities.find(capability => capability.capability_id === model.workflow_entry?.capability_id);
201
+ if (!delivery && model.workflow?.status === "completed" && entry?.requires_payment === false && !entry.vault_required)
202
+ return "agent_visible_result";
203
+ const explicit = String(delivery?.redacted_summary?.delivery_mode ?? "");
204
+ if (explicit)
205
+ return explicit;
206
+ return delivery?.vault_artifact_id ? "vault_artifact" : "";
207
+ }
208
+ export function normalizeGrantStatus(status) {
209
+ return !status || status === "missing" ? "none" : status;
210
+ }
211
+ export function grantedResultEnvelope(response, orderID) {
212
+ return {
213
+ status: "granted_result_ready",
214
+ result: {
215
+ service_execution_id: response.service_execution_id,
216
+ ...(orderID ? { order_id: orderID, aftercare: { optional: true, command: `itpay feedback submit --order ${shellArgument(orderID)} --json`, reason: "交付答复后按需复盘,不猜评分或上传隐私" } } : {}),
217
+ ...(response.expires_at ? { grant_expires_at: response.expires_at } : {}),
218
+ granted_fields: Object.keys(response.result),
219
+ payload: response.result,
220
+ },
221
+ instruction: orderID
222
+ ? appendFeedbackPostmortemInstruction("结果来自当前有效 Vault Grant;只使用本次授权字段,过期后停止读取并重新请求用户同意。", "delivered")
223
+ : "结果来自当前有效 Vault Grant;只使用本次授权字段,过期后停止读取并重新请求用户同意。",
224
+ next: null,
225
+ recovery: [],
226
+ };
227
+ }
228
+ export function servicesNextPlainResult(result) {
229
+ return plainValueLines(result);
230
+ }
231
+ export function parseKeyValueList(values) {
232
+ const result = {};
233
+ for (const value of values ?? []) {
234
+ const index = value.indexOf("=");
235
+ if (index <= 0) {
236
+ throw new Error(`invalid --input "${value}", expected key=value`);
237
+ }
238
+ result[value.slice(0, index)] = parseValue(value.slice(index + 1));
239
+ }
240
+ return result;
241
+ }
242
+ export function collectOption(value, previous = []) {
243
+ previous.push(value);
244
+ return previous;
245
+ }
246
+ export function parseValue(value) {
247
+ // Structured inputs (objects/arrays) are passed as JSON; a string that merely
248
+ // starts with a JSON delimiter but is not valid JSON stays a string — the
249
+ // backend schema validates the field type either way.
250
+ if (value.startsWith("{") || value.startsWith("[")) {
251
+ try {
252
+ return JSON.parse(value);
253
+ }
254
+ catch {
255
+ return value;
256
+ }
257
+ }
258
+ if (value === "true")
259
+ return true;
260
+ if (value === "false")
261
+ return false;
262
+ if (/^-?\d+(\.\d+)?$/.test(value))
263
+ return Number(value);
264
+ return value;
265
+ }
266
+ export function buildServicesCheckoutEnvelope(response, checkoutURL, plan, agentType, target) {
267
+ const checkout = response.checkout;
268
+ const platform = platformKeyForHost(plan.host);
269
+ const amount = formatMoney(checkout.checkout.amount_minor, checkout.checkout.currency);
270
+ const presentationHandoff = buildCheckoutHandoff({
271
+ platform,
272
+ url: plan.linkOnlyURL ?? checkoutURL,
273
+ mobileUrl: checkoutURL,
274
+ amount,
275
+ plan,
276
+ ...(agentType ? { agentType } : {}),
277
+ ...(target ? { target } : {}),
278
+ ...(plan.preferredQRSources[0] ? { qrImageURL: plan.preferredQRSources[0] } : {}),
279
+ ...(plan.ideImageAttach?.status === "downloaded" && plan.ideImageAttach.localPath
280
+ ? { localPath: plan.ideImageAttach.localPath }
281
+ : {}),
282
+ ...(platform === "markdown" ? { markdown: buildAgentChatHandoff(plan).markdown } : {}),
283
+ });
284
+ return {
285
+ status: "human_checkout_required",
286
+ result: {
287
+ service_execution_id: response.binding.service_execution_id,
288
+ checkout_id: checkout.checkout.checkout_id,
289
+ capability_id: checkoutCapabilityID(response),
290
+ items: plan.orderItems ?? [],
291
+ amount,
292
+ ...(checkout.checkout.expires_at ? {
293
+ // The shared server-owned payable deadline D: refreshing the page or
294
+ // re-creating a checkout can never extend it; past it the checkout
295
+ // and pending order are terminally cancelled.
296
+ payment_deadline_at: checkout.checkout.expires_at,
297
+ } : {}),
298
+ },
299
+ handoff: presentationHandoff.handoff,
300
+ instruction: presentationHandoff.instruction,
301
+ next: {
302
+ command: plan.afterActionCommand ?? `itpay checkout --id ${checkout.checkout.checkout_id} --token ${checkout.display_token} --json`,
303
+ reason: "仅在用户完成付款操作或要求查询后,读取同一 Checkout 的权威状态",
304
+ },
305
+ recovery: [],
306
+ };
307
+ }
308
+ export function fallbackCardURL(baseURL, checkoutID, displayToken) {
309
+ const root = baseURL.replace(/\/$/, "");
310
+ return `${root}/v1/checkouts/${encodeURIComponent(checkoutID)}/card?display_token=${encodeURIComponent(displayToken)}`;
311
+ }
312
+ export function fallbackCardPNGURL(baseURL, checkoutID, displayToken) {
313
+ return `${fallbackCardURL(baseURL, checkoutID, displayToken)}.png`.replace("/card?", "/card.png?");
314
+ }
315
+ export function checkoutCapabilityID(response, fallback = "") {
316
+ return response.capability_id || fallback;
317
+ }
318
+ export function absolutePublicURL(baseURL, value) {
319
+ try {
320
+ return new URL(value, baseURL.endsWith("/") ? baseURL : `${baseURL}/`).toString();
321
+ }
322
+ catch {
323
+ return value;
324
+ }
325
+ }
326
+ export function formatMoney(amountMinor, currency) {
327
+ return `${(amountMinor / 100).toFixed(2)} ${currency}`;
328
+ }
329
+ export function normalizeServiceActionStatus(status, serviceExecutionID) {
330
+ const normalized = status.trim().toLowerCase();
331
+ if (!serviceActionStatuses.has(normalized)) {
332
+ throw actionInputError(serviceExecutionID, `invalid --status "${status}". Supported: pending, approved, rejected, expired, cancelled`);
333
+ }
334
+ return normalized;
335
+ }
336
+ export function tokenizedCheckoutURL(checkoutURL, displayToken, qrPayload) {
337
+ if (qrPayload.trim().length > 0) {
338
+ return qrPayload;
339
+ }
340
+ if (checkoutURL.trim().length === 0 || displayToken.trim().length === 0) {
341
+ return checkoutURL;
342
+ }
343
+ try {
344
+ const parsed = new URL(checkoutURL);
345
+ if (!parsed.searchParams.has("display_token")) {
346
+ parsed.searchParams.set("display_token", displayToken);
347
+ }
348
+ return parsed.toString();
349
+ }
350
+ catch {
351
+ const separator = checkoutURL.includes("?") ? "&" : "?";
352
+ return `${checkoutURL}${separator}display_token=${encodeURIComponent(displayToken)}`;
353
+ }
354
+ }
355
+ export function publicPurchaseTemplate(value) {
356
+ if (value === undefined)
357
+ return undefined;
358
+ const invalid = () => new CommandContractError("public_operation_contract_invalid", "incomplete public purchase operation", "服务返回的准备购买合同不完整;保留当前快照与已有购买并停止,不能退回旧入口拼接凭据。", [], undefined, { reason: "purchase_contract_missing" });
359
+ if (typeof value !== "object" || value === null || Array.isArray(value) || !("service_id" in value) || typeof value.service_id !== "string" || !("operation_id" in value) || typeof value.operation_id !== "string" || !("input" in value) || typeof value.input !== "object" || value.input === null || Array.isArray(value.input) || !("continuation" in value))
360
+ throw invalid();
361
+ const ref = value.continuation;
362
+ if (typeof ref !== "object" || ref === null || Array.isArray(ref) || !("service_execution_id" in ref) || typeof ref.service_execution_id !== "string" || !("snapshot_id" in ref) || typeof ref.snapshot_id !== "string")
363
+ throw invalid();
364
+ return { command: `itpay services run ${shellArgument(value.service_id)} --operation ${shellArgument(value.operation_id)} --input-json <file> --json`, input_example: { input: value.input, continuation: { service_execution_id: ref.service_execution_id, snapshot_id: ref.snapshot_id } }, executable: false };
365
+ }
@@ -0,0 +1,40 @@
1
+ export function refundStateEnvelope(refund, status) {
2
+ const terminal = ["succeeded", "failed", "cancelled", "rejected"].includes(refund.status);
3
+ let instruction = refund.decision_mode === "manual"
4
+ ? "先告诉用户退款已进入人工审核,原交付保持冻结;人工审核不等于拒绝,等待服务器决定,不要重复申请或承诺结果。"
5
+ : "先告诉用户退款申请已经记录,原交付已冻结;自动路径表示系统会继续处理,但只有最终 succeeded 才能确认退款成功。然后只跟踪同一退款,不读取交付、不创建授权或重复申请。";
6
+ if (!refund.access_locked)
7
+ instruction = "先告诉用户退款当前没有锁定交付;按服务器事实解释当前状态,不要自行推断退款结果、到账时间或交付资格。";
8
+ if (refund.status === "succeeded")
9
+ instruction = "先告诉用户本次退款的钱款已由 ItPay 确认成功;按原订单核对金额范围和其他履约事实,不能推断整单全部退款。平台交付访问仍按返回的锁处理,不恢复旧授权。";
10
+ if (refund.status === "cancelled" || refund.status === "rejected")
11
+ instruction = "先告诉用户退款没有执行,交付资格可以恢复;旧读取授权不会复活,需要用户重新授权。不要把取消或拒绝说成退款成功。";
12
+ if (refund.status === "failed") {
13
+ if (refund.failure_class === "known_no_effect")
14
+ instruction = "先告诉用户本次退款请求确认未发送,不能说退款已成功;Agent 不自行重试,请用户等待平台管理员决定是否重新执行。";
15
+ else if (refund.failure_class === "retryable")
16
+ instruction = "先告诉用户渠道明确返回可重试失败,但 Agent 不会自行重试或重复申请;请用户等待平台管理员处理。";
17
+ else if (refund.failure_class === "outcome_unknown")
18
+ instruction = "先告诉用户退款渠道结果未知,原交付继续锁定且必须先由平台对账;禁止重试、重复申请或承诺退款结果。";
19
+ else if (refund.failure_class === "permanent")
20
+ instruction = "先告诉用户渠道明确拒绝本次退款,当前不能承诺退款成功;不要重试,请用户联系平台支持。";
21
+ else
22
+ instruction = "先告诉用户退款没有正常完成,当前不能承诺退款成功;不要重试或重复申请,请用户联系平台支持。";
23
+ }
24
+ return {
25
+ status,
26
+ result: {
27
+ refund_request_id: refund.refund_request_id,
28
+ order_id: refund.order_id,
29
+ decision_mode: refund.decision_mode,
30
+ refund_status: refund.status,
31
+ consumption_state: refund.consumption_state,
32
+ ...(refund.failure_class ? { failure_class: refund.failure_class } : {}),
33
+ access_locked: refund.access_locked,
34
+ can_cancel: refund.can_cancel,
35
+ },
36
+ instruction: instruction + " consumption_state是平台交付内容的消费事实,access_locked是交付访问锁;不能据此推断线下服务是否已使用或退款资格。",
37
+ next: terminal ? null : { command: `itpay refund get ${refund.refund_request_id} --json`, reason: "按需读取同一退款;不自动循环等待" },
38
+ recovery: [],
39
+ };
40
+ }
@@ -0,0 +1,28 @@
1
+ import { executionInputContext, operationInputTemplate, serviceInputTemplate } from "./input.js";
2
+ import { requiredInputFields } from "./platform.js";
3
+ export function workflowEnvelope(model) {
4
+ if (!model.workflow_entry || ["completed", "delivery"].includes(model.workflow?.status ?? ""))
5
+ return undefined;
6
+ const id = model.execution.service_execution_id;
7
+ const code = model.workflow?.error_code;
8
+ if (["failed", "quota_paused"].includes(model.workflow?.status ?? "") && ["login_required", "rate_limited"].includes(code ?? "")) {
9
+ const login = code === "login_required";
10
+ return { status: code, result: { service_execution_id: id, current_step: model.workflow?.current_step, ...(model.quota ? { quota: model.quota } : {}), ...(model.workflow?.dependency ? { dependency: model.workflow.dependency } : {}) }, instruction: login ? "当前账户需要登录;展示官方登录入口后,按返回恢复动作继续同一执行,不重新创建。" : "本执行达到调用额度;尊重服务端恢复时间,只继续同一执行,不自动重查或创建付款。", next: login ? { command: "itpay auth login --json", reason: "登录后恢复同一执行" } : null, recovery: [] };
11
+ }
12
+ const action = model.workflow?.human_action;
13
+ const state = model.workflow?.status ?? "input_required";
14
+ if (state === "human_action" && action)
15
+ return {
16
+ status: "confirmation_required", result: { service_execution_id: id, human_action: action },
17
+ instruction: "展示本次操作的待确认内容;已有明确同意且目标与版本未变时沿用,只补缺失的真实同意。按完整模板提交同一执行;选择对象不等于购买批准。",
18
+ next: null, recovery: [], interaction: { schema_version: "itpay.interaction.v1", stage: "confirmation_required", input_template: { command: `itpay services action ${id} --action ${action.action_type} --actor-type human --status approved --input-json <file> --json`, input: serviceInputTemplate(action.input_schema), required_input: ["file", ...requiredInputFields(action.input_schema)], executable: false } },
19
+ };
20
+ const inputRequired = state === "input_required";
21
+ const failed = ["failed", "recovery_required", "cancelled"].includes(state);
22
+ const dependency = model.workflow?.dependency;
23
+ return { status: dependency ? "dependency_unavailable" : state, result: { service_execution_id: id, service_id: model.execution.service_id, ...(model.workflow ? { current_step: model.workflow.current_step, workflow_revision: model.workflow.revision, error_code: model.workflow.error_code } : {}), ...(model.workflow?.failure ? { failure: model.workflow.failure } : {}), ...(dependency ? { dependency } : {}), ...(inputRequired ? { input_schema: model.workflow_entry.input_schema } : {}) },
24
+ instruction: dependency ? "服务返回外部依赖故障;保留原输入及已有结果,只按返回的重试条件恢复。不猜供应商或错误原因,也不要求用户更改业务条件。" : failed ? "本执行未完成;核对返回原因和已有付款事实。没有明确恢复动作时停止,不重建购买。" : inputRequired ? "按当前锁定合同写入完整输入文件,继续同一执行,不猜字段。" : state === "payment" ? "已到准备付款入口步骤;报价与当前范围批准仍需核对,实际付款由用户在官方页面完成。" : "继续读取同一执行的当前状态。",
25
+ next: failed || inputRequired || dependency ? null : { command: state === "payment" ? `itpay services checkout ${id} --json` : `itpay services next ${id} --json`, reason: "继续同一执行" }, recovery: [],
26
+ ...(inputRequired ? { interaction: { schema_version: "itpay.interaction.v1", stage: "input_required", input_template: operationInputTemplate(executionInputContext(model.execution), serviceInputTemplate(model.workflow_entry.input_schema, model.workflow?.query_input), requiredInputFields(model.workflow_entry.input_schema)) } } : {}),
27
+ };
28
+ }