@alipay/alipay-aipay 1.6.2 → 1.6.3
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 +1 -1
- package/skill/VERSION +1 -1
- package/skill/references/normal/scripts/integration_message_runner.mjs +11 -2
- package/skill/references/normal/scripts/onboarding_message_runner.mjs +16 -3
- package/skill/references/normal/scripts/telemetry.mjs +105 -68
- package/skill/references/normal/telemetry.md +3 -3
- package/skill/references/runtime-entry.md +3 -3
package/package.json
CHANGED
package/skill/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.6.
|
|
1
|
+
1.6.3
|
|
@@ -1231,7 +1231,7 @@ function runProductClarify() {
|
|
|
1231
1231
|
function runProductUnsupported(args) {
|
|
1232
1232
|
return renderMessage('product.unsupported', 'DEFAULT', {
|
|
1233
1233
|
productName: required(args, 'product-name')
|
|
1234
|
-
}, terminalMetricContextFromArgs(args));
|
|
1234
|
+
}, terminalMetricContextFromArgs(args, { terminalReason: 'unsupported_product' }));
|
|
1235
1235
|
}
|
|
1236
1236
|
|
|
1237
1237
|
function runWindowsSandboxSetup() {
|
|
@@ -1313,6 +1313,12 @@ function checklistInputFromArgs(args) {
|
|
|
1313
1313
|
return input;
|
|
1314
1314
|
}
|
|
1315
1315
|
|
|
1316
|
+
function terminalReasonForChecklist(input) {
|
|
1317
|
+
if (input.overallResult === '通过') return 'completed';
|
|
1318
|
+
if (input.overallResult === '未通过' || input.blockingDefectState === 'PRESENT') return 'integration_blocked';
|
|
1319
|
+
return 'partial_with_remaining_actions';
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1316
1322
|
async function runChecklistResult(args) {
|
|
1317
1323
|
const variant = required(args, 'variant');
|
|
1318
1324
|
if (!['AIPAY', 'WEBPAY', 'APPPAY'].includes(variant)) throw new Error('variant 非法,仅支持 AIPAY、WEBPAY、APPPAY');
|
|
@@ -1360,7 +1366,10 @@ async function runChecklistResult(args) {
|
|
|
1360
1366
|
emitTerminalMetric({
|
|
1361
1367
|
rootIntent: 'integration_only',
|
|
1362
1368
|
productType: variant,
|
|
1363
|
-
...terminalMetricContextFromArgs(args, {
|
|
1369
|
+
...terminalMetricContextFromArgs(args, {
|
|
1370
|
+
rootIntent: 'integration_only',
|
|
1371
|
+
terminalReason: terminalReasonForChecklist(input)
|
|
1372
|
+
})
|
|
1364
1373
|
});
|
|
1365
1374
|
}
|
|
1366
1375
|
return rendered;
|
|
@@ -157,7 +157,7 @@ function runPlatformEntry(args = {}) {
|
|
|
157
157
|
}
|
|
158
158
|
return renderMessage('onboarding.windows.redirect', 'DEFAULT', {
|
|
159
159
|
officialUrl: buildWindowsOnboardingRedirectUrl(runContext.run_id)
|
|
160
|
-
}, terminalMetricContextFromArgs(args));
|
|
160
|
+
}, terminalMetricContextFromArgs(args, { terminalReason: 'unsupported_platform' }));
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
function parseJsonArg(args, name, label) {
|
|
@@ -178,7 +178,12 @@ function runEnterpriseUnsupported(args) {
|
|
|
178
178
|
if (!['ONBOARDING_ONLY', 'FULL_PROCESS'].includes(variant)) {
|
|
179
179
|
throw new Error('variant 非法,仅支持 ONBOARDING_ONLY、FULL_PROCESS');
|
|
180
180
|
}
|
|
181
|
-
return renderMessage(
|
|
181
|
+
return renderMessage(
|
|
182
|
+
'onboarding.enterprise.unsupported',
|
|
183
|
+
variant,
|
|
184
|
+
{},
|
|
185
|
+
terminalMetricContextFromArgs(args, { terminalReason: 'enterprise_account_unsupported' })
|
|
186
|
+
);
|
|
182
187
|
}
|
|
183
188
|
|
|
184
189
|
async function runMccClarify(args) {
|
|
@@ -751,10 +756,18 @@ function closeoutInputFromArgs(args) {
|
|
|
751
756
|
async function runCloseout(args) {
|
|
752
757
|
const variables = closeoutInputFromArgs(args);
|
|
753
758
|
const rendered = renderMessage('process.partial_result', 'DEFAULT', variables);
|
|
759
|
+
const terminalReason = args['root-intent'] === 'full_process' && variables.integrationResult !== '已完成'
|
|
760
|
+
? 'partial_with_remaining_actions'
|
|
761
|
+
: variables.processResultTitle === '支付产品-产品开通已完成'
|
|
762
|
+
? 'completed'
|
|
763
|
+
: 'partial_with_remaining_actions';
|
|
754
764
|
emitTerminalMetric({
|
|
755
765
|
productName: variables.productName,
|
|
756
766
|
rootIntent: args['root-intent'],
|
|
757
|
-
...terminalMetricContextFromArgs(args, {
|
|
767
|
+
...terminalMetricContextFromArgs(args, {
|
|
768
|
+
rootIntent: args['root-intent'],
|
|
769
|
+
terminalReason
|
|
770
|
+
})
|
|
758
771
|
});
|
|
759
772
|
return rendered;
|
|
760
773
|
}
|
|
@@ -22,17 +22,26 @@ const terminalForegroundUploadTimeoutMs = 1200;
|
|
|
22
22
|
const terminalForegroundUploadMaxAttempts = 2;
|
|
23
23
|
const terminalForegroundUploadRetryDelayMs = 300;
|
|
24
24
|
const terminalBackgroundRetryDelayMs = 1000;
|
|
25
|
+
const permissionedForegroundFlushLimit = 5;
|
|
25
26
|
const terminalCountArgNames = Object.freeze([
|
|
26
27
|
'user-input-count-total',
|
|
27
28
|
'user-input-count-after-flow-start',
|
|
28
29
|
'flow-start-observed'
|
|
29
30
|
]);
|
|
31
|
+
const terminalReasonValues = new Set([
|
|
32
|
+
'completed',
|
|
33
|
+
'partial_with_remaining_actions',
|
|
34
|
+
'integration_blocked',
|
|
35
|
+
'unsupported_product',
|
|
36
|
+
'unsupported_platform',
|
|
37
|
+
'enterprise_account_unsupported',
|
|
38
|
+
'unknown'
|
|
39
|
+
]);
|
|
30
40
|
const initialUserInputId = 'run.start';
|
|
31
41
|
const outboxFlushLimit = 50;
|
|
32
42
|
const outboxClaimTtlMs = 5 * 60 * 1000;
|
|
33
43
|
const outboxMaxRecords = 1000;
|
|
34
44
|
const outboxMaxAgeMs = 7 * 24 * 60 * 60 * 1000;
|
|
35
|
-
const permissionedFlushTtlMs = 12 * 60 * 60 * 1000;
|
|
36
45
|
const platformProjectIdEnvKeys = Object.freeze(['COZE_PROJECT_ID', 'MEOO_PROJECT_ID']);
|
|
37
46
|
const sessionStateEnvKeys = Object.freeze([
|
|
38
47
|
'CODEX_THREAD_ID',
|
|
@@ -227,21 +236,12 @@ function uploadStatusPath(env = process.env) {
|
|
|
227
236
|
return path.join(stateDir(env), 'upload-status.json');
|
|
228
237
|
}
|
|
229
238
|
|
|
230
|
-
function permissionedFlushStatePath(env = process.env) {
|
|
231
|
-
if (env.ALIPAY_AIPAY_TELEMETRY_PERMISSIONED_FLUSH_STATE_FILE) {
|
|
232
|
-
return env.ALIPAY_AIPAY_TELEMETRY_PERMISSIONED_FLUSH_STATE_FILE;
|
|
233
|
-
}
|
|
234
|
-
const source = stableStateSource(env) || (Object.keys(env).length > 0 ? readRunState(env)?.run_id : '') || '';
|
|
235
|
-
return path.join(stateDir(env), `${stateKeyFromSource(source)}.permissioned-flush.json`);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
239
|
export function telemetryStoragePaths(env = process.env) {
|
|
239
240
|
return {
|
|
240
241
|
state_dir: stateDir(env),
|
|
241
242
|
run_state_file: runStatePath(env),
|
|
242
243
|
outbox_dir: outboxDir(env),
|
|
243
|
-
upload_status_file: uploadStatusPath(env)
|
|
244
|
-
permissioned_flush_state_file: permissionedFlushStatePath(env)
|
|
244
|
+
upload_status_file: uploadStatusPath(env)
|
|
245
245
|
};
|
|
246
246
|
}
|
|
247
247
|
|
|
@@ -417,6 +417,12 @@ function parseBooleanFlag(value) {
|
|
|
417
417
|
return null;
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
+
function normalizeTerminalReason(value) {
|
|
421
|
+
if (typeof value !== 'string' || !value) return 'unknown';
|
|
422
|
+
const normalized = value.trim().toLowerCase().replace(/[\s-]+/g, '_');
|
|
423
|
+
return terminalReasonValues.has(normalized) ? normalized : '';
|
|
424
|
+
}
|
|
425
|
+
|
|
420
426
|
function normalizeUserInputId(value) {
|
|
421
427
|
if (typeof value !== 'string') return '';
|
|
422
428
|
const text = value.trim();
|
|
@@ -518,11 +524,14 @@ function terminalExtInfoFromPayload(payload = {}, env = process.env) {
|
|
|
518
524
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return '';
|
|
519
525
|
const counts = validateTerminalCountPayload(payload, env);
|
|
520
526
|
if (!counts) return '';
|
|
527
|
+
const terminalReason = normalizeTerminalReason(payload.terminalReason ?? payload.terminal_reason);
|
|
528
|
+
if (!terminalReason) return '';
|
|
521
529
|
return JSON.stringify({
|
|
522
530
|
user_input_count_total: counts.total,
|
|
523
531
|
user_input_count_after_flow_start: counts.afterFlowStart,
|
|
524
532
|
flow_start_observed: counts.flowStartObserved,
|
|
525
|
-
root_intent: counts.rootIntent
|
|
533
|
+
root_intent: counts.rootIntent,
|
|
534
|
+
terminal_reason: terminalReason
|
|
526
535
|
});
|
|
527
536
|
}
|
|
528
537
|
|
|
@@ -685,6 +694,11 @@ export function terminalMetricContextFromArgs(args, context = {}) {
|
|
|
685
694
|
const value = args[name];
|
|
686
695
|
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
687
696
|
};
|
|
697
|
+
const terminalReason = normalizeTerminalReason(
|
|
698
|
+
optional('terminal-reason') ?? context.terminalReason ?? context.terminal_reason
|
|
699
|
+
);
|
|
700
|
+
if (!terminalReason) throw new Error('--terminal-reason 不在允许枚举中');
|
|
701
|
+
const reasonContext = { terminalReason };
|
|
688
702
|
const hasCountArg = terminalCountArgNames.some((name) => Object.prototype.hasOwnProperty.call(args, name));
|
|
689
703
|
if (hasCountArg) {
|
|
690
704
|
const missing = terminalCountArgNames.filter((name) => optional(name) === undefined);
|
|
@@ -696,9 +710,11 @@ export function terminalMetricContextFromArgs(args, context = {}) {
|
|
|
696
710
|
rootIntent: args['root-intent'] || args.rootIntent || args.root_intent || context.rootIntent || context.root_intent
|
|
697
711
|
}, process.env, { strict: true });
|
|
698
712
|
return {
|
|
713
|
+
rootIntent: counts.rootIntent,
|
|
699
714
|
userInputCountTotal: counts.total,
|
|
700
715
|
userInputCountAfterFlowStart: counts.afterFlowStart,
|
|
701
|
-
flowStartObserved: counts.flowStartObserved
|
|
716
|
+
flowStartObserved: counts.flowStartObserved,
|
|
717
|
+
...reasonContext
|
|
702
718
|
};
|
|
703
719
|
}
|
|
704
720
|
const state = readRunState(process.env);
|
|
@@ -712,9 +728,11 @@ export function terminalMetricContextFromArgs(args, context = {}) {
|
|
|
712
728
|
}, process.env);
|
|
713
729
|
if (!fallbackCounts) return {};
|
|
714
730
|
return {
|
|
731
|
+
rootIntent: fallbackCounts.rootIntent,
|
|
715
732
|
userInputCountTotal: fallbackCounts.total,
|
|
716
733
|
userInputCountAfterFlowStart: fallbackCounts.afterFlowStart,
|
|
717
|
-
flowStartObserved: fallbackCounts.flowStartObserved
|
|
734
|
+
flowStartObserved: fallbackCounts.flowStartObserved,
|
|
735
|
+
...reasonContext
|
|
718
736
|
};
|
|
719
737
|
}
|
|
720
738
|
const counts = validateTerminalCountPayload({
|
|
@@ -725,19 +743,25 @@ export function terminalMetricContextFromArgs(args, context = {}) {
|
|
|
725
743
|
}, process.env);
|
|
726
744
|
if (!counts) return {};
|
|
727
745
|
return {
|
|
746
|
+
rootIntent: counts.rootIntent,
|
|
728
747
|
userInputCountTotal: counts.total,
|
|
729
748
|
userInputCountAfterFlowStart: counts.afterFlowStart,
|
|
730
|
-
flowStartObserved: counts.flowStartObserved
|
|
749
|
+
flowStartObserved: counts.flowStartObserved,
|
|
750
|
+
...reasonContext
|
|
731
751
|
};
|
|
732
752
|
}
|
|
733
753
|
|
|
734
754
|
function contextFromCliArgs(args) {
|
|
735
|
-
|
|
755
|
+
const context = {
|
|
736
756
|
runId: args['run-id'],
|
|
737
757
|
rootIntent: args['root-intent'],
|
|
738
758
|
productType: args['product-type'],
|
|
739
|
-
productName: args['product-name']
|
|
740
|
-
|
|
759
|
+
productName: args['product-name']
|
|
760
|
+
};
|
|
761
|
+
if (args._[0] !== 'run.terminal') return context;
|
|
762
|
+
return {
|
|
763
|
+
...context,
|
|
764
|
+
...terminalMetricContextFromArgs(args, context)
|
|
741
765
|
};
|
|
742
766
|
}
|
|
743
767
|
|
|
@@ -1386,22 +1410,6 @@ function readUploadStatus(env = process.env) {
|
|
|
1386
1410
|
}
|
|
1387
1411
|
}
|
|
1388
1412
|
|
|
1389
|
-
function readPermissionedFlushState(env = process.env, nowMs = Date.now()) {
|
|
1390
|
-
try {
|
|
1391
|
-
const parsed = JSON.parse(fs.readFileSync(permissionedFlushStatePath(env), 'utf8'));
|
|
1392
|
-
if (!parsed || parsed.schema_ver !== 1) return null;
|
|
1393
|
-
const requestedAt = Number(parsed.requested_at || 0);
|
|
1394
|
-
if (!Number.isFinite(requestedAt) || nowMs - requestedAt > permissionedFlushTtlMs) return null;
|
|
1395
|
-
return parsed;
|
|
1396
|
-
} catch {
|
|
1397
|
-
return null;
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
function writePermissionedFlushState(state, env = process.env) {
|
|
1402
|
-
return writePrivateJson(permissionedFlushStatePath(env), { schema_ver: 1, ...state });
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
1413
|
function networkUploadFailure(status) {
|
|
1406
1414
|
const errorCode = safeDimension(status?.error_code || '', '', 80).toUpperCase();
|
|
1407
1415
|
if (permissionedFlushNetworkErrors.has(errorCode)) return errorCode;
|
|
@@ -1415,47 +1423,25 @@ function networkUploadFailure(status) {
|
|
|
1415
1423
|
return '';
|
|
1416
1424
|
}
|
|
1417
1425
|
|
|
1418
|
-
export function permissionedFlushDecision(env = process.env
|
|
1426
|
+
export function permissionedFlushDecision(env = process.env) {
|
|
1419
1427
|
const outboxCount = outboxFiles(env).length;
|
|
1420
1428
|
const status = readUploadStatus(env);
|
|
1421
1429
|
const errorCode = networkUploadFailure(status);
|
|
1422
|
-
const existing = readPermissionedFlushState(env, nowMs);
|
|
1423
|
-
const alreadyRequested = Boolean(existing);
|
|
1424
1430
|
const result = {
|
|
1425
1431
|
needed: false,
|
|
1426
1432
|
outbox_count: outboxCount,
|
|
1427
|
-
already_requested: alreadyRequested,
|
|
1428
1433
|
error_code: errorCode || safeDimension(status?.error_code || '', '', 80),
|
|
1429
1434
|
status: safeDimension(status?.status || 'unknown', 'unknown', 80)
|
|
1430
1435
|
};
|
|
1431
1436
|
if (!telemetryUploadEnabled(env) || outboxCount <= 0 || !errorCode) {
|
|
1432
1437
|
return result;
|
|
1433
1438
|
}
|
|
1434
|
-
if (alreadyRequested) {
|
|
1435
|
-
return {
|
|
1436
|
-
...result,
|
|
1437
|
-
requested_at: existing.requested_at
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
1439
|
result.needed = true;
|
|
1441
1440
|
return result;
|
|
1442
1441
|
}
|
|
1443
1442
|
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
writePermissionedFlushState({
|
|
1447
|
-
requested_at: nowMs,
|
|
1448
|
-
error_code: decision.error_code,
|
|
1449
|
-
outbox_count: decision.outbox_count
|
|
1450
|
-
}, env);
|
|
1451
|
-
return {
|
|
1452
|
-
...decision,
|
|
1453
|
-
requested_at: nowMs
|
|
1454
|
-
};
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
function emitPermissionedFlushNeededMarkers(env = process.env, nowMs = Date.now(), stream = process.stderr) {
|
|
1458
|
-
const decision = permissionedFlushDecision(env, nowMs);
|
|
1443
|
+
function emitPermissionedFlushNeededMarkers(env = process.env, stream = process.stderr) {
|
|
1444
|
+
const decision = permissionedFlushDecision(env);
|
|
1459
1445
|
if (!decision.needed || !stream || typeof stream.write !== 'function') return false;
|
|
1460
1446
|
stream.write(`${internalPrefix}PERMISSIONED_FLUSH_DECISION=${JSON.stringify(decision)}\n`);
|
|
1461
1447
|
stream.write(`${internalPrefix}PERMISSIONED_FLUSH_NEEDED=true\n`);
|
|
@@ -1466,11 +1452,12 @@ export async function flushOutbox(env = process.env, {
|
|
|
1466
1452
|
limit = outboxFlushLimit,
|
|
1467
1453
|
targetFile = '',
|
|
1468
1454
|
timeoutMs = uploadTimeoutMs,
|
|
1469
|
-
sender = sendSyncLogReportDetailed
|
|
1455
|
+
sender = sendSyncLogReportDetailed,
|
|
1456
|
+
prioritizeTerminal = false
|
|
1470
1457
|
} = {}) {
|
|
1471
1458
|
const result = { attempted: 0, sent: 0, retained: 0, invalid: 0 };
|
|
1472
1459
|
if (!telemetryUploadEnabled(env)) return result;
|
|
1473
|
-
const files =
|
|
1460
|
+
const files = outboxFilesForFlush(env, { targetFile, limit, prioritizeTerminal });
|
|
1474
1461
|
for (const file of files) {
|
|
1475
1462
|
const claimedFile = claimOutboxFile(file);
|
|
1476
1463
|
if (!claimedFile) continue;
|
|
@@ -1532,6 +1519,26 @@ export async function flushOutbox(env = process.env, {
|
|
|
1532
1519
|
return result;
|
|
1533
1520
|
}
|
|
1534
1521
|
|
|
1522
|
+
function outboxFilesForFlush(env = process.env, { targetFile = '', limit = 0, prioritizeTerminal = false } = {}) {
|
|
1523
|
+
const files = outboxFiles(env, { targetFile, limit: prioritizeTerminal ? 0 : limit });
|
|
1524
|
+
const ordered = prioritizeTerminal ? prioritizeTerminalOutboxFiles(files) : files;
|
|
1525
|
+
return limit > 0 ? ordered.slice(0, limit) : ordered;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
function prioritizeTerminalOutboxFiles(files) {
|
|
1529
|
+
return files
|
|
1530
|
+
.map((file, index) => {
|
|
1531
|
+
const record = readOutboxRecord(file);
|
|
1532
|
+
const stage = record?.event?.stage || record?.report?.item?.c2 || '';
|
|
1533
|
+
return { file, index, terminal: stage === 'run.terminal' };
|
|
1534
|
+
})
|
|
1535
|
+
.sort((left, right) => {
|
|
1536
|
+
if (left.terminal !== right.terminal) return left.terminal ? -1 : 1;
|
|
1537
|
+
return left.index - right.index;
|
|
1538
|
+
})
|
|
1539
|
+
.map((entry) => entry.file);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1535
1542
|
function spawnOutboxFlush(env = process.env, { delayMs = 0 } = {}) {
|
|
1536
1543
|
if (!telemetryUploadEnabled(env)) return false;
|
|
1537
1544
|
try {
|
|
@@ -1564,6 +1571,34 @@ function spawnOutboxFlush(env = process.env, { delayMs = 0 } = {}) {
|
|
|
1564
1571
|
}
|
|
1565
1572
|
}
|
|
1566
1573
|
|
|
1574
|
+
function permissionedForegroundFlushLimitFromEnv(env = process.env) {
|
|
1575
|
+
const parsed = parseNonNegativeInteger(env.ALIPAY_AIPAY_TELEMETRY_PERMISSIONED_FLUSH_LIMIT);
|
|
1576
|
+
return parsed && parsed > 0 ? Math.min(parsed, outboxFlushLimit) : permissionedForegroundFlushLimit;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
export async function runPermissionedOutboxFlush(env = process.env, {
|
|
1580
|
+
limit = permissionedForegroundFlushLimitFromEnv(env),
|
|
1581
|
+
timeoutMs = uploadTimeoutMs,
|
|
1582
|
+
sender = sendSyncLogReportDetailed,
|
|
1583
|
+
spawnBackground = false
|
|
1584
|
+
} = {}) {
|
|
1585
|
+
const flush = await flushOutbox(env, {
|
|
1586
|
+
limit,
|
|
1587
|
+
timeoutMs,
|
|
1588
|
+
sender,
|
|
1589
|
+
prioritizeTerminal: true
|
|
1590
|
+
});
|
|
1591
|
+
const backgroundRetry = spawnBackground
|
|
1592
|
+
&& flush.attempted > 0
|
|
1593
|
+
&& outboxFiles(env).length > 0
|
|
1594
|
+
? spawnOutboxFlush(env, { delayMs: terminalBackgroundRetryDelayMs })
|
|
1595
|
+
: false;
|
|
1596
|
+
return {
|
|
1597
|
+
flush,
|
|
1598
|
+
background_retry: backgroundRetry
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1567
1602
|
function foregroundFlushTerminalOutboxFile(file, env = process.env) {
|
|
1568
1603
|
if (!telemetryUploadEnabled(env) || !file) return false;
|
|
1569
1604
|
const parsed = parseNonNegativeInteger(env.ALIPAY_AIPAY_TELEMETRY_TERMINAL_FLUSH_TIMEOUT_MS);
|
|
@@ -1606,7 +1641,11 @@ function scheduleSyncLogUpload(event, env = process.env) {
|
|
|
1606
1641
|
return false;
|
|
1607
1642
|
}
|
|
1608
1643
|
if (event?.stage === 'run.terminal') {
|
|
1609
|
-
if (
|
|
1644
|
+
if (foregroundFlushTerminalOutboxFile(queuedFile, env)) {
|
|
1645
|
+
if (outboxFiles(env).length > 0) {
|
|
1646
|
+
spawnOutboxFlush(env, { delayMs: terminalBackgroundRetryDelayMs });
|
|
1647
|
+
}
|
|
1648
|
+
} else {
|
|
1610
1649
|
spawnOutboxFlush(env, { delayMs: terminalBackgroundRetryDelayMs });
|
|
1611
1650
|
emitPermissionedFlushNeededMarkers(env);
|
|
1612
1651
|
}
|
|
@@ -1680,7 +1719,7 @@ export async function runTelemetryCli(argv = process.argv) {
|
|
|
1680
1719
|
}
|
|
1681
1720
|
if (command === 'permissioned-flush-status' && args._.length === 0) {
|
|
1682
1721
|
assertCliArgs(args, new Set(), 0);
|
|
1683
|
-
const decision = permissionedFlushDecision(process.env
|
|
1722
|
+
const decision = permissionedFlushDecision(process.env);
|
|
1684
1723
|
process.stderr.write(`${internalPrefix}PERMISSIONED_FLUSH_STATUS=${JSON.stringify(decision)}\n`);
|
|
1685
1724
|
if (decision.needed) {
|
|
1686
1725
|
process.stderr.write(`${internalPrefix}PERMISSIONED_FLUSH_CANDIDATE=true\n`);
|
|
@@ -1689,7 +1728,7 @@ export async function runTelemetryCli(argv = process.argv) {
|
|
|
1689
1728
|
}
|
|
1690
1729
|
if (command === 'permissioned-flush-needed' && args._.length === 0) {
|
|
1691
1730
|
assertCliArgs(args, new Set(), 0);
|
|
1692
|
-
const decision = permissionedFlushDecision(process.env
|
|
1731
|
+
const decision = permissionedFlushDecision(process.env);
|
|
1693
1732
|
process.stderr.write(`${internalPrefix}PERMISSIONED_FLUSH_DECISION=${JSON.stringify(decision)}\n`);
|
|
1694
1733
|
if (decision.needed) process.stderr.write(`${internalPrefix}PERMISSIONED_FLUSH_NEEDED=true\n`);
|
|
1695
1734
|
return 0;
|
|
@@ -1697,10 +1736,7 @@ export async function runTelemetryCli(argv = process.argv) {
|
|
|
1697
1736
|
if (command === 'flush-outbox') {
|
|
1698
1737
|
assertCliArgs(args, new Set(), 0);
|
|
1699
1738
|
if (!telemetryEnabled(process.env)) return 0;
|
|
1700
|
-
|
|
1701
|
-
if (spawnOutboxFlush(process.env)) {
|
|
1702
|
-
recordPermissionedFlushRequested(process.env, Date.now(), decision);
|
|
1703
|
-
}
|
|
1739
|
+
await runPermissionedOutboxFlush(process.env, { spawnBackground: true });
|
|
1704
1740
|
return 0;
|
|
1705
1741
|
}
|
|
1706
1742
|
if (command === 'flush-outbox-worker') {
|
|
@@ -1725,6 +1761,7 @@ export async function runTelemetryCli(argv = process.argv) {
|
|
|
1725
1761
|
'root-intent',
|
|
1726
1762
|
'product-type',
|
|
1727
1763
|
'product-name',
|
|
1764
|
+
'terminal-reason',
|
|
1728
1765
|
'timestamp-ms',
|
|
1729
1766
|
'user-input-count-total',
|
|
1730
1767
|
'user-input-count-after-flow-start',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
- 字段白名单:stage/flow、产品、根意图、时间戳、Skill 版本、宿主/模型、匿名机器哈希、`run_id
|
|
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
|
|
4
|
-
- 补传:不为权限探测新增网络事件;实际低敏上传就是自然探测,宿主已允许访问 collector
|
|
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。
|
|
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` 兜底。
|
|
@@ -48,11 +48,11 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" render <messageId> --va
|
|
|
48
48
|
|
|
49
49
|
`full_process` 识别根意图后立即执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" telemetry start-run --root-intent full_process --input-id "$USER_INPUT_ID"`;产品已唯一确定时追加 `--product-type <aipay|webpay|apppay>`。`integration_only` 和 `onboarding_only` 已由上表各自 bootstrap 包含同一次 `start-run`,不得重复执行本条 telemetry。命令创建/复用当前流程 `run_id`,并在遥测开启时记录 `run.intent_detected`:同一意图和产品继续复用,`UNKNOWN` 补全产品仍复用同一 `run_id`;明确换产品、换根意图或既有 run 已终态才重建。第三方平台项目 ID 仍优先固定为 `run_id`,不得被显式 run ID 或本地生成值覆盖。业务 stage 只由登记 runner/script 异步记录。
|
|
50
50
|
|
|
51
|
-
遥测默认开启,`ALIPAY_AIPAY_TELEMETRY=0|false|off|no` 关闭事件/outbox/上传/flush;私有 run context 仍供 `PLATFORM_ID` 与终态生命周期。失败静默,不改变业务/消息/确认,stdout 空且 stderr 内部行不对客。`integration_only` 由 checklist 终结;Onboarding 由 closeout 或登记提前终止消息终结。上报权限不新增网络探针;实际低敏上传就是自然探测,collector 可达时不申请。任一终态后若
|
|
51
|
+
遥测默认开启,`ALIPAY_AIPAY_TELEMETRY=0|false|off|no` 关闭事件/outbox/上传/flush;私有 run context 仍供 `PLATFORM_ID` 与终态生命周期。失败静默,不改变业务/消息/确认,stdout 空且 stderr 内部行不对客。`integration_only` 由 checklist 终结;Onboarding 由 closeout 或登记提前终止消息终结。上报权限不新增网络探针;实际低敏上传就是自然探测,collector 可达时不申请。任一终态后若 telemetry outbox 因 DNS、连接或超时失败积压,按 `normal/telemetry.md` 先本地状态判断,确实需要时按需申请补传权限;`flush-outbox` 前台小批量补传并优先发送 `run.terminal`,失败仍不影响业务收口。
|
|
52
52
|
|
|
53
|
-
用户输入计数是入口前置职责,统计窗口从首次触发本 Skill 的用户消息开始,到当前 `run_id` 的 `run.terminal` 事件生成为止。只计用户在 Agent 对话框发送的消息,不计宿主/系统/网页授权、扫码、Skill stage、runner
|
|
53
|
+
用户输入计数是入口前置职责,统计窗口从首次触发本 Skill 的用户消息开始,到当前 `run_id` 的 `run.terminal` 事件生成为止。只计用户在 Agent 对话框发送的消息,不计宿主/系统/网页授权、扫码、Skill stage、runner、工具调用或自动轮询。文字、图片/文件、空白/短确认、材料补充、确认回复和中途插话都按一条用户消息计;同一条消息含文字和图片/文件仍只计一条。`USER_INPUT_ID` 是当前用户消息的本地幂等键:Agent 从首次触发本 Skill 起,给每条新用户消息分配对话内单调序号,同一消息重试复用同一值。`USER_INPUT_ID` 不得包含或派生自用户输入内容、文件名、路径、截图/OCR、业务材料、stage 名或工具调用序号;运行时只保存其本地 hash,不上报。
|
|
54
54
|
|
|
55
|
-
`start-run` 自动把触发当前 run 的首条用户输入计为 `before-flow
|
|
55
|
+
`start-run` 自动把触发当前 run 的首条用户输入计为 `before-flow`;中断/压缩后以新 `USER_INPUT_ID` 重入同一未终态 run 时,`start-run` 按当前 phase 幂等补计该消息。阻塞确认统一通过 `reply confirm --reply "$USER_REPLY" --input-id "$USER_INPUT_ID"`,该 runner 自动按当前 phase 幂等计数,禁止再为同一确认回复额外执行 `telemetry count-user-input`。其他用户输入(中途打断、补材料、只上传图片/文件、只问问题等)若不经 `reply confirm` 或 onboarding recovery,先于任何 Skill 动作或业务答复按当前 phase 执行一次 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" telemetry count-user-input --phase before-flow|after-flow --input-id "$USER_INPUT_ID"`;重复同一 `USER_INPUT_ID` 不加计。`*.user_confirmed` 由 flow loader 自动标记起点但不计数,after-flow 从其后的下一条用户消息开始,前置澄清和候选不计 after-flow,`full_process` 不因 Onboarding 重置。closeout 默认从本地 run state 派生计数;状态为空时用首条输入的最小合法计数,状态不可用且已核对时才显式传三项,runner 拒绝缺项、负数、`after > total`、已观测 flow start 但 `after >= total`。`terminal_reason` 为终态 runner 写入的固定低敏枚举,禁自由文本、错误详情和业务响应。不新增业务确认点或独立上报;入队后当前 `run.terminal` 文件做短超时前台 drain,失败保留 outbox 且不影响业务收口,网络类失败进入通用补传判断。脚本以当前 `run_id` 为 `PLATFORM_ID`;Agent 不手写环境前缀或改 MCP payload;维护诊断/outbox/上传/flush 见 `normal/telemetry.md`。
|
|
56
56
|
|
|
57
57
|
三种入口均只在无法从已有产品名称或业务描述可靠确定唯一产品时执行 flow 登记的 `product-clarify` runner;不得推定或手写澄清。产品明确但 MCC 尚无检索输入时使用 `mcc-clarify`;已有经营描述后先按 Onboarding flow 读取一次当前 `<SKILL_DIR>/references/onboarding/modules/mcc-reference.md` 做 Agent 语义候选,再统一调用 `mcc-resolve-plan` 由 runner 复核、合并和渲染;用户直接提供当前表内完整 MCC 编码时可跳过 Agent 泛化并由 runner 精确校验。不得拆回 `mcc-search + mcc-candidates`、先旧检索失败后临时猜测,或为方案展示再拼 renderer 输入。
|
|
58
58
|
|