@xiashe/sdk 0.1.12 → 0.1.15

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/alipayWallet.mjs CHANGED
@@ -8,6 +8,7 @@ const DEFAULT_TIMEOUT_MS = 8 * 60 * 1000;
8
8
  const RESOURCE_FETCH_TIMEOUT_MS = 20 * 1000;
9
9
  const MAX_OUTPUT_BYTES = 32 * 1024;
10
10
  const SENSITIVE_OUTPUT = /payment[-_ ]?proof|payment[-_ ]?needed|seller_signature|runtimeToken|authorization\s*:/i;
11
+ const SENSITIVE_USER_OUTPUT = /payment[-_ ]?proof|payment[-_ ]?needed|seller_signature|runtimeToken|authorization\s*:|trade[_ -]?no|交易(?:流水)?号|out[_ -]?shake|client[_ -]?session|payguard|validateclientparent|agentfamily/i;
11
12
  const DEFAULT_STATE_DIR = path.join(os.homedir(), '.xiashe', 'alipay-402');
12
13
 
13
14
  export class AlipayWalletError extends Error {
@@ -65,7 +66,7 @@ function statePaths(resourceUrl, options = {}) {
65
66
  const directory = stateDirectory(options);
66
67
  return {
67
68
  directory,
68
- paymentNeededPath: path.join(directory, `${base}.payment-needed`),
69
+ paymentNeededPath: path.join(directory, `${base}.txt`),
69
70
  statePath: path.join(directory, `${base}.json`)
70
71
  };
71
72
  }
@@ -118,8 +119,12 @@ function defaultRunFile(command, args, options) {
118
119
  }
119
120
 
120
121
  function normalizedRunnerResult(result) {
121
- if (typeof result === 'string') return { stdout: result, stderr: '' };
122
- return { stdout: String(result?.stdout || ''), stderr: String(result?.stderr || '') };
122
+ if (typeof result === 'string') return { stdout: result, stderr: '', exitCode: 0 };
123
+ return {
124
+ stdout: String(result?.stdout || ''),
125
+ stderr: String(result?.stderr || ''),
126
+ exitCode: Number.isInteger(result?.exitCode) ? result.exitCode : 0
127
+ };
123
128
  }
124
129
 
125
130
  async function runOfficialCli(args, options = {}) {
@@ -140,6 +145,16 @@ async function runOfficialCli(args, options = {}) {
140
145
  if (error?.killed || error?.signal === 'SIGTERM') {
141
146
  throw new AlipayWalletError('ALIPAY_WALLET_TIMEOUT', '支付宝钱包等待确认超时;请用同一支付命令的 status 查询状态。');
142
147
  }
148
+ // The official buyer CLI can use a non-zero process exit for a user-action
149
+ // state such as “payment pending”. Preserve this invocation's output for
150
+ // the caller to parse before classifying it as a real command failure.
151
+ if (Number.isInteger(error?.code)) {
152
+ return {
153
+ stdout: String(error.stdout || ''),
154
+ stderr: String(error.stderr || ''),
155
+ exitCode: error.code
156
+ };
157
+ }
143
158
  throw new AlipayWalletError('ALIPAY_WALLET_COMMAND_FAILED', '支付宝官方钱包未完成该操作。');
144
159
  }
145
160
  }
@@ -169,6 +184,29 @@ function paymentStatusFromOutput(stdout, stderr) {
169
184
  };
170
185
  }
171
186
 
187
+ function userVisibleWalletOutput(stdout, stderr) {
188
+ const lines = `${stdout}\n${stderr}`
189
+ .replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, '')
190
+ .split(/\r?\n/)
191
+ .map((line) => line.trim())
192
+ .filter((line) => line && !SENSITIVE_USER_OUTPUT.test(line))
193
+ .slice(0, 12);
194
+ return lines.length ? lines.join('\n').slice(0, 1_200) : null;
195
+ }
196
+
197
+ function walletFailureCode(stdout, stderr, exitCode) {
198
+ const output = `${stdout}\n${stderr}`;
199
+ if (/TRADE_PROGRESS_FAILED/i.test(output)) return 'wallet_trade_progress_failed';
200
+ if (/余额不足|insufficient\s+balance/i.test(output)) return 'insufficient_balance';
201
+ if (/已过期|过期|expired/i.test(output)) return 'payment_expired';
202
+ if (/钱包.*(?:未开启|未授权)|wallet.*(?:not\s+enabled|not\s+authorized)/i.test(output)) return 'wallet_not_ready';
203
+ if (/买家.*(?:不匹配|校验失败)|buyer.*(?:mismatch|identity)/i.test(output)) return 'buyer_identity_rejected';
204
+ if (/payguard|validateclientparent|agentfamily=unknown|agent\s*family.*unknown/i.test(output)) return 'wallet_runtime_not_supported';
205
+ if (/账单.*(?:无效|签名)|签名.*(?:无效|失败)|bill.*(?:invalid|signature)/i.test(output)) return 'payment_needed_rejected';
206
+ if (/network|网络|timeout|超时/i.test(output)) return 'wallet_network_error';
207
+ return Number.isInteger(exitCode) && exitCode !== 0 ? 'official_cli_nonzero_exit' : 'official_cli_unclassified';
208
+ }
209
+
172
210
  function paymentMessage(status) {
173
211
  if (status === 'completed') return '支付宝钱包显示本次付款已完成;正在由服务端确认权益。';
174
212
  if (status === 'pending_confirmation') return '支付宝已发起本次付款确认;请打开支付宝 App 完成确认,然后运行 status。';
@@ -176,6 +214,16 @@ function paymentMessage(status) {
176
214
  return '支付宝官方钱包已被调用,但尚未返回可确认的付款状态;请运行 status 查询,或重新发起付款。';
177
215
  }
178
216
 
217
+ function paymentFailureMessage(failureCode) {
218
+ if (failureCode === 'wallet_trade_progress_failed') {
219
+ return '支付宝钱包未能创建可确认的交易;未授予付费 Skill 权益。请在重新加载后的受支持 Agent 环境中重试。';
220
+ }
221
+ if (failureCode === 'wallet_runtime_not_supported') {
222
+ return '当前 Agent 运行环境不支持支付宝 AI付;未授予付费 Skill 权益。';
223
+ }
224
+ return '支付宝官方钱包没有生成可确认的付款状态;未授予付费 Skill 权益。';
225
+ }
226
+
179
227
  async function fetchPaymentNeeded(resourceUrl, options = {}) {
180
228
  const fetchFn = options.fetch || globalThis.fetch;
181
229
  const controller = new AbortController();
@@ -207,15 +255,20 @@ async function fetchPaymentNeeded(resourceUrl, options = {}) {
207
255
  }
208
256
  }
209
257
 
210
- function publicPaymentResult(state, status, diagnostic) {
258
+ function publicPaymentResult(state, status, diagnostic, options = {}) {
259
+ const commandFailed = options.commandFailed === true;
260
+ const failureCode = options.failureCode || 'official_cli_nonzero_exit';
261
+ const failed = status === 'failed' || status === 'wallet_command_failed';
211
262
  return {
212
- ok: status !== 'failed',
263
+ ok: !failed,
213
264
  status,
214
265
  provider: 'alipay-agent-payment',
215
266
  attemptId: `alipay-402:${state.resourceDigest.slice(0, 16)}`,
216
267
  walletInvoked: true,
217
- message: paymentMessage(status),
218
- diagnostic
268
+ message: failed ? paymentFailureMessage(failureCode) : paymentMessage(status),
269
+ diagnostic,
270
+ ...(options.userVisible ? { userVisible: options.userVisible } : {}),
271
+ ...(commandFailed ? { failureCode } : {})
219
272
  };
220
273
  }
221
274
 
@@ -286,20 +339,29 @@ export async function payAlipay402Resource(input, options = {}) {
286
339
  };
287
340
  await writePaymentState(state, options);
288
341
 
289
- const { stdout, stderr } = await runOfficialCli([
342
+ const walletInvocation = await runOfficialCli([
290
343
  '402-buyer-pay',
291
344
  '--session-id', state.sessionId,
292
345
  '--file', state.paymentNeededPath,
293
346
  '--resource-url', resourceUrl,
294
347
  '--intent-summary', intentSummary
295
348
  ], options);
296
- const result = paymentStatusFromOutput(stdout, stderr);
297
- state.status = result.status;
349
+ const result = paymentStatusFromOutput(walletInvocation.stdout, walletInvocation.stderr);
350
+ const commandFailed = walletInvocation.exitCode !== 0 && result.status === 'unknown';
351
+ const status = commandFailed ? 'wallet_command_failed' : result.status;
352
+ state.status = status;
298
353
  state.queryCredential = result.credential;
299
354
  state.updatedAt = new Date().toISOString();
300
355
  await writePaymentState(state, options);
301
- if (result.status === 'completed') await rm(paths.paymentNeededPath, { force: true });
302
- return publicPaymentResult(state, result.status, result.diagnostic);
356
+ if (status === 'completed') await rm(paths.paymentNeededPath, { force: true });
357
+ const failed = commandFailed || status === 'failed';
358
+ return publicPaymentResult(state, status, result.diagnostic, {
359
+ commandFailed: failed,
360
+ failureCode: failed ? walletFailureCode(walletInvocation.stdout, walletInvocation.stderr, walletInvocation.exitCode) : undefined,
361
+ userVisible: ['pending_confirmation', 'completed'].includes(status)
362
+ ? userVisibleWalletOutput(walletInvocation.stdout, walletInvocation.stderr)
363
+ : null
364
+ });
303
365
  }
304
366
 
305
367
  /** Queries an existing local payment attempt using the official query credential. */
@@ -319,19 +381,28 @@ export async function queryAlipay402Resource(input, options = {}) {
319
381
  const queryArg = state.queryCredential.type === 'out_shake_no'
320
382
  ? ['--out-shake-no', state.queryCredential.value]
321
383
  : ['-t', state.queryCredential.value];
322
- const { stdout, stderr } = await runOfficialCli([
384
+ const walletInvocation = await runOfficialCli([
323
385
  '402-query-payment-status',
324
386
  ...queryArg,
325
387
  '--resource-url', resourceUrl
326
388
  ], options);
327
- const result = paymentStatusFromOutput(stdout, stderr);
328
- state.status = result.status;
389
+ const result = paymentStatusFromOutput(walletInvocation.stdout, walletInvocation.stderr);
390
+ const commandFailed = walletInvocation.exitCode !== 0 && result.status === 'unknown';
391
+ const status = commandFailed ? 'wallet_command_failed' : result.status;
392
+ state.status = status;
329
393
  state.queryCredential = result.credential || state.queryCredential;
330
394
  state.updatedAt = new Date().toISOString();
331
395
  await writePaymentState(state, options);
332
- if (result.status === 'completed') {
396
+ if (status === 'completed') {
333
397
  const { paymentNeededPath } = statePaths(resourceUrl, options);
334
398
  await rm(paymentNeededPath, { force: true });
335
399
  }
336
- return publicPaymentResult(state, result.status, result.diagnostic);
400
+ const failed = commandFailed || status === 'failed';
401
+ return publicPaymentResult(state, status, result.diagnostic, {
402
+ commandFailed: failed,
403
+ failureCode: failed ? walletFailureCode(walletInvocation.stdout, walletInvocation.stderr, walletInvocation.exitCode) : undefined,
404
+ userVisible: ['pending_confirmation', 'completed'].includes(status)
405
+ ? userVisibleWalletOutput(walletInvocation.stdout, walletInvocation.stderr)
406
+ : null
407
+ });
337
408
  }
package/index.mjs CHANGED
@@ -6,7 +6,7 @@ import path from 'node:path';
6
6
  import { ConvexHttpClient } from 'convex/browser';
7
7
  import { anyApi as api } from 'convex/server';
8
8
 
9
- export const VERSION = '0.1.12';
9
+ export const VERSION = '0.1.15';
10
10
 
11
11
  export const DEFAULTS = {
12
12
  cn: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiashe/sdk",
3
- "version": "0.1.12",
3
+ "version": "0.1.15",
4
4
  "type": "module",
5
5
  "main": "index.mjs",
6
6
  "exports": {