@alipay/alipay-aipay 1.6.3 → 1.6.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alipay/alipay-aipay",
3
- "version": "1.6.3",
3
+ "version": "1.6.4",
4
4
  "description": "支付宝 AI 付 Skill 安装包 - 优先安装 Agent Skill,并在 macOS/Linux 后台准备用户级 CLI 工具",
5
5
  "type": "module",
6
6
  "bin": {
package/skill/VERSION CHANGED
@@ -1 +1 @@
1
- 1.6.3
1
+ 1.6.4
@@ -320,15 +320,15 @@ emit_stage_metric_async() {
320
320
  [ -f "$telemetry_script" ] && command -v node >/dev/null 2>&1 || return 0
321
321
  if [ -n "$product_name" ]; then
322
322
  if [ "${ALIPAY_AIPAY_TEST_MODE:-0}" = "1" ]; then
323
- node "$telemetry_script" stage "$stage" --product-name "$product_name"
323
+ node "$telemetry_script" stage-async "$stage" --product-name "$product_name"
324
324
  else
325
- node "$telemetry_script" stage "$stage" --product-name "$product_name" >/dev/null 2>&1 &
325
+ node "$telemetry_script" stage-async "$stage" --product-name "$product_name" >/dev/null 2>&1
326
326
  fi
327
327
  else
328
328
  if [ "${ALIPAY_AIPAY_TEST_MODE:-0}" = "1" ]; then
329
- node "$telemetry_script" stage "$stage"
329
+ node "$telemetry_script" stage-async "$stage"
330
330
  else
331
- node "$telemetry_script" stage "$stage" >/dev/null 2>&1 &
331
+ node "$telemetry_script" stage-async "$stage" >/dev/null 2>&1
332
332
  fi
333
333
  fi
334
334
  return 0
@@ -240,7 +240,7 @@ export function runFlowCli(argv = process.argv) {
240
240
  for (const stage of stageDeclarations(phaseBlock, productType)) {
241
241
  emitStageMetricAsync(stage, { productType });
242
242
  if (stage === 'integration.user_confirmed' || stage === 'onboarding.user_confirmed') {
243
- markTelemetryFlowStart();
243
+ markTelemetryFlowStart(process.env, Date.now(), productType);
244
244
  }
245
245
  }
246
246
  process.stdout.write(`${loadFlowBundle(flow, phase, { productType })}\n`);
@@ -617,16 +617,17 @@ export function emitStageMetric(stage, context = {}, env = process.env) {
617
617
 
618
618
  export function emitStageMetricAsync(stage, context = {}, env = process.env) {
619
619
  try {
620
- if (!telemetryEnabled(env) || !stageRules.has(stage)) return false;
620
+ if (!telemetryEnabled(env) || !stageRules.has(stage) || stage === 'run.terminal') return false;
621
+ const event = stageMetricEvent(stage, context, Date.now(), env);
622
+ if (!event) return false;
621
623
  if (normalizeSwitch(env.ALIPAY_AIPAY_TEST_MODE) === '1') {
622
- return emitStageMetric(stage, context, env);
624
+ return emitMetricEvent(event, { env });
623
625
  }
624
- const args = [scriptPath(), 'stage', stage, '--timestamp-ms', String(Date.now())];
626
+ const args = [scriptPath(), 'stage', event.stage, '--timestamp-ms', String(event.timestamp)];
625
627
  const options = [
626
- ['run-id', context.runId || context.run_id],
627
- ['root-intent', context.rootIntent || context.root_intent],
628
- ['product-type', context.productType || context.product_type],
629
- ['product-name', context.productName]
628
+ ['run-id', event.run_id],
629
+ ['root-intent', event.root_intent],
630
+ ['product-type', event.product_type]
630
631
  ];
631
632
  for (const [name, value] of options) {
632
633
  if (typeof value === 'string' && value) args.push(`--${name}`, value);
@@ -653,7 +654,7 @@ export function emitStageMetricAsync(stage, context = {}, env = process.env) {
653
654
  export function emitMessageMetric(messageId, variantName, variables = {}, context = {}) {
654
655
  try {
655
656
  const event = messageMetricEvent(messageId, variantName, variables, context);
656
- if (event?.stage === 'run.terminal') updateExistingRunStateFromEvent(event);
657
+ if (event?.stage === 'run.terminal') updateExistingRunStateFromTerminalEvent(event);
657
658
  return emitMetricEvent(event, { stream: null });
658
659
  } catch {
659
660
  return false;
@@ -664,7 +665,7 @@ export function emitTerminalMetric(context = {}) {
664
665
  try {
665
666
  const event = stageMetricEvent('run.terminal', context);
666
667
  if (!event) return false;
667
- updateExistingRunStateFromEvent(event);
668
+ updateExistingRunStateFromTerminalEvent(event);
668
669
  return emitMetricEvent(event, { stream: null });
669
670
  } catch {
670
671
  return false;
@@ -871,10 +872,6 @@ function createOrReuseRunState(args, env = process.env, nowMs = Date.now()) {
871
872
  return createRunState(args, env, nowMs);
872
873
  }
873
874
 
874
- function flowStartStage(stage) {
875
- return stage === 'integration.user_confirmed' || stage === 'onboarding.user_confirmed';
876
- }
877
-
878
875
  function withFlowStartObserved(state, nowMs = Date.now()) {
879
876
  const counts = normalizeUserInputCounts(state.user_input_counts);
880
877
  return {
@@ -888,36 +885,17 @@ function withFlowStartObserved(state, nowMs = Date.now()) {
888
885
  };
889
886
  }
890
887
 
891
- function updateRunStateFromEvent(event, env = process.env) {
892
- const existing = readRunState(env) || {};
893
- let next = {
894
- ...existing,
895
- schema_ver: 1,
896
- run_id: event.run_id,
897
- root_intent: event.root_intent || existing.root_intent || '',
898
- product_type: event.product_type && event.product_type !== 'UNKNOWN' ? event.product_type : existing.product_type || 'UNKNOWN',
899
- created_at: existing.created_at || Date.now(),
900
- updated_at: Date.now()
901
- };
902
- if (flowStartStage(event.stage)) {
903
- next = withFlowStartObserved(next, Number.isFinite(Number(event.timestamp)) ? Number(event.timestamp) : Date.now());
904
- }
905
- if (event.stage === 'run.terminal') {
906
- next.terminal = true;
907
- next.terminal_at = Number.isFinite(Number(event.timestamp)) ? Number(event.timestamp) : Date.now();
908
- } else if (existing.terminal && existing.run_id === event.run_id) {
909
- next.terminal = existing.terminal;
910
- next.terminal_at = existing.terminal_at;
911
- }
912
- writeRunState(next, env);
913
- }
914
-
915
- export function markTelemetryFlowStart(env = process.env, nowMs = Date.now()) {
888
+ export function markTelemetryFlowStart(env = process.env, nowMs = Date.now(), productType = '') {
916
889
  if (!telemetryEnabled(env)) return null;
917
890
  const existing = readRunState(env);
918
891
  if (!existing?.run_id || existing.terminal) return null;
892
+ const requestedProductType = canonicalProductType(productType);
893
+ if (requestedProductType && !compatibleProductType(existing.product_type, requestedProductType)) return null;
919
894
  const next = withFlowStartObserved({
920
895
  ...existing,
896
+ product_type: requestedProductType
897
+ ? mergeProductType(existing.product_type, requestedProductType)
898
+ : existing.product_type,
921
899
  updated_at: nowMs
922
900
  }, nowMs);
923
901
  writeRunState(next, env);
@@ -961,7 +939,8 @@ export function recordTelemetryUserInputForCurrentPhase(inputId, env = process.e
961
939
  return recordTelemetryUserInput(counts.flow_start_observed ? 'after-flow' : 'before-flow', env, nowMs, inputId);
962
940
  }
963
941
 
964
- function updateExistingRunStateFromEvent(event, env = process.env) {
942
+ function updateExistingRunStateFromTerminalEvent(event, env = process.env) {
943
+ if (event?.stage !== 'run.terminal') return false;
965
944
  const existing = readRunState(env);
966
945
  if (!existing) return false;
967
946
  if (existing.run_id !== event.run_id) return false;
@@ -969,7 +948,19 @@ function updateExistingRunStateFromEvent(event, env = process.env) {
969
948
  rootIntent: event.root_intent,
970
949
  productType: event.product_type
971
950
  })) return false;
972
- updateRunStateFromEvent(event, env);
951
+ writeRunState({
952
+ ...existing,
953
+ schema_ver: 1,
954
+ run_id: event.run_id,
955
+ root_intent: event.root_intent || existing.root_intent || '',
956
+ product_type: event.product_type && event.product_type !== 'UNKNOWN'
957
+ ? event.product_type
958
+ : existing.product_type || 'UNKNOWN',
959
+ created_at: existing.created_at || Date.now(),
960
+ updated_at: Date.now(),
961
+ terminal: true,
962
+ terminal_at: Number.isFinite(Number(event.timestamp)) ? Number(event.timestamp) : Date.now()
963
+ }, env);
973
964
  return true;
974
965
  }
975
966
 
@@ -1752,6 +1743,17 @@ export async function runTelemetryCli(argv = process.argv) {
1752
1743
  return 0;
1753
1744
  }
1754
1745
  if (!telemetryEnabled(process.env)) return 0;
1746
+ if (command === 'stage-async') {
1747
+ assertCliArgs(args, new Set([
1748
+ 'run-id',
1749
+ 'root-intent',
1750
+ 'product-type',
1751
+ 'product-name'
1752
+ ]), 1);
1753
+ if (args._[0] === 'run.terminal') throw new Error('run.terminal 必须由前台终态入口上报');
1754
+ emitStageMetricAsync(args._[0], contextFromCliArgs(args), process.env);
1755
+ return 0;
1756
+ }
1755
1757
  if (command !== 'stage' || args._.length !== 1) {
1756
1758
  emitTelemetryError(new Error('invalid telemetry arguments'));
1757
1759
  return 0;
@@ -1768,7 +1770,7 @@ export async function runTelemetryCli(argv = process.argv) {
1768
1770
  'flow-start-observed'
1769
1771
  ]), 1);
1770
1772
  const event = stageMetricEvent(args._[0], contextFromCliArgs(args), timestampFromCliArgs(args));
1771
- if (event) updateExistingRunStateFromEvent(event);
1773
+ if (event?.stage === 'run.terminal') updateExistingRunStateFromTerminalEvent(event);
1772
1774
  emitMetricEvent(event);
1773
1775
  return 0;
1774
1776
  } catch (error) {
@@ -1,4 +1,4 @@
1
1
  - 字段白名单:stage/flow、产品、根意图、时间戳、Skill 版本、宿主/模型、匿名机器哈希、`run_id`、终态计数和终态原因枚举;禁密钥/账号密码/订单/交易号/截图/凭证/用户输入/路径/URL/响应/命令输出/堆栈原文;失败不影响业务,内部行 `ALIPAY_AIPAY_INTERNAL:`。
2
2
  - run:`start-run` 创建/复用 `run_id`,脚本继承 `PLATFORM_ID=<run_id>`;`root_intent + product_type` 判定复用/新建,`UNKNOWN` 补全产品仍复用同一 `run_id`;换产品/根意图/已终态/重新开始/重新走流程/作废本轮流程才新建。`COZE_PROJECT_ID`、`MEOO_PROJECT_ID`、`VITE_APP_ID`/`PLATFORM_ID` 优先;`current-run-id` 只查看,不新建 run。outbox 2xx 才删;维护/测试/Benchmark 禁上传。
3
- - 计数:统计窗口从首次触发本 Skill 的用户消息开始,到当前 `run_id` 的 `run.terminal` 事件生成为止。只计用户在 Agent 对话框发送的消息,不计宿主权限弹窗、系统授权、网页授权、扫码、Skill stage、runner、工具调用或自动轮询;文字、图片/文件上传、空白或短确认消息、材料补充、确认回复和中途插话都按一条用户消息计,同一条消息同时包含文字和图片/文件时仍只计一条。`USER_INPUT_ID` 由 Agent 从首次触发本 Skill 的用户消息开始,在当前对话内给每条新用户消息分配单调序号,同一消息重试复用同一值;禁止包含或派生自用户输入内容、文件名、路径、截图/OCR、业务材料、stage 名或工具调用序号,运行时只保存本地 hash 且不上报。`start-run --input-id "$USER_INPUT_ID"` 自动把触发当前 run 的首条用户输入计为 `before-flow`;若中断/压缩后用新的 `USER_INPUT_ID` 重复进入同一未终态 run,`start-run` 会按当前 phase 幂等补计该用户消息。`reply confirm --reply "$USER_REPLY" --input-id "$USER_INPUT_ID"` 和 onboarding recovery 自动按当前 phase 幂等计数,禁止再为同一条回复手动执行 `count-user-input`。其他用户输入到达当前 run 后,包括用户中途打断、补充材料、只上传图片/文件或只问问题,在继续任何 Skill 动作或对该输入作业务答复前立刻执行 `telemetry count-user-input --phase before-flow|after-flow --input-id "$USER_INPUT_ID"` 写本地 run state,不联网;重复同一 `USER_INPUT_ID` 不加计。`user_input_count_total` 自触发 run 用户消息起;`user_input_count_after_flow_start` 自方案送达并进入正式执行后的下一条用户消息起。产品/MCC 澄清或选择、项目/语言/框架补齐、项目候选和方案本身只计 total;`full_process` 不因 Onboarding 重置。`flow_start_observed`=正式 flow start,由 `integration.user_confirmed|onboarding.user_confirmed` 自动标记但不计数;flow start 前拒绝 `after-flow`,用户 kill 未终态不补报。closeout 默认派生本地计数;状态为空时终态使用首条输入的最小合法计数,状态不可用且已核对时才显式传三项;runner 拒绝缺项、负数、`after > total`、已观测 flow start 但 `after >= total`。`terminal_reason` 只允许 `completed|partial_with_remaining_actions|integration_blocked|unsupported_product|unsupported_platform|enterprise_account_unsupported|unknown`,由终态 runner 依据机器事实派生,禁止自由文本原因。终态只追加计数和原因枚举,不新增确认点、stage 或独立上报。`run.terminal` 入队后以短超时前台 drain,仅当前 terminal 文件,避免流程最后的 detached worker 被宿主回收;当前 terminal 文件成功发送但仍有历史 outbox 积压时挂延迟后台补传;若文件仍在且是网络类错误,短重试一次并挂延迟后台补传,同时输出内部 `PERMISSIONED_FLUSH_NEEDED=true` 供 Agent 当场进入通用补传路径;失败保留 outbox。`c4=ext_info`,计数合法且有 `root_intent` 才写 JSON。
3
+ - 计数:统计窗口从首次触发本 Skill 的用户消息开始,到当前 `run_id` 的 `run.terminal` 事件生成为止。只计用户在 Agent 对话框发送的消息,不计宿主权限弹窗、系统授权、网页授权、扫码、Skill stage、runner、工具调用或自动轮询;文字、图片/文件上传、空白或短确认消息、材料补充、确认回复和中途插话都按一条用户消息计,同一条消息同时包含文字和图片/文件时仍只计一条。`USER_INPUT_ID` 由 Agent 从首次触发本 Skill 的用户消息开始,在当前对话内给每条新用户消息分配单调序号,同一消息重试复用同一值;禁止包含或派生自用户输入内容、文件名、路径、截图/OCR、业务材料、stage 名或工具调用序号,运行时只保存本地 hash 且不上报。`start-run --input-id "$USER_INPUT_ID"` 自动把触发当前 run 的首条用户输入计为 `before-flow`;若中断/压缩后用新的 `USER_INPUT_ID` 重复进入同一未终态 run,`start-run` 会按当前 phase 幂等补计该用户消息。`reply confirm --reply "$USER_REPLY" --input-id "$USER_INPUT_ID"` 和 onboarding recovery 自动按当前 phase 幂等计数,禁止再为同一条回复手动执行 `count-user-input`。其他用户输入到达当前 run 后,包括用户中途打断、补充材料、只上传图片/文件或只问问题,在继续任何 Skill 动作或对该输入作业务答复前立刻执行 `telemetry count-user-input --phase before-flow|after-flow --input-id "$USER_INPUT_ID"` 写本地 run state,不联网;重复同一 `USER_INPUT_ID` 不加计。`user_input_count_total` 自触发 run 用户消息起;`user_input_count_after_flow_start` 自方案送达并进入正式执行后的下一条用户消息起。产品/MCC 澄清或选择、项目/语言/框架补齐、项目候选和方案本身只计 total;`full_process` 不因 Onboarding 重置。`flow_start_observed`=正式 flow start,由 `integration.user_confirmed|onboarding.user_confirmed` 的 flow loader 前台写入自动标记并在 state 为 `UNKNOWN` 时绑定已确认产品,但不计数;detached stage 只使用父进程固定的 `run_id/root_intent/product_type/timestamp` 投递事件,不读取后续 run 归因、不修改 run state。flow start 前拒绝 `after-flow`,用户 kill 未终态不补报。closeout 默认派生本地计数;状态为空时终态使用首条输入的最小合法计数,状态不可用且已核对时才显式传三项;runner 拒绝缺项、负数、`after > total`、已观测 flow start 但 `after >= total`。`terminal_reason` 只允许 `completed|partial_with_remaining_actions|integration_blocked|unsupported_product|unsupported_platform|enterprise_account_unsupported|unknown`,由终态 runner 依据机器事实派生,禁止自由文本原因。终态只追加计数和原因枚举,不新增确认点、stage 或独立上报。`run.terminal` 入队后以短超时前台 drain,仅当前 terminal 文件,避免流程最后的 detached worker 被宿主回收;当前 terminal 文件成功发送但仍有历史 outbox 积压时挂延迟后台补传;若文件仍在且是网络类错误,短重试一次并挂延迟后台补传,同时输出内部 `PERMISSIONED_FLUSH_NEEDED=true` 供 Agent 当场进入通用补传路径;失败保留 outbox。`c4=ext_info`,计数合法且有 `root_intent` 才写 JSON。
4
4
  - 补传:不为权限探测新增网络事件;实际低敏上传就是自然探测,宿主已允许访问 collector 时不额外申请权限。任一 telemetry 上传若因 DNS、连接或超时失败导致 outbox 积压,均使用同一套本地判断和补传机制。`upload-status`/`permissioned-flush-status`/`permissioned-flush-needed` 都不联网、不写请求窗口或抑制状态;只要最近错误是 `ENOTFOUND|EAI_AGAIN|ETIMEDOUT` 等 DNS/连接/超时类错误且 outbox 仍有积压,`permissioned-flush-status` 出 `PERMISSIONED_FLUSH_CANDIDATE=true`,`permissioned-flush-needed` 出唯一 `PERMISSIONED_FLUSH_NEEDED=true` 后才按需申请补传权限。`flush-outbox` 前台完成一次有界小批量补传,优先发送积压的 `run.terminal`,再处理其他 outbox;失败、非 2xx 或仍有剩余积压时继续保留 outbox,失败后仍按实时条件继续允许补传,并可挂一个延迟后台 worker 作兜底。目标限定为 POST `collect.alipay.com:443` 的 `/yuyan/` 路径。说明:`是否允许 Agent 上报 Skill 使用过程的统计信息?只含产品类别、时间、Skill 版本、匿名设备标识、人机交互次数等用于帮助改善用户体验的非敏感信息,不会采集包含源代码、用户输入内容等任何敏感信息。` collector 结果非业务条件;无 `inspect-outbox`、`--confirm-reviewed`、`alipay-cli whoami` 兜底。