@alipay/alipay-aipay 1.5.0 → 1.5.1

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.
@@ -131,6 +131,32 @@ function runPlatformEntry() {
131
131
  : '';
132
132
  }
133
133
 
134
+ function optional(args, name) {
135
+ const value = args[name];
136
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
137
+ }
138
+
139
+ function parseJsonArg(args, name, label) {
140
+ const raw = required(args, name);
141
+ try {
142
+ return JSON.parse(raw);
143
+ } catch {
144
+ throw new Error(`${label} 必须是合法 JSON`);
145
+ }
146
+ }
147
+
148
+ function runProductClarify() {
149
+ return renderMessage('product.clarify', 'DEFAULT', {});
150
+ }
151
+
152
+ function runEnterpriseUnsupported(args) {
153
+ const variant = required(args, 'variant');
154
+ if (!['ONBOARDING_ONLY', 'FULL_PROCESS'].includes(variant)) {
155
+ throw new Error('variant 非法,仅支持 ONBOARDING_ONLY、FULL_PROCESS');
156
+ }
157
+ return renderMessage('onboarding.enterprise.unsupported', variant, {});
158
+ }
159
+
134
160
  async function runMccClarify(args) {
135
161
  const productType = required(args, 'product-type');
136
162
  const product = productFor({ ...args, 'product-type': productType });
@@ -212,13 +238,34 @@ async function runMccCandidates(args) {
212
238
  }, { productType });
213
239
  }
214
240
 
215
- async function runMccResolve(args) {
241
+ function renderPlanConfirm(productType, mccCandidate) {
242
+ const product = productFor({ 'product-type': productType });
243
+ return renderMessage('onboarding.plan.confirm', product.variant, {
244
+ mccCode: mccCandidate.mccCode,
245
+ mccName: mccCandidate.mccName,
246
+ mccQualification: mccCandidate.qualification
247
+ }, { productType });
248
+ }
249
+
250
+ async function runPlanConfirm(args) {
251
+ const productType = required(args, 'product-type');
252
+ productFor({ ...args, 'product-type': productType });
253
+ return renderPlanConfirm(productType, {
254
+ mccCode: required(args, 'mcc-code'),
255
+ mccName: required(args, 'mcc-name'),
256
+ qualification: required(args, 'mcc-qualification')
257
+ });
258
+ }
259
+
260
+ async function runMccResolve(args, { renderPlan = false } = {}) {
216
261
  const productType = required(args, 'product-type');
217
262
  productFor({ ...args, 'product-type': productType });
218
263
  emitStageMetricAsync('onboarding.mcc_prepare.start', { productType });
219
264
  const result = mccSearchResult(required(args, 'query'));
220
265
  process.stderr.write(`ALIPAY_AIPAY_INTERNAL:MCC_RESOLUTION=${JSON.stringify(result)}\n`);
221
- if (result.matchType === 'UNIQUE') return '';
266
+ if (result.matchType === 'UNIQUE') {
267
+ return renderPlan ? renderPlanConfirm(productType, result.candidates[0]) : '';
268
+ }
222
269
  if (result.matchType === 'NONE') {
223
270
  return renderMessage('onboarding.mcc.no_match', 'DEFAULT', {}, { productType });
224
271
  }
@@ -294,7 +341,9 @@ async function runMaterialCollect(args) {
294
341
  }, { productType });
295
342
  }
296
343
 
297
- const input = await readStdin();
344
+ const input = optional(args, 'missing-fields') !== undefined
345
+ ? { missingFields: args['missing-fields'] }
346
+ : await readStdin();
298
347
  return renderMessage('materials.category.collect', stateRule.variant, {
299
348
  categoryName,
300
349
  currentStatus: stateRule.currentStatus,
@@ -305,7 +354,9 @@ async function runMaterialCollect(args) {
305
354
  async function runApplicationCandidates(args) {
306
355
  const applicationType = required(args, 'application-type');
307
356
  if (!['WEBAPP', 'MOBILEAPP'].includes(applicationType)) throw new Error('application-type 非法,仅支持 WEBAPP、MOBILEAPP');
308
- const applicationCandidates = await readStdin();
357
+ const applicationCandidates = optional(args, 'candidates-json') !== undefined
358
+ ? parseJsonArg(args, 'candidates-json', '应用候选输入')
359
+ : await readStdin();
309
360
  if (!Array.isArray(applicationCandidates)) throw new Error('应用候选输入必须是 JSON 数组');
310
361
  const onlineCount = applicationCandidates.filter((candidate) => candidate?.status === 'ON_LINE').length;
311
362
  const variant = applicationCandidates.length === 0
@@ -320,8 +371,10 @@ async function runApplicationCandidates(args) {
320
371
  return `${facts}\n\n${selection}`;
321
372
  }
322
373
 
323
- async function runServiceCandidates() {
324
- const serviceCandidates = await readStdin();
374
+ async function runServiceCandidates(args) {
375
+ const serviceCandidates = optional(args, 'candidates-json') !== undefined
376
+ ? parseJsonArg(args, 'candidates-json', '服务候选输入')
377
+ : await readStdin();
325
378
  if (!Array.isArray(serviceCandidates)) throw new Error('服务候选输入必须是 JSON 数组');
326
379
  const variant = serviceCandidates.length === 0 ? 'EMPTY' : 'DEFAULT';
327
380
  const facts = renderMessage('service.candidates.facts', variant, { serviceCandidates });
@@ -341,7 +394,12 @@ function canonicalCandidates(candidates) {
341
394
  async function runApplicationRecheck(args) {
342
395
  const applicationType = required(args, 'application-type');
343
396
  if (!['WEBAPP', 'MOBILEAPP'].includes(applicationType)) throw new Error('application-type 非法,仅支持 WEBAPP、MOBILEAPP');
344
- const input = await readStdin();
397
+ const input = optional(args, 'previous-candidates-json') !== undefined || optional(args, 'current-candidates-json') !== undefined
398
+ ? {
399
+ previousCandidates: parseJsonArg(args, 'previous-candidates-json', '上一轮应用候选快照'),
400
+ currentCandidates: parseJsonArg(args, 'current-candidates-json', '当前应用候选快照')
401
+ }
402
+ : await readStdin();
345
403
  if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('应用候选重查输入必须是 JSON 对象');
346
404
  if (canonicalCandidates(input.previousCandidates) === canonicalCandidates(input.currentCandidates)) return '';
347
405
  const applicationCandidates = input.currentCandidates;
@@ -388,13 +446,102 @@ function normalizeCloseoutInput(input) {
388
446
  return variables;
389
447
  }
390
448
 
391
- async function runCloseout() {
392
- const variables = normalizeCloseoutInput(await readStdin());
449
+ function closeoutInputFromArgs(args) {
450
+ if (optional(args, 'input-json') !== undefined) return parseJsonArg(args, 'input-json', 'closeout 输入');
451
+ const mapping = {
452
+ 'product-name': 'productName',
453
+ 'process-result-title': 'processResultTitle',
454
+ 'integration-result': 'integrationResult',
455
+ 'sandbox-config-state': 'sandboxConfigState',
456
+ 'signing-result': 'signingResult',
457
+ 'service-result': 'serviceResult',
458
+ 'application-result': 'applicationResult',
459
+ 'remaining-actions': 'remainingActions',
460
+ 'manual-verification-items': 'manualVerificationItems',
461
+ 'application-operation-app-id': 'applicationOperationAppId',
462
+ 'application-operation-actual-status': 'applicationOperationActualStatus',
463
+ 'application-operation-next-action': 'applicationOperationNextAction',
464
+ 'next-flow-reminder': 'nextFlowReminder'
465
+ };
466
+ const hasTypedInput = Object.keys(mapping).some((name) => Object.prototype.hasOwnProperty.call(args, name));
467
+ if (!hasTypedInput) return null;
468
+ const input = {};
469
+ for (const [argName, fieldName] of Object.entries(mapping)) {
470
+ if (Object.prototype.hasOwnProperty.call(args, argName)) input[fieldName] = args[argName];
471
+ }
472
+ for (const name of ['product-name', 'process-result-title', 'integration-result', 'signing-result', 'service-result', 'application-result', 'remaining-actions', 'next-flow-reminder']) {
473
+ required(args, name);
474
+ }
475
+ return input;
476
+ }
477
+
478
+ async function runCloseout(args) {
479
+ const variables = normalizeCloseoutInput(closeoutInputFromArgs(args) || await readStdin());
393
480
  const rendered = renderMessage('process.partial_result', 'DEFAULT', variables);
394
481
  emitTerminalMetric({ productName: variables.productName });
395
482
  return rendered;
396
483
  }
397
484
 
485
+ function runServiceCreateSummary(args) {
486
+ return renderMessage('service.create.summary', 'DEFAULT', {
487
+ serviceName: required(args, 'service-name'),
488
+ serviceDescription: required(args, 'service-description'),
489
+ serviceUrl: required(args, 'service-url'),
490
+ servicePricing: required(args, 'service-pricing'),
491
+ serviceSchema: required(args, 'service-schema')
492
+ });
493
+ }
494
+
495
+ function runServiceUpdateConfirm(args) {
496
+ return renderMessage('onboarding.write.confirm', 'SERVICE_UPDATE_ONLY', {
497
+ sessionSummary: required(args, 'session-summary'),
498
+ subjectSummary: required(args, 'subject-summary'),
499
+ productName: 'AI 按量付费',
500
+ actionTypes: '服务修改',
501
+ serviceId: required(args, 'service-id'),
502
+ serviceName: required(args, 'service-name'),
503
+ serviceDescription: required(args, 'service-description'),
504
+ serviceUrl: required(args, 'service-url'),
505
+ servicePricing: required(args, 'service-pricing'),
506
+ serviceSchema: required(args, 'service-schema')
507
+ });
508
+ }
509
+
510
+ function runHarmonyManualCreate() {
511
+ return renderMessage('application.harmony.manual_create', 'DEFAULT', {});
512
+ }
513
+
514
+ function runApplicationReuseNoKey(args) {
515
+ return renderMessage('application.reuse.no_key', 'DEFAULT', {
516
+ appId: required(args, 'app-id')
517
+ });
518
+ }
519
+
520
+ function runSigningResult(args) {
521
+ const variables = {
522
+ actualStatus: required(args, 'actual-status'),
523
+ nextAction: required(args, 'next-action')
524
+ };
525
+ const feeDisplay = optional(args, 'fee-display');
526
+ if (feeDisplay !== undefined) variables.feeDisplay = feeDisplay;
527
+ return renderMessage('signing.operation.result', feeDisplay === undefined ? 'WITHOUT_FEE' : 'WITH_FEE', variables);
528
+ }
529
+
530
+ function runServiceResult(args) {
531
+ return renderMessage('service.operation.result', 'DEFAULT', {
532
+ serviceId: required(args, 'service-id'),
533
+ actualResult: required(args, 'actual-result')
534
+ });
535
+ }
536
+
537
+ function runApplicationResult(args) {
538
+ return renderMessage('application.operation.result', 'DEFAULT', {
539
+ appId: required(args, 'app-id'),
540
+ actualStatus: required(args, 'actual-status'),
541
+ nextAction: required(args, 'next-action')
542
+ });
543
+ }
544
+
398
545
  async function main() {
399
546
  const { command, args } = parseArgs(process.argv);
400
547
  if (command === 'platform-entry') {
@@ -402,6 +549,14 @@ async function main() {
402
549
  if (message) console.log(message);
403
550
  return;
404
551
  }
552
+ if (command === 'product-clarify') {
553
+ console.log(runProductClarify());
554
+ return;
555
+ }
556
+ if (command === 'enterprise-unsupported') {
557
+ console.log(runEnterpriseUnsupported(args));
558
+ return;
559
+ }
405
560
  if (command === 'mcc-clarify') {
406
561
  console.log(await runMccClarify(args));
407
562
  return;
@@ -419,6 +574,15 @@ async function main() {
419
574
  if (message) console.log(message);
420
575
  return;
421
576
  }
577
+ if (command === 'mcc-resolve-plan') {
578
+ const message = await runMccResolve(args, { renderPlan: true });
579
+ if (message) console.log(message);
580
+ return;
581
+ }
582
+ if (command === 'plan-confirm') {
583
+ console.log(await runPlanConfirm(args));
584
+ return;
585
+ }
422
586
  if (command === 'material-collect') {
423
587
  console.log(await runMaterialCollect(args));
424
588
  return;
@@ -428,7 +592,7 @@ async function main() {
428
592
  return;
429
593
  }
430
594
  if (command === 'service-candidates') {
431
- console.log(await runServiceCandidates());
595
+ console.log(await runServiceCandidates(args));
432
596
  return;
433
597
  }
434
598
  if (command === 'application-recheck') {
@@ -437,10 +601,38 @@ async function main() {
437
601
  return;
438
602
  }
439
603
  if (command === 'closeout') {
440
- console.log(await runCloseout());
604
+ console.log(await runCloseout(args));
605
+ return;
606
+ }
607
+ if (command === 'service-create-summary') {
608
+ console.log(runServiceCreateSummary(args));
609
+ return;
610
+ }
611
+ if (command === 'service-update-confirm') {
612
+ console.log(runServiceUpdateConfirm(args));
613
+ return;
614
+ }
615
+ if (command === 'application-harmony-manual-create') {
616
+ console.log(runHarmonyManualCreate());
617
+ return;
618
+ }
619
+ if (command === 'application-reuse-no-key') {
620
+ console.log(runApplicationReuseNoKey(args));
621
+ return;
622
+ }
623
+ if (command === 'signing-result') {
624
+ console.log(runSigningResult(args));
625
+ return;
626
+ }
627
+ if (command === 'service-result') {
628
+ console.log(runServiceResult(args));
629
+ return;
630
+ }
631
+ if (command === 'application-result') {
632
+ console.log(runApplicationResult(args));
441
633
  return;
442
634
  }
443
- throw new Error('用法: onboarding_message_runner.mjs <platform-entry|mcc-resolve|mcc-search|mcc-clarify|mcc-candidates|material-collect|application-candidates|service-candidates|application-recheck|closeout> ...');
635
+ throw new Error('用法: onboarding_message_runner.mjs <platform-entry|product-clarify|enterprise-unsupported|mcc-resolve|mcc-resolve-plan|plan-confirm|mcc-search|mcc-clarify|mcc-candidates|material-collect|application-candidates|service-candidates|application-recheck|closeout|service-create-summary|service-update-confirm|application-harmony-manual-create|application-reuse-no-key|signing-result|service-result|application-result> ...');
444
636
  }
445
637
 
446
638
  if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
@@ -553,6 +553,18 @@ function validateVariantInputShape(messageId, variantName, variables) {
553
553
  }
554
554
 
555
555
  const managedEntrypoints = {
556
+ 'product.unsupported': {
557
+ command: 'node scripts/integration_message_runner.mjs product-unsupported --product-name <产品名>',
558
+ reason: '不支持产品拒绝引导必须由 runner 接收产品名后托管输出,禁止 jq/printf 管道直调 renderer。'
559
+ },
560
+ 'product.clarify': {
561
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs product-clarify,或代码开发上下文执行 node ../normal/scripts/integration_message_runner.mjs product-clarify',
562
+ reason: '产品澄清属于通用托管消息,禁止用 printf/renderer 管道直接输出。'
563
+ },
564
+ 'onboarding.enterprise.unsupported': {
565
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs enterprise-unsupported --variant <ONBOARDING_ONLY|FULL_PROCESS>',
566
+ reason: '企业账号停止分支必须由 Onboarding runner 输出,禁止空 JSON 管道直调 renderer。'
567
+ },
556
568
  'onboarding.windows.redirect': {
557
569
  command: 'node ../normal/scripts/onboarding_message_runner.mjs platform-entry',
558
570
  reason: 'Windows 产品开通入口必须由 runner 根据真实运行平台确定;非 Windows 不得输出该消息。'
@@ -562,7 +574,7 @@ const managedEntrypoints = {
562
574
  reason: '代码开发启动确认必须绑定 runner 同一次项目扫描的项目来源、代码状态和其他支付产品,禁止手写项目扫描事实。'
563
575
  },
564
576
  'integration.checklist.result': {
565
- command: 'node ../normal/scripts/integration_message_runner.mjs checklist-result --variant <AIPAY|WEBPAY|APPPAY>',
577
+ command: 'node ../normal/scripts/integration_message_runner.mjs checklist-result --variant <AIPAY|WEBPAY|APPPAY> --project-path <absPath> --scope-receipt <receipt> --overall-result <result> --failed-items <items> --manual-items <items> ...',
566
578
  reason: '最终 checklist 结论必须由 runner 统一接收最终证据并调用 renderer,同一次代码开发只输出一次。'
567
579
  },
568
580
  'integration.project_candidates.select': {
@@ -585,30 +597,66 @@ const managedEntrypoints = {
585
597
  command: 'node ../normal/scripts/onboarding_discovery_runner.mjs run --product-type <aipay|webpay|apppay> --sales-code <SALES_CODE>',
586
598
  reason: '该 runner 执行本轮适用查询、校验原始脚本事实、输出唯一 DISCOVERY_RESULT envelope,并在同一次 stdout 生成摘要;禁止单独重放摘要。'
587
599
  },
600
+ 'onboarding.plan.confirm': {
601
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs mcc-resolve-plan --query <经营描述|MCC编码|MCC名称> --product-type <aipay|webpay|apppay>,或已持有唯一 MCC 事实时执行 plan-confirm --product-type <...> --mcc-code <...> --mcc-name <...> --mcc-qualification <...>',
602
+ reason: '方案确认必须由 runner 绑定本轮唯一 MCC 事实和产品变体生成,避免 Agent 用 jq/printf/管道手写 renderer 输入。'
603
+ },
588
604
  'materials.category.collect': {
589
- command: 'node ../normal/scripts/onboarding_message_runner.mjs material-collect --category <signing|service|application> --state <INITIAL|PARTIAL|INVALID|APP_MOBILE_INITIAL> --product-type <aipay|webpay|apppay>',
605
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs material-collect --category <signing|service|application> --state <INITIAL|PARTIAL|INVALID|APP_MOBILE_INITIAL> --product-type <aipay|webpay|apppay> [--missing-fields <字段列表>]',
590
606
  reason: '运行时禁止手写 categoryName/currentStatus/missingFields;runner 会归一化“签约资料”“AI 网页应用收款签约截图”“mobilePlatform”等常见别名后再调用 renderer。'
591
607
  },
608
+ 'service.create.summary': {
609
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs service-create-summary --service-name <name> --service-description <desc> --service-url <url> --service-pricing <amount> --service-schema <json>',
610
+ reason: '服务创建摘要必须由 runner 接收已校验五项资料后托管输出,禁止 jq/printf 管道直调 renderer。'
611
+ },
612
+ 'onboarding.write.confirm': {
613
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs service-update-confirm --session-summary <summary> --subject-summary <summary> --service-id <id> --service-name <name> --service-description <desc> --service-url <url> --service-pricing <amount> --service-schema <json>',
614
+ reason: '服务修改确认必须由 runner 固定产品和动作,只绑定当前服务修改摘要,禁止 Agent 自行拼 actionTypes。'
615
+ },
616
+ 'signing.operation.result': {
617
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs signing-result --actual-status <status> [--fee-display <fee>] --next-action <action>',
618
+ reason: '签约分支结果必须由 Onboarding runner 输出,避免直调 renderer 或手写状态。'
619
+ },
620
+ 'service.operation.result': {
621
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs service-result --service-id <id> --actual-result <result>',
622
+ reason: '服务分支结果必须由 Onboarding runner 输出,避免直调 renderer 或手写状态。'
623
+ },
624
+ 'application.harmony.manual_create': {
625
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs application-harmony-manual-create',
626
+ reason: 'HarmonyOS 移动应用边界必须由 Onboarding runner 输出,禁止空 JSON 管道直调 renderer。'
627
+ },
628
+ 'application.reuse.no_key': {
629
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs application-reuse-no-key --app-id <appId>',
630
+ reason: '复用应用缺公钥提示必须由 runner 绑定当前完整 appId 后输出。'
631
+ },
632
+ 'application.operation.result': {
633
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs application-result --app-id <id> --actual-status <status> --next-action <action>',
634
+ reason: '应用分支结果必须由 Onboarding runner 输出,避免直调 renderer 或手写状态。'
635
+ },
592
636
  'application.candidates.facts': {
593
- command: 'node ../normal/scripts/onboarding_message_runner.mjs application-candidates --application-type <WEBAPP|MOBILEAPP>',
637
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs application-candidates --application-type <WEBAPP|MOBILEAPP> --candidates-json <APP_CANDIDATES_JSON>',
594
638
  reason: '应用候选事实和适用选择提示必须由 runner 接收结构化候选数组并统一生成,禁止脚本或 Agent 拼接 Markdown 行。'
595
639
  },
596
640
  'application.candidate.select': {
597
- command: 'node ../normal/scripts/onboarding_message_runner.mjs application-candidates --application-type <WEBAPP|MOBILEAPP>',
641
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs application-candidates --application-type <WEBAPP|MOBILEAPP> --candidates-json <APP_CANDIDATES_JSON>',
598
642
  reason: '应用候选选择必须由 runner 绑定同一份结构化候选快照生成,禁止脚本或 Agent 单独调用。'
599
643
  },
600
644
  'service.candidates.facts': {
601
- command: 'node ../normal/scripts/onboarding_message_runner.mjs service-candidates',
645
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs service-candidates --candidates-json <SERVICE_CANDIDATES_JSON>',
602
646
  reason: '服务候选事实和适用选择提示必须由 runner 接收结构化候选数组并统一生成,禁止脚本或 Agent 拼接 Markdown 行。'
603
647
  },
604
648
  'service.candidate.select': {
605
- command: 'node ../normal/scripts/onboarding_message_runner.mjs service-candidates',
649
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs service-candidates --candidates-json <SERVICE_CANDIDATES_JSON>',
606
650
  reason: '服务候选选择必须由 runner 绑定同一份结构化候选快照生成,禁止脚本或 Agent 单独调用。'
607
651
  },
608
652
  'process.partial_result': {
609
- command: 'node ../normal/scripts/onboarding_message_runner.mjs closeout',
653
+ command: 'node ../normal/scripts/onboarding_message_runner.mjs closeout --product-name <name> --process-result-title <title> --integration-result <result> --signing-result <result> --service-result <result> --application-result <result> --remaining-actions <actions> --next-flow-reminder <reminder>',
610
654
  reason: '产品开通 Step 6 最终收口必须由 runner 统一合并代码开发状态、产品开通分支状态、最近应用分支结果和上线前关键点;禁止 Agent 手写、缩写或删减 AI 网页应用收款限制引导。'
611
655
  },
656
+ 'sandbox.android.client': {
657
+ command: 'node ../normal/scripts/integration_message_runner.mjs sandbox-android-client',
658
+ reason: '沙箱支付宝 Android 下载说明必须由 Integration runner 输出,避免 Agent 为静态消息直调 renderer 或追加 shell 过滤。'
659
+ },
612
660
  'auth.page': {
613
661
  command: 'bash modules/scripts/auth.sh init --defer-open --scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>,页面 stdout 独立送达后执行 bash modules/scripts/auth.sh open ...',
614
662
  reason: '授权页必须由 auth.sh 绑定本次 CLI deviceCode、固定产品/MCC 上下文和 URL 白名单;受控 opener 必须在页面消息独立送达后执行,后续 auth.pending 不得合并到同一条对客回复。',
@@ -1245,6 +1293,18 @@ async function readStdin() {
1245
1293
  return input.trim() ? JSON.parse(input) : {};
1246
1294
  }
1247
1295
 
1296
+ function inputJsonArg() {
1297
+ const index = process.argv.indexOf('--input-json');
1298
+ if (index < 0) return null;
1299
+ const value = process.argv[index + 1];
1300
+ if (value === undefined || value.startsWith('--')) throw new Error('参数 --input-json 缺少取值');
1301
+ try {
1302
+ return JSON.parse(value);
1303
+ } catch {
1304
+ throw new Error('--input-json 必须是合法 JSON 对象');
1305
+ }
1306
+ }
1307
+
1248
1308
  async function main() {
1249
1309
  if (process.argv.includes('--check-catalog')) {
1250
1310
  const errors = validateCatalog();
@@ -1275,7 +1335,7 @@ async function main() {
1275
1335
  const variant = variantIndex >= 0 ? process.argv[variantIndex + 1] : 'DEFAULT';
1276
1336
  if (!messageId) throw new Error('用法: render_customer_message.mjs <messageId> [--variant NAME]');
1277
1337
  assertManagedCliAllowed(messageId);
1278
- const variables = await readStdin();
1338
+ const variables = inputJsonArg() ?? await readStdin();
1279
1339
  console.log(renderMessageWithFallback(messageId, variant, variables));
1280
1340
  }
1281
1341
 
@@ -396,7 +396,9 @@ export function messageMetricEvent(messageId, variantName, variables = {}, conte
396
396
 
397
397
  export function emitMetricEvent(event, { env = process.env, stream = process.stderr, upload = true } = {}) {
398
398
  if (!telemetryEnabled(env) || !event) return false;
399
- stream.write(`${internalPrefix}METRIC_EVENT=${JSON.stringify(event)}\n`);
399
+ if (stream && typeof stream.write === 'function') {
400
+ stream.write(`${internalPrefix}METRIC_EVENT=${JSON.stringify(event)}\n`);
401
+ }
400
402
  if (upload) scheduleSyncLogUpload(event, env);
401
403
  return true;
402
404
  }
@@ -448,7 +450,7 @@ export function emitMessageMetric(messageId, variantName, variables = {}, contex
448
450
  try {
449
451
  const event = messageMetricEvent(messageId, variantName, variables, context);
450
452
  if (event?.stage === 'run.terminal' && telemetryEnabled()) updateExistingRunStateFromEvent(event);
451
- return emitMetricEvent(event);
453
+ return emitMetricEvent(event, { stream: null });
452
454
  } catch {
453
455
  return false;
454
456
  }
@@ -460,7 +462,7 @@ export function emitTerminalMetric(context = {}) {
460
462
  const event = stageMetricEvent('run.terminal', context);
461
463
  if (!event) return false;
462
464
  updateExistingRunStateFromEvent(event);
463
- return emitMetricEvent(event);
465
+ return emitMetricEvent(event, { stream: null });
464
466
  } catch {
465
467
  return false;
466
468
  }