@xiashe/cli 0.1.32 → 0.1.34

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 (3) hide show
  1. package/README.md +3 -3
  2. package/bin/xiashe.mjs +111 -34
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -50,11 +50,11 @@ Production defaults:
50
50
 
51
51
  `xiashe payment` is the shell-capable Agent path for Alipay AI Pay. It delegates only to the official local `alipay-bot` supplied by `@alipay/agent-payment`; it does not implement or expose `Payment-Needed` / `Payment-Proof` itself.
52
52
 
53
- - `xiashe payment bridge-info` checks that the official buyer CLI can run and exposes the buyer-pay/status command contract required by the bridge. It does not check wallet authorization, merchant activation, or whether a transaction can be created. `payment doctor` remains a manual wallet-status diagnostic only.
54
- - `xiashe payment pay` requests the resource once, saves its exact 402 request in a private local file, and passes that file plus the current Agent business-session ID to the official buyer CLI. It requires an HTTPS AI-Collect resource URL, a visible purchase-purpose summary, and `--confirm`. After confirmation it resolves a valid current host session only from its own local process; `--session-id` remains an optional explicit adapter override. It never generates, prints, or persists a replacement session ID. If no valid host session is available, it returns `host_session_unavailable` and does not invoke the wallet.
53
+ - `xiashe payment bridge-info` checks that the official buyer CLI can run and reports the non-sensitive availability of the `xiashe.payment-host.v1` host-session contract. It does not check wallet authorization, merchant activation, or whether a transaction can be created. `payment doctor` remains a manual wallet-status diagnostic only.
54
+ - `xiashe payment pay` requests the resource once, saves its exact 402 request in a private local file, and passes that file plus the current Agent business-session ID to the official buyer CLI. It requires an HTTPS AI-Collect resource URL, a visible purchase-purpose summary, and `--confirm`. The Agent host must inject its real current task or conversation ID only into that foreground child process as `XIASHE_HOST_BUSINESS_SESSION` under `xiashe.payment-host.v1`. The command does not accept `--session-id`, never generates, prints, persists, or sends a replacement session ID to XiaShe. If no valid host session is available, it returns `host_session_unavailable`; if an override is supplied, it returns `host_session_override_forbidden`; neither result invokes the wallet.
55
55
  - `xiashe payment status` resumes the same local attempt with its official query credential and returns a normalized status only. If Alipay support provides the documented 32-digit `outShakeNo` (with `8282` in positions 11–14, sometimes called a business order number), use `--out-shake-no` or `--business-no` for one read-only status query; it is validated, never stored, and cannot create a payment or acknowledge fulfillment. No wallet protocol payload or proof is printed. A QR or confirmation image is exposed only as `officialMedia` when the official wallet emitted that image in the same invocation; the bridge never generates one itself.
56
56
  - `xiashe payment preflight` requests the seller resource once and validates the public field shape of its 402 bill without opening a wallet or creating a trade. It does not prove that Alipay accepts the seller signature, service binding, or merchant activation. `xiashe payment diagnose` is read-only. It reports the last stored safe diagnosis for that local attempt, including allow-listed official error/sub-codes, message, and request/trace identifiers when present, without contacting Alipay, replaying the resource, or exposing its local query credential.
57
57
 
58
58
  An Agent must never add `--confirm` before the user has explicitly agreed to the displayed purpose. If the wallet is unavailable, obtain permission before running the official `@alipay/agent-payment` setup, then run `payment doctor` again.
59
59
 
60
- For a clean Codex Task, the XiaShe MCP response provides a version-pinned ephemeral command such as `npx -y @xiashe/cli@0.1.32 payment bridge-info`. No global `xiashe` installation is required for that path. Do not run preflight before requesting payment confirmation: it is a read-only diagnostic only. After explicit confirmation, run the supplied payment and status commands exactly as supplied in the foreground. The Agent must inspect bridge JSON instead of suppressing output or guessing status from shell text. Failed attempts include a redacted `diagnosis` object and `userVisible` next step; do not replace them with a generic payment-failed message.
60
+ For a clean Codex Task, the XiaShe MCP response provides a version-pinned ephemeral command such as `npx -y @xiashe/cli@0.1.34 payment bridge-info`. No global `xiashe` installation is required for that path. Do not run preflight before requesting payment confirmation: it is a read-only diagnostic only. After explicit confirmation, ordinary hosts inject `XIASHE_HOST_BUSINESS_SESSION` only into the supplied payment command’s foreground child process. Codex Node REPL uses the private MCP response’s stdin-only adapter because its spawned child does not inherit request metadata. The Agent must inspect bridge JSON instead of suppressing output or guessing status from shell text. Failed attempts include a redacted `diagnosis` object and `userVisible` next step; do not replace them with a generic payment-failed message.
package/bin/xiashe.mjs CHANGED
@@ -51,18 +51,20 @@ const EXIT_RUNTIME = 5;
51
51
  const COMMAND_NAME = process.env.XIASHE_CLI_NAME || 'xiashe';
52
52
  const PRODUCT_NAME = process.env.XIASHE_PRODUCT_NAME || (COMMAND_NAME === 'agentpie' ? 'AgentPie' : 'XiaShe');
53
53
  const PRODUCT_ENV = COMMAND_NAME === 'agentpie' ? 'global' : 'cn';
54
- // A payment business session is host-owned. Keep the allowlist deliberately
55
- // narrow: this CLI may consume a current Task/session identifier from its own
56
- // process, but it must never manufacture, print, or persist one.
57
- const HOST_BUSINESS_SESSION_ENV_NAMES = Object.freeze([
58
- 'XIASHE_HOST_BUSINESS_SESSION',
59
- 'AGENT_HOST_BUSINESS_SESSION',
60
- 'AGENT_SESSION_ID',
61
- 'CODEX_THREAD_ID',
62
- 'OPENAI_THREAD_ID',
63
- 'AGENT_THREAD_ID',
64
- 'THREAD_ID',
65
- 'CONVERSATION_ID'
54
+ // xiashe.payment-host.v1 is the portable contract between an Agent host and
55
+ // this CLI. A host injects its *current* business session only into the
56
+ // foreground payment subprocess. The value never enters MCP, XiaShe APIs,
57
+ // local state, stdout, or a model-visible command argument.
58
+ const PAYMENT_HOST_CONTRACT_VERSION = 'xiashe.payment-host.v1';
59
+ const PAYMENT_HOST_SESSION_ENV = 'XIASHE_HOST_BUSINESS_SESSION';
60
+ const PAYMENT_HOST_STDIN_FLAG = 'host-session-stdin';
61
+ const PAYMENT_HOST_STDIN_MAX_BYTES = 256;
62
+ // These are compatibility adapters for known OpenAI/Codex hosts while their
63
+ // runtimes adopt the portable environment contract. Unknown host variables
64
+ // are deliberately not guessed: other Agents must inject the standard name.
65
+ const PAYMENT_HOST_COMPATIBILITY_PROVIDERS = Object.freeze([
66
+ { environmentName: 'CODEX_THREAD_ID', provider: 'codex_current_task' },
67
+ { environmentName: 'OPENAI_THREAD_ID', provider: 'openai_current_thread' }
66
68
  ]);
67
69
  const HOST_BUSINESS_SESSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,191}$/;
68
70
 
@@ -117,7 +119,7 @@ Usage:
117
119
 
118
120
  ${c} payment bridge-info|doctor
119
121
  ${c} payment preflight --resource-url <https-url>
120
- ${c} payment pay --resource-url <https-url> --intent-summary <text> [--session-id <active-agent-session>] --confirm [--wallet-timeout-ms <ms>]
122
+ ${c} payment pay --resource-url <https-url> --intent-summary <text> --confirm [--wallet-timeout-ms <ms>]
121
123
  ${c} payment status --resource-url <https-url> [--out-shake-no <32-digit-8282>|--business-no <32-digit-8282>]
122
124
  ${c} payment diagnose --resource-url <https-url>
123
125
  ${c} payment support-report --resource-url <https-url>
@@ -265,26 +267,77 @@ function requireArg(value, label) {
265
267
  return value;
266
268
  }
267
269
 
268
- function resolvePaymentBusinessSession(flags) {
269
- const explicitValue = flags['session-id'] ?? flags.sessionId;
270
- if (explicitValue !== undefined) {
271
- if (explicitValue === true || !String(explicitValue).trim()) {
272
- fail('Missing value for --session-id.', EXIT_USAGE);
270
+ function hostSessionContractStatus(resolution = null) {
271
+ return {
272
+ protocolVersion: PAYMENT_HOST_CONTRACT_VERSION,
273
+ requiredEnvironment: PAYMENT_HOST_SESSION_ENV,
274
+ resolution: resolution
275
+ ? { available: true, provider: resolution.provider }
276
+ : { available: false, provider: null },
277
+ sessionValueExposed: false,
278
+ sessionValuePersisted: false,
279
+ sessionValueSentToXiaShe: false
280
+ };
281
+ }
282
+
283
+ async function readPaymentBusinessSessionFromStdin() {
284
+ return await new Promise((resolve) => {
285
+ let received = '';
286
+ let settled = false;
287
+ const finish = (value = null) => {
288
+ if (settled) return;
289
+ settled = true;
290
+ clearTimeout(timeout);
291
+ process.stdin.pause();
292
+ resolve(value);
293
+ };
294
+ const timeout = setTimeout(() => finish(null), 5_000);
295
+ process.stdin.setEncoding('utf8');
296
+ process.stdin.on('data', (chunk) => {
297
+ received += chunk;
298
+ if (Buffer.byteLength(received, 'utf8') > PAYMENT_HOST_STDIN_MAX_BYTES) {
299
+ finish(null);
300
+ }
301
+ });
302
+ process.stdin.once('end', () => finish(received.trim()));
303
+ process.stdin.once('error', () => finish(null));
304
+ process.stdin.resume();
305
+ });
306
+ }
307
+
308
+ async function resolvePaymentBusinessSession(flags) {
309
+ // A model-visible command-line session is not an acceptable trust boundary.
310
+ // Hosts that cannot inject the standard environment must implement the v1
311
+ // adapter rather than asking a user or model to provide a replacement.
312
+ if (flags['session-id'] !== undefined || flags.sessionId !== undefined) {
313
+ return { error: 'host_session_override_forbidden' };
314
+ }
315
+ if (flags[PAYMENT_HOST_STDIN_FLAG] !== undefined) {
316
+ if (flags[PAYMENT_HOST_STDIN_FLAG] !== true) {
317
+ return { error: 'host_session_override_forbidden' };
318
+ }
319
+ const candidate = await readPaymentBusinessSessionFromStdin();
320
+ if (HOST_BUSINESS_SESSION_PATTERN.test(String(candidate || '').trim())) {
321
+ return {
322
+ sessionId: String(candidate).trim(),
323
+ provider: 'codex_node_repl_request_metadata'
324
+ };
273
325
  }
274
- // Preserve the official SDK's validation and error reporting for an
275
- // explicit caller-provided value. This is still useful for compatible
276
- // hosts that expose a session through their own runtime adapter.
326
+ return null;
327
+ }
328
+ const standardCandidate = String(process.env[PAYMENT_HOST_SESSION_ENV] || '').trim();
329
+ if (HOST_BUSINESS_SESSION_PATTERN.test(standardCandidate)) {
277
330
  return {
278
- sessionId: String(explicitValue).trim(),
279
- source: 'explicit_argument'
331
+ sessionId: standardCandidate,
332
+ provider: 'standard_host_environment'
280
333
  };
281
334
  }
282
- for (const environmentName of HOST_BUSINESS_SESSION_ENV_NAMES) {
283
- const candidate = String(process.env[environmentName] || '').trim();
335
+ for (const compatibilityProvider of PAYMENT_HOST_COMPATIBILITY_PROVIDERS) {
336
+ const candidate = String(process.env[compatibilityProvider.environmentName] || '').trim();
284
337
  if (HOST_BUSINESS_SESSION_PATTERN.test(candidate)) {
285
338
  return {
286
339
  sessionId: candidate,
287
- source: 'current_host_environment'
340
+ provider: compatibilityProvider.provider
288
341
  };
289
342
  }
290
343
  }
@@ -301,8 +354,26 @@ function hostBusinessSessionUnavailableResult() {
301
354
  code: 'host_business_session_unavailable',
302
355
  stage: 'host_session_resolution'
303
356
  },
357
+ hostSessionContract: hostSessionContractStatus(),
304
358
  userVisible: '当前 Agent 未提供可用的任务会话标识,尚未发起支付宝付款。',
305
- nextStep: '请在能向本地付款 CLI 提供当前任务 sessionId、threadId 或 conversationId 的 Agent 环境中重试;不要手工创建或粘贴替代值。'
359
+ nextStep: `请让当前 Agent 宿主仅为本次付款子进程注入 ${PAYMENT_HOST_SESSION_ENV}(${PAYMENT_HOST_CONTRACT_VERSION});不要手工创建、粘贴或让模型传入替代值。`
360
+ };
361
+ }
362
+
363
+ async function hostBusinessSessionOverrideForbiddenResult() {
364
+ const hostResolution = await resolvePaymentBusinessSession({});
365
+ return {
366
+ ok: false,
367
+ status: 'host_session_override_forbidden',
368
+ failureCode: 'HOST_SESSION_OVERRIDE_FORBIDDEN',
369
+ walletInvoked: false,
370
+ diagnosis: {
371
+ code: 'host_session_override_forbidden',
372
+ stage: 'host_session_resolution'
373
+ },
374
+ hostSessionContract: hostSessionContractStatus(hostResolution?.error ? null : hostResolution),
375
+ userVisible: '付款命令不接受模型或用户提供的业务会话标识,尚未发起支付宝付款。',
376
+ nextStep: `请让当前 Agent 宿主按 ${PAYMENT_HOST_CONTRACT_VERSION} 私有注入 ${PAYMENT_HOST_SESSION_ENV},然后原样重试 payCommand。`
306
377
  };
307
378
  }
308
379
 
@@ -1239,7 +1310,11 @@ async function cmdPayment(sub, args, global) {
1239
1310
  const flags = parseFlags(args);
1240
1311
  if (sub === 'bridge-info' || sub === 'bridge' || sub === 'prepare') {
1241
1312
  const result = await getAlipayWalletBridgeInfo({ timeoutMs: global.timeoutMs });
1242
- print(result, global);
1313
+ const businessSession = await resolvePaymentBusinessSession({});
1314
+ print({
1315
+ ...result,
1316
+ hostSessionContract: hostSessionContractStatus(businessSession?.error ? null : businessSession)
1317
+ }, global);
1243
1318
  return;
1244
1319
  }
1245
1320
  const walletTimeoutValue = flags['wallet-timeout-ms'] ?? flags.walletTimeoutMs ?? (global.timeoutMsExplicit ? global.timeoutMs : undefined);
@@ -1263,7 +1338,12 @@ async function cmdPayment(sub, args, global) {
1263
1338
  if (sub === 'pay') {
1264
1339
  const resourceUrl = requireArg(flags['resource-url'] || flags.resourceUrl || flags.r, '--resource-url');
1265
1340
  const intentSummary = requireArg(flags['intent-summary'] || flags.intentSummary || flags.i, '--intent-summary');
1266
- const businessSession = resolvePaymentBusinessSession(flags);
1341
+ const businessSession = await resolvePaymentBusinessSession(flags);
1342
+ if (businessSession?.error === 'host_session_override_forbidden') {
1343
+ print(await hostBusinessSessionOverrideForbiddenResult(), global);
1344
+ process.exitCode = EXIT_RUNTIME;
1345
+ return;
1346
+ }
1267
1347
  if (!businessSession) {
1268
1348
  print(hostBusinessSessionUnavailableResult(), global);
1269
1349
  process.exitCode = EXIT_RUNTIME;
@@ -1278,11 +1358,8 @@ async function cmdPayment(sub, args, global) {
1278
1358
  print({
1279
1359
  ...result,
1280
1360
  // This confirms resolution occurred without exposing the identifier or
1281
- // the environment variable that contained it.
1282
- hostBusinessSession: {
1283
- resolved: true,
1284
- source: businessSession.source
1285
- }
1361
+ // the source environment variable that contained it.
1362
+ hostSessionContract: hostSessionContractStatus(businessSession)
1286
1363
  }, global);
1287
1364
  return;
1288
1365
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiashe/cli",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "xiashe": "bin/xiashe.mjs"
@@ -10,7 +10,7 @@
10
10
  "README.md"
11
11
  ],
12
12
  "dependencies": {
13
- "@xiashe/sdk": "0.1.32"
13
+ "@xiashe/sdk": "0.1.34"
14
14
  },
15
15
  "engines": {
16
16
  "node": ">=20"