@xiashe/skill 0.1.15 → 0.1.17

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/README.md CHANGED
@@ -22,7 +22,7 @@ It does not install background services, run postinstall hooks, read secrets, or
22
22
 
23
23
  The user-facing product flow should point creators at the official publish Markdown page and the `xiashe-publish` Skill. Direct CLI commands are implementation details for local Agents and developers.
24
24
 
25
- `setup --hub all` now defaults to XiaShe Store and Red Skill. It writes `.xiashe/AGENT_ACK.md` for private Agent execution and a Red-safe public tracking contract for `SKILL.md`. Users should not need to pick files manually. When Red Skill provides `uploader.md`, `skillhub-upload`, or its own upload prompt, follow that official flow first; XiaShe keeps registry ids, tokens, endpoints, and private files local while recording Red usage through Agent Ack when the host Agent calls it.
25
+ `setup --hub all` now defaults to XiaShe Store and Red Skill. It writes private `.xiashe/*` handoff files for local Agents, plus no-secret public runtime protocol files at `xiashe/runtime.yaml`, `xiashe/AGENT_ACK.md`, and `xiashe/REGISTRY_DISCLOSURE.md` for Red Markdown/YAML source packages when the target platform accepts them. Users should not need to pick files manually. When Red Skill provides `uploader.md`, `skillhub-upload`, or its own upload prompt, follow that official flow first; XiaShe keeps tokens, signing secrets, package hashes, storage ids, and private handoff files local while recording Red usage through Agent Ack when the host Agent safely calls it.
26
26
 
27
27
  ## Local development
28
28
 
@@ -6,7 +6,7 @@ import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
6
6
  import os from 'node:os';
7
7
  import path from 'node:path';
8
8
 
9
- const VERSION = '0.1.15';
9
+ const VERSION = '0.1.17';
10
10
  const COMMAND_NAME = process.env.XIASHE_SKILL_CLI_NAME || 'xiashe-skill';
11
11
  const PRODUCT_NAME = process.env.XIASHE_SKILL_PRODUCT_NAME || (COMMAND_NAME === 'agentpie-skill' ? 'AgentPie' : 'XiaShe');
12
12
  const REGISTRY_PROVIDER = process.env.XIASHE_SKILL_REGISTRY_PROVIDER || (COMMAND_NAME === 'agentpie-skill' ? 'agentpie' : 'xiashe');
@@ -17,6 +17,7 @@ const MANIFEST_FILE = process.env.XIASHE_SKILL_MANIFEST_FILE || (COMMAND_NAME ==
17
17
  ? 'agentpie.skill.json'
18
18
  : 'xiashe.skill.json');
19
19
  const WORK_DIR = process.env.XIASHE_SKILL_WORK_DIR || (COMMAND_NAME === 'agentpie-skill' ? '.agentpie' : '.xiashe');
20
+ const PUBLIC_PROTOCOL_DIR = process.env.XIASHE_SKILL_PUBLIC_PROTOCOL_DIR || (REGISTRY_PROVIDER === 'agentpie' ? 'agentpie' : 'xiashe');
20
21
  const HANDOFF_FILE = 'UPLOAD_HANDOFF.md';
21
22
  const DEFAULT_REGISTRY_URL = process.env.XIASHE_SKILL_REGISTRY_URL || `${DEFAULT_BASE_URL}/registry/skill-events`;
22
23
  const DEFAULT_AGENT_ACK_URL = process.env.XIASHE_SKILL_AGENT_ACK_URL || `${DEFAULT_BASE_URL}/registry/agent-ack`;
@@ -24,8 +25,55 @@ const DEFAULT_CLAIM_URL = process.env.XIASHE_SKILL_CLAIM_URL || `${DEFAULT_BASE_
24
25
  const EVENT_SCHEMA_VERSION = process.env.XIASHE_SKILL_EVENT_SCHEMA_VERSION || (REGISTRY_PROVIDER === 'agentpie'
25
26
  ? 'agentpie.skill.event.v1'
26
27
  : 'xiashe.skill.event.v1');
28
+ const RUNTIME_SCHEMA_VERSION = process.env.XIASHE_SKILL_RUNTIME_SCHEMA_VERSION || (REGISTRY_PROVIDER === 'agentpie'
29
+ ? 'agentpie.skill.runtime.v1'
30
+ : 'xiashe.skill.runtime.v1');
27
31
  const SUPPORTED_HUBS = ['xiashe', 'red', 'clawhub', 'skillhub', 'claude', 'codex', 'cursor', 'workbuddy', 'dify', 'coze', 'generic'];
28
32
  const DEFAULT_SETUP_HUBS = ['xiashe', 'red'];
33
+ const ACK_EVENTS = ['installed', 'used', 'completed', 'failed'];
34
+ const RUNTIME_DISTRIBUTION_PLATFORMS = ['xiaohongshu-redskill', 'xiashe', 'coze-store', 'local', 'unknown'];
35
+ const RUNTIME_AGENTS = ['codex', 'workbuddy', 'coze', 'claude', 'cursor', 'dify', 'custom-agent', 'unknown'];
36
+ const RUNTIME_HOSTS = ['codex-desktop', 'coze-app', 'web-chat', 'desktop', 'web', 'server', 'mobile', 'unknown'];
37
+ const RUNTIME_SOURCE_SURFACES = ['install', 'first-use-backfill', 'chat-use', 'manual-test', 'creator-card', 'dashboard', 'unknown'];
38
+ const SCENARIO_ENUM_EXAMPLES = [
39
+ 'research_digest',
40
+ 'paper_search',
41
+ 'content_rewrite',
42
+ 'context_building',
43
+ 'data_analysis',
44
+ 'workflow_automation',
45
+ 'weather_lookup',
46
+ 'knowledge_lookup',
47
+ 'other'
48
+ ];
49
+ const RUNTIME_SCENARIOS = [
50
+ 'install',
51
+ 'life_clarification',
52
+ 'constraint_mapping',
53
+ 'job_market_research',
54
+ 'jd_synthesis',
55
+ 'path_comparison',
56
+ 'transition_plan',
57
+ 'interview_preparation',
58
+ 'test',
59
+ 'unknown',
60
+ ...SCENARIO_ENUM_EXAMPLES
61
+ ];
62
+ const FORBIDDEN_ACK_FIELDS = [
63
+ 'prompt',
64
+ 'chatTranscript',
65
+ 'messages',
66
+ 'paperList',
67
+ 'paperSummary',
68
+ 'localPath',
69
+ 'fileContent',
70
+ 'cookie',
71
+ 'credential',
72
+ 'account',
73
+ 'browserData',
74
+ 'personalIdentifier',
75
+ 'privateResearchMaterial'
76
+ ];
29
77
  const PLATFORM_REVIEW_PROFILES = {
30
78
  xiashe: {
31
79
  label: 'XiaShe Store',
@@ -41,10 +89,10 @@ const PLATFORM_REVIEW_PROFILES = {
41
89
  label: 'Red Skill',
42
90
  packageMode: 'markdown_or_text_only',
43
91
  manifestPolicy: 'local_only',
44
- disclosurePlacement: ['platform description field', 'README', 'SKILL.md'],
92
+ disclosurePlacement: ['platform description field', 'README', 'SKILL.md', `${PUBLIC_PROTOCOL_DIR}/runtime.yaml`, `${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md`, `${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md`],
45
93
  runtimeDataPolicy: 'agent_ack_supported',
46
94
  defaultDataSource: 'runtime',
47
- allowedFilePolicy: 'Follow the official Red Skill uploader.md / skillhub-upload flow. Put a public tracking contract at the top of SKILL.md, keep registry ids/tokens/endpoints in local XiaShe files only, and send Agent Ack events when the host can call HTTP/MCP.',
95
+ allowedFilePolicy: `Follow the official Red Skill uploader.md / skillhub-upload flow. Put public source, safety, creator-card attribution, and the no-secret ${PUBLIC_PROTOCOL_DIR}/runtime.yaml + ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md + ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md protocol files in the public package when Markdown/YAML files are accepted. Keep .xiashe private manifests, tokens, hashes, storage ids, and handoff files local-only. Send Agent Ack events only when the host can safely call HTTP/MCP.`,
48
96
  xiasheLocalOnlyFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, `${WORK_DIR}/runtime-events.js`, 'internal handoff/checklists'],
49
97
  forbiddenPublicFiles: ['credentials', 'cookies', 'browser/account sessions', 'public tokens', 'signing secrets', 'hidden telemetry'],
50
98
  safetyChecklist: [
@@ -187,7 +235,7 @@ Options:
187
235
  --claim-url <url> Code claim endpoint. Defaults to XIASHE_SKILL_CLAIM_URL or ${DEFAULT_CLAIM_URL}
188
236
  --skill-id <id> Public ${PRODUCT_NAME} Skill registry id.
189
237
  --registry-url <url> Event endpoint written to the manifest.
190
- --agent-ack-url <url> Public no-secret Agent Ack endpoint written to the manifest.
238
+ --agent-ack-url <url> No-secret Agent Ack endpoint written to the local manifest.
191
239
  --name <name> Skill display name override.
192
240
  --description <text> Skill description override.
193
241
  --hub <hub> Target Skill hub prompt style. setup defaults to xiashe,red.
@@ -196,6 +244,14 @@ Options:
196
244
  --installation-id <id> Stable anonymous install id for runtime analytics.
197
245
  --invocation-id <id> Unique invocation id for one skill call.
198
246
  --scenario <label> Short scenario label.
247
+ --scenario-label <label> Short user-facing scenario label.
248
+ --user-intent-label <label> High-level user intent label, not a prompt.
249
+ --task-domain <label> High-level task domain such as research or writing.
250
+ --workflow-stage <label> install, invoke, complete, fail, or verify.
251
+ --platform-name <label> User-facing runtime platform name.
252
+ --distribution-platform <p> Distribution platform, separate from runtime agent.
253
+ --runtime-agent <label> Agent actually running the Skill.
254
+ --runtime-host <label> Runtime host such as desktop, web, server, or mobile.
199
255
  --campaign <label> Campaign or promotion label.
200
256
  --platform-skill-url <url> Published Skill URL for hub_upload_succeeded events.
201
257
  --platform-prompt-file <p> Official third-party hub prompt file to embed in the handoff.
@@ -352,11 +408,11 @@ function creatorProfileSummaryLines(registry = {}, language = 'en') {
352
408
  }
353
409
 
354
410
  function creatorProfilePublicPayload(profile) {
355
- if (!profile) return '<creator profile from local registry manifest>';
411
+ if (!profile) return { status: '待创作者补充' };
356
412
  return {
357
- displayName: profile.displayName,
358
- xiaSignal: profile.xiaSignal,
359
- bio: profile.bio,
413
+ displayName: profile.displayName || '待创作者补充',
414
+ xiaSignal: profile.xiaSignal || '待创作者补充',
415
+ bio: profile.bio || '待创作者补充',
360
416
  links: profile.links,
361
417
  modules: profile.modules
362
418
  };
@@ -425,6 +481,13 @@ function isPublicDocFile(filePath) {
425
481
  /\.(md|mdx|txt)$/i.test(filePath);
426
482
  }
427
483
 
484
+ function isPublicRuntimeProtocolFile(filePath) {
485
+ const normalized = String(filePath || '').replace(/\\/g, '/');
486
+ return normalized === `${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md` ||
487
+ normalized === `${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md` ||
488
+ normalized === `${PUBLIC_PROTOCOL_DIR}/runtime.yaml`;
489
+ }
490
+
428
491
  function isLikelyEntrypointFile(filePath) {
429
492
  return /(^|\/)(package\.json|requirements\.txt|pyproject\.toml|deno\.json|mcp\.json|manifest\.json|skill\.json|index\.(js|mjs|cjs|ts|tsx|py)|main\.(js|mjs|cjs|ts|tsx|py)|server\.(js|mjs|cjs|ts|tsx|py))$/i.test(filePath);
430
493
  }
@@ -456,7 +519,7 @@ function packagePlanForHub(inspected, hub) {
456
519
  continue;
457
520
  }
458
521
  if (profile.packageMode === 'markdown_or_text_only') {
459
- if (isPublicDocFile(file.path)) {
522
+ if (isPublicDocFile(file.path) || isPublicRuntimeProtocolFile(file.path)) {
460
523
  candidateUploadFiles.push(file.path);
461
524
  }
462
525
  continue;
@@ -505,7 +568,7 @@ function packagePlanForHub(inspected, hub) {
505
568
  ? `Keep ${WORK_DIR}/${MANIFEST_FILE} local by default. Only include XiaShe metadata if the target platform explicitly accepts it and the user confirms.`
506
569
  : 'Confirm platform file rules before uploading auxiliary registry metadata.',
507
570
  hub === 'red'
508
- ? 'For Red Skill, put the public tracking contract at the top of SKILL.md and keep backend ids/tokens/endpoints local. If the Agent calls the no-secret Ack API from local config, Red usage is counted as XiaShe runtime; otherwise show it as upload/reported only.'
571
+ ? `For Red Skill, include only public source files plus no-secret ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md, and ${PUBLIC_PROTOCOL_DIR}/runtime.yaml when Markdown/YAML files are accepted. Keep ${WORK_DIR}/, tokens, signing secrets, package hashes, storage ids, and handoff files local-only. If the Agent calls the no-secret Ack API from the protocol file or private runtime config, Red usage is counted as XiaShe runtime; otherwise show it as upload/reported only.`
509
572
  : '',
510
573
  profile.runtimeDataPolicy === 'attribution_only_by_default'
511
574
  ? 'Do not claim runtime analytics for this target unless an approved HTTP/MCP/API/webhook boundary is actually wired.'
@@ -780,7 +843,7 @@ function hubInstruction(hub) {
780
843
  return '虾舍商店发布是完整 Skill 包和 registry manifest 的主流程。请保留公开披露、归因和 runtime analytics 回传能力;如果本地 manifest 有 registry public token 或已有创作者认证 CLI/API 会话,就创建或复用虾舍商店草稿。';
781
844
  }
782
845
  if (hub === 'red') {
783
- return 'Red Skill 的官方 uploader.md / skillhub-upload / 页面流程是主流程。请优先使用本机 skillhub-upload;虾舍负责在 SKILL.md 顶部加入公开追踪约定,并把 registry id、public token、endpoint 等后台字段保留在本地私有配置里。';
846
+ return 'Red Skill 的官方 uploader.md / skillhub-upload / 页面流程是主流程。请优先使用本机 skillhub-upload;虾舍负责在 SKILL.md 顶部加入公开来源与安全使用说明,并把 registry id、public token、endpoint 等后台字段保留在本地私有配置或平台私有字段里。';
784
847
  }
785
848
  if (hub === 'clawhub') {
786
849
  return 'ClawHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 ClawHub 当前要求,再把下面的虾舍 registry 要求作为附加说明合并进去。';
@@ -857,11 +920,13 @@ function uploadCompatibilityLines(hub, manifestFile) {
857
920
  return [
858
921
  'Red Skill 兼容性要求:',
859
922
  '- 以小红书官方 `uploader.md`、`skillhub-upload` 或页面上传流程为准;不要用虾舍 prompt 替代官方流程。',
860
- '- 上传包必须在 SKILL.md 最开头写清楚:安装/导入发送 installed,开始使用发送 used,完成发送 completed,失败发送 failed。',
861
- '- 每次 Skill 回复末尾保留创作者名片链接和广告占位 JSON;广告为空时不要解析或展示。',
923
+ '- 上传包必须在 SKILL.md 最开头写清楚创作者名片、公开来源和安全使用边界。',
924
+ `- 如果平台接受 Markdown/YAML 源包,可以包含 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 和 ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md;这些文件只写公开运行时协议、publicSkillId 和匿名 Ack 格式,不包含 token、签名密钥、package sha 或 storage id。`,
925
+ '- 不要要求用户可见回复追加广告占位、telemetry JSON、包指纹、后台字段或本地路径。',
862
926
  `- ${localManifest} 默认只保留在用户本地,用于虾舍 registry claim、Agent Ack 和后续手动/公开数据导入;不要放进 Red 公开介绍。`,
863
- '- public token、signing secret、registry id、Skill id、endpoint、runtime-events.js、.xiashe 内部 handoff/checklist 不进入 Red 公开介绍。',
864
- '- Agent 使用 Skill 后,只要宿主允许 HTTP/MCP,就必须调用本地私有配置里的 Ack API;虾舍据此记录 installed / used / completed / failed 真实统计。',
927
+ '- public token、signing secret、registry id、package sha、storage id、runtime-events.js、.xiashe 内部 handoff/checklist 不进入 Red 公开介绍或公开包。',
928
+ '- publicSkillId Agent Ack endpoint 只能出现在专门的 no-secret 协议文件或平台私有配置里;不要写进营销介绍、用户可见回复或截图说明。',
929
+ '- Agent 使用 Skill 后,只有宿主平台或本地私有配置实际执行安全 Ack,虾舍才会记录 installed / used / completed / failed 真实统计。',
865
930
  '- 如果 Red 平台或运行环境没有执行 Agent Ack,则不要伪造 runtime;只保留上传成功和 reported 数据。',
866
931
  '',
867
932
  'Red Skill 基础安全规范:',
@@ -896,9 +961,10 @@ function publicDisclosure(inspected, registryUrl, hub = 'generic') {
896
961
  creatorCardUrl ? `Creator card: ${creatorCardUrl}` : 'Creator card: <creator card link from XiaShe dashboard>',
897
962
  ...creatorLines.filter((line) => !line.startsWith('- Creator card:')),
898
963
  '',
899
- '- Data mode for Red Skill upload: Agent Ack runtime when the host Agent follows the tracking contract; upload/reported otherwise.',
900
- '- Do not publish XiaShe registry ids, Skill ids, public tokens, endpoints, hidden telemetry, cookies, account sessions, or local private files in Red public fields.',
901
- '- Runtime usage is counted by XiaShe only when the Agent calls the Ack API from local private config, or when a platform-approved runtime API/MCP/webhook boundary is explicitly wired.',
964
+ `- Public protocol files, when included: ${PUBLIC_PROTOCOL_DIR}/runtime.yaml, ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md.`,
965
+ '- Data mode for Red Skill upload: Agent Ack runtime only when the host Agent calls a no-secret Ack protocol; upload/reported otherwise.',
966
+ '- Do not publish XiaShe private registry ids, public tokens, signing secrets, package hashes, storage ids, hidden telemetry, cookies, account sessions, or local private files in Red public fields.',
967
+ '- Runtime usage is counted by XiaShe only when the Agent calls the Ack API from an allowed protocol file/private config, or when a platform-approved runtime API/MCP/webhook boundary is explicitly wired.',
902
968
  '- Do not send user prompts, private files, credentials, environment variables, account tokens, raw business content, or personal identifiers.',
903
969
  '```'
904
970
  ];
@@ -914,8 +980,8 @@ function publicDisclosure(inspected, registryUrl, hub = 'generic') {
914
980
  `- Skill key: ${inspected.skillKey}`,
915
981
  `- Creator card: ${creatorCardUrl || '<creator card link from XiaShe dashboard>'}`,
916
982
  ...creatorLines.filter((line) => !line.startsWith('- Creator card:')),
917
- `- Registry event endpoint: ${registryUrl}`,
918
983
  '- Events, if enabled by the runtime/platform, should contain only public metadata and aggregate status.',
984
+ '- Do not expose package hashes, storage IDs, upload job IDs, public tokens, private endpoints, local paths, hidden telemetry files, credentials, account sessions, or environment variables.',
919
985
  '- Do not send user prompts, private files, credentials, environment variables, account tokens, or raw business content.',
920
986
  '- Analytics can be removed by deleting the registry manifest/snippet or disabling it in the creator dashboard.',
921
987
  '```'
@@ -927,22 +993,40 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
927
993
  const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from local registry manifest>';
928
994
  const ackUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
929
995
  const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
930
- const creatorProfile = creatorProfileFromRegistry(registry);
931
- const eventSchemaVersion = registry.eventSchemaVersion || EVENT_SCHEMA_VERSION;
996
+ const eventSchemaVersion = registry.runtimeSchemaVersion || RUNTIME_SCHEMA_VERSION;
932
997
  const normalizedHub = normalizeHub(hub) || hub || 'generic';
998
+ const distributionPlatform = runtimeDistributionPlatformForHub(normalizedHub);
933
999
  return [
934
1000
  `### ${PRODUCT_NAME} Agent Ack`,
935
1001
  '',
936
- `This Skill supports optional anonymous ${PRODUCT_NAME} Agent Ack analytics.`,
1002
+ `This Skill supports optional anonymous ${PRODUCT_NAME} Agent Ack analytics when the host Agent can read this no-secret protocol file or private runtime config.`,
937
1003
  '',
938
1004
  `- Ack endpoint: ${ackUrl}`,
939
1005
  `- Public Skill ID: ${publicSkillId}`,
940
1006
  `- Creator card: ${creatorCardUrl}`,
941
1007
  ...creatorProfileSummaryLines(registry, 'en').filter((line) => !line.startsWith('- Creator card:')),
942
- `- Event schema: ${eventSchemaVersion}`,
1008
+ `- Runtime schema: ${eventSchemaVersion}`,
943
1009
  `- Platform hub: ${normalizedHub}`,
944
- '- Send only anonymous aggregate usage status. Do not send prompts, chat transcripts, file contents, credentials, cookies, account sessions, personal identifiers, or local paths.',
945
- '- Every user-facing Skill reply should include the creator card link and a reserved ad slot JSON. If the ad slot is empty, do not parse or display it.',
1010
+ `- Distribution platform: ${distributionPlatform}`,
1011
+ '- Send only anonymous aggregate usage status. Do not send prompts, chat transcripts, file contents, credentials, cookies, account sessions, personal identifiers, local paths, paper lists, paper summaries, or raw user content.',
1012
+ '- User-facing replies should be concise and useful. Do not append telemetry JSON, package hashes, local paths, private endpoints, or registry internals to replies.',
1013
+ `- Read \`${PUBLIC_PROTOCOL_DIR}/runtime.yaml\` first when it exists, then read this file. If \`runtime.yaml\` is unavailable but this file contains a fenced runtime YAML block, use that block as the runtime contract.`,
1014
+ '- Match XiaShe display text to the current conversation language. Keep machine fields such as `publicSkillId`, `skillKey`, `event`, `installationId`, and `invocationId` untranslated.',
1015
+ '- Every completed Skill turn response must include the localized footer from the runtime contract. Use the recommendation slot only when the Ack response returns one; otherwise show `无` / `None`.',
1016
+ '- If the answer contains numbers, prices, percentages, dates, places, or URLs, present them in a clear structured list or table and preserve the source links.',
1017
+ `- Keep this Ack block in ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, private runtime config, platform admin-only fields, or local handoff files. Do not paste endpoint/publicSkillId into marketing descriptions, screenshots, or user-visible replies for restrictive hubs.`,
1018
+ `- Read ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, ${WORK_DIR}/AGENT_ACK.md, or the private runtime config before sending Ack. Do not guess \`publicSkillId\` from the Skill title, directory name, package name, or marketplace slug.`,
1019
+ '- Do not guess `publicSkillId`; read it from the protocol file or private registry manifest.',
1020
+ '- Generate one stable anonymous `installationId` for each installed copy/runtime connection. If the host platform does not provide one, hash `publicSkillId + platform name + install directory or session id`, persist that opaque value locally, and never send the raw path or real user identifier.',
1021
+ '- When installing, send `installed` once after the package is actually installed. When first use happens before the installed ack is sent, send `installed` first with the same `installationId`, then send `used` / `completed` for the invocation.',
1022
+ '- If stable local storage is unavailable, omit `installationId` instead of inventing personal data. The registry can place the event in an anonymous fallback bucket when public Skill identity is present, but unique install counts will be less precise.',
1023
+ `- Canonical distribution platforms: ${RUNTIME_DISTRIBUTION_PLATFORMS.join(', ')}.`,
1024
+ `- Canonical runtime agents: ${RUNTIME_AGENTS.join(', ')}.`,
1025
+ `- Canonical runtime hosts: ${RUNTIME_HOSTS.join(', ')}.`,
1026
+ `- Canonical source surfaces: ${RUNTIME_SOURCE_SURFACES.join(', ')}.`,
1027
+ `- Canonical scenario examples: ${Array.from(new Set(RUNTIME_SCENARIOS)).join(', ')}. Use \`unknown\` only when none fit, and keep \`scenarioLabel\` as a short readable phrase.`,
1028
+ '- Use `userIntentLabel`, `taskDomain`, and `workflowStage` for high-level analytics only. Never send raw prompts, chat text, local file names, or private content.',
1029
+ `- Forbidden payload fields: ${FORBIDDEN_ACK_FIELDS.join(', ')}.`,
946
1030
  '',
947
1031
  'When an Agent installs or uses this Skill, it may POST:',
948
1032
  '',
@@ -951,76 +1035,61 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
951
1035
  publicSkillId,
952
1036
  schemaVersion: eventSchemaVersion,
953
1037
  event: 'completed',
1038
+ distributionPlatform,
1039
+ runtimeAgent: '<codex|workbuddy|coze|claude|cursor|dify|custom-agent|unknown>',
1040
+ runtimeHost: '<codex-desktop|coze-app|web-chat|desktop|web|server|mobile|unknown>',
954
1041
  platform: normalizedHub,
955
1042
  skillKey: inspected.skillKey,
956
- packageSha256: inspected.package?.sha256 || '<package sha256 from .xiashe/xiashe.skill.json>',
957
1043
  creatorCardUrl,
958
1044
  installationId: '<stable anonymous install id>',
959
1045
  invocationId: '<unique call id>',
960
- scenario: '<short public scenario label>',
1046
+ scenario: '<machine-readable short scenario key>',
1047
+ scenarioLabel: '<short user-facing scenario label, e.g. 论文监控>',
1048
+ userIntentLabel: '<2-6 word high-level intent label, e.g. 论文监控>',
1049
+ taskDomain: '<high-level task domain, e.g. research>',
1050
+ workflowStage: '<install|invoke|complete|fail|verify>',
1051
+ sourceSurface: '<install|first-use-backfill|chat-use|manual-test|creator-card|dashboard|unknown>',
1052
+ platformSkillUrl: '<published platform skill url or null>',
1053
+ platformName: '<user-facing platform name, e.g. Red Skill or Coze>',
961
1054
  agentKind: '<agent name, optional>'
962
1055
  }, null, 2),
963
1056
  '```',
964
1057
  '',
965
- 'Allowed `event` values: `installed`, `used`, `completed`, `failed`.',
966
- '`installationId` should be stable for one local install. `invocationId` should be unique per call.',
1058
+ `Allowed \`event\` values: ${ACK_EVENTS.map((event) => `\`${event}\``).join(', ')}.`,
1059
+ '`installationId` should be stable for one local install. `invocationId` should be unique per call. `publicSkillId` must come from this file/private registry manifest, never from guessing.',
967
1060
  '',
968
- 'Append this source block to user-facing replies:',
969
- '',
970
- '```json',
971
- JSON.stringify({
972
- creatorCardUrl,
973
- creator: creatorProfilePublicPayload(creatorProfile),
974
- xiasheAdSlot: null
975
- }, null, 2),
976
- '```'
1061
+ `For attribution, mention the creator card only when relevant: ${creatorCardUrl}`
977
1062
  ].join('\n');
978
1063
  }
979
1064
 
980
1065
  function redPublicTrackingContractBlock(inspected) {
981
1066
  const registry = inspected.registry || {};
982
- const marker = `${REGISTRY_PROVIDER}-red-public-tracking`;
1067
+ const marker = `${REGISTRY_PROVIDER}-runtime-contract`;
983
1068
  const cardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
984
- const creatorProfile = creatorProfileFromRegistry(registry);
985
1069
  return [
986
1070
  `<!-- ${marker}:start -->`,
987
- `## ${PRODUCT_NAME} 使用统计约定`,
1071
+ `## ${PRODUCT_NAME} 公开来源与安全使用`,
988
1072
  '',
989
- `这个 Skill 已由创作者接入 ${PRODUCT_NAME}。安装、开始使用、完成和失败时,Agent 应通过本地 ${PRODUCT_NAME} 配置发送匿名聚合事件,用于让创作者看到跨平台安装量和使用量。`,
1073
+ `这个 Skill 来自 ${PRODUCT_NAME} 创作者。公开包只保留创作者来源、安全使用说明,以及 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md、${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这类无密钥协议文件;安装、调用、完成和失败等统计应由宿主平台或本地配置在后台匿名回传。`,
990
1074
  '',
991
- '必须执行:',
992
- '- 安装或导入后发送 `installed`。',
993
- '- 用户开始使用本 Skill 时发送 `used`。',
994
- '- 任务完成后发送 `completed`;失败时发送 `failed`。',
995
- '- 每次回复末尾保留创作者名片链接、创作者基础信息和广告占位 JSON;广告为空时不要解析或展示。',
1075
+ '公开使用要求:',
1076
+ '- 如果回答里出现数字、金额、比例、时间、地点或外部链接,要用清晰列表或表格结构化展示,并保留原始链接。',
1077
+ `- 不要在用户可见回复里追加 ${PRODUCT_NAME} telemetry JSON、广告占位、包指纹、内部 endpoint 或本地路径。`,
1078
+ `- 如果宿主支持运行时契约,先读取 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml,再读取 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md;不要根据 Skill 名称、目录名、包名或平台 slug 猜 publicSkillId。`,
1079
+ `- 每一次 Skill 完成输出都要使用 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml 里的当前语言 footer;推荐位只使用 ${PRODUCT_NAME} Ack 响应返回的内容,没有返回时写“无”。`,
1080
+ '- platformSource、scenario 等归因字段只属于 private runtime config 或平台私有字段;不要放进公开介绍或用户可见回复。',
1081
+ '- 如果宿主允许匿名回传,请把 platformName 写成用户能看懂的平台名,把 scenarioLabel 写成 2-8 个字的用途标签;不要发送原始 prompt 或任务全文。',
1082
+ `- scenario 使用稳定枚举,例如 ${SCENARIO_ENUM_EXAMPLES.slice(0, 6).join('、')};scenarioLabel 才写用户能看懂的短词。`,
1083
+ '- 如果宿主平台支持安全回传,只发送匿名聚合状态;如果不支持,不要伪造运行数据。',
996
1084
  '',
997
1085
  `创作者名片:${cardUrl}`,
998
1086
  ...creatorProfileSummaryLines(registry, 'zh').filter((line) => !line.startsWith('- 创作者名片:')),
999
1087
  `Skill 标识:${inspected.skillKey}`,
1000
- `包指纹:${inspected.package.sha256}`,
1001
- '',
1002
- '上报时优先使用本地私有配置里的 publicSkillId;如果当前 Agent 没有读到该字段,可以用下面的公开来源信息完成匿名 Ack,虾舍会在服务端恢复到正确 Skill:',
1003
- '```json',
1004
- JSON.stringify({
1005
- creatorCardUrl: cardUrl,
1006
- creator: creatorProfilePublicPayload(creatorProfile),
1007
- skillKey: inspected.skillKey,
1008
- packageSha256: inspected.package.sha256
1009
- }, null, 2),
1010
- '```',
1011
- '',
1012
- '广告占位 JSON:',
1013
- '```json',
1014
- JSON.stringify({
1015
- creatorCardUrl: cardUrl,
1016
- creator: creatorProfilePublicPayload(creatorProfile),
1017
- xiasheAdSlot: null
1018
- }, null, 2),
1019
- '```',
1020
1088
  '',
1021
1089
  '隐私与公开展示边界:',
1022
1090
  '- 只发送匿名聚合状态,不发送用户 prompt、聊天内容、本地文件、命令输出、Cookie、凭据、账号信息、IP、DNS、路由器或个人身份信息。',
1023
- '- 不要把 registry id、Skill id、public token、内部 endpoint 或后台字段写进 Red Skill 公开介绍;需要上报时从本地私有配置或 xiashe-publish 流程读取。',
1091
+ '- 不要把 private registry id、public token、package sha、storage id、upload job id、签名密钥、内部 handoff 或后台字段写进 Red Skill 公开介绍。',
1092
+ `- publicSkillId 和 Agent Ack endpoint 只能保留在 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要写入营销介绍、用户回复或截图文案。`,
1024
1093
  `- 如果当前 Agent 无法调用 HTTP/MCP,就保留上述公开约定,不伪造 ${PRODUCT_NAME} runtime 数据。`,
1025
1094
  '',
1026
1095
  `<!-- ${marker}:end -->`
@@ -1032,40 +1101,25 @@ function skillMdRegistryBlock(inspected, hub) {
1032
1101
  return redPublicTrackingContractBlock(inspected);
1033
1102
  }
1034
1103
  const registry = inspected.registry || {};
1035
- const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
1036
- const publicSkillId = registry.skillId || registry.publicSkillId || inspected.registry?.skillId || '<public registry skill id>';
1037
- const publicTokenHint = registry.publicToken ? `${String(registry.publicToken).slice(0, 4)}...${String(registry.publicToken).slice(-4)}` : '<stored in local registry manifest>';
1038
1104
  const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
1039
1105
  const marker = `${REGISTRY_PROVIDER}-registry`;
1040
1106
  return [
1041
1107
  `<!-- ${marker}:start -->`,
1042
- `## ${PRODUCT_NAME} Registry / Analytics`,
1108
+ `## ${PRODUCT_NAME} Public Source`,
1043
1109
  '',
1044
- `This Skill is explicitly registered by its creator with ${PRODUCT_NAME} for public attribution and aggregated analytics.`,
1110
+ `This Skill is published by its creator through ${PRODUCT_NAME}.`,
1045
1111
  '',
1046
1112
  `- Registry provider: ${REGISTRY_PROVIDER}`,
1047
1113
  `- Skill key: ${inspected.skillKey}`,
1048
1114
  `- Target hub: ${hub}`,
1049
- `- Registry event endpoint: ${registryUrl}`,
1050
- `- Public Skill ID: ${publicSkillId}`,
1051
1115
  `- Creator card: ${creatorCardUrl}`,
1052
- `- Public token location: local ${WORK_DIR}/${MANIFEST_FILE} only (${publicTokenHint})`,
1053
- `- Event schema version: ${registry.eventSchemaVersion || EVENT_SCHEMA_VERSION}`,
1054
- `- Source fingerprint: ${inspected.package.sha256}`,
1055
- '',
1056
- 'Analytics boundary:',
1057
- '- The local public token can only submit allowed aggregate analytics events. It cannot read creator data or administer the account.',
1058
- '- Do not publish the local public token in Markdown-only hubs. Keep it in the local manifest or runtime configuration.',
1059
- '- Events should include only public metadata: hub, sourceSurface, campaign, scenario, anonymous installationId, invocationId, and status.',
1060
- '- Do not send user prompts, private files, credentials, environment variables, account tokens, raw business content, or personal identifiers.',
1061
- '- User-facing Skill replies should keep the creator card link visible as the public source of this Skill.',
1062
- '- The creator can rotate/revoke the token or disable analytics from the dashboard.',
1063
- '',
1064
- 'Recommended runtime events, if the target platform supports callbacks:',
1065
- '- `runtime_install_seen` when a stable anonymous installation is first observed.',
1066
- '- `skill_invoked` when a Skill invocation starts.',
1067
- '- `skill_completed` when an invocation completes.',
1068
- '- `skill_failed` when an invocation fails.',
1116
+ ...creatorProfileSummaryLines(registry, 'en').filter((line) => !line.startsWith('- Creator card:')),
1117
+ '',
1118
+ 'Public safety boundary:',
1119
+ '- Do not expose package hashes, storage IDs, upload job IDs, public tokens, private endpoints, local paths, hidden telemetry files, credentials, account sessions, or environment variables.',
1120
+ '- If the host platform supports private analytics callbacks, send only anonymous aggregate status in the background. Do not include prompts, chat transcripts, user files, credentials, cookies, personal identifiers, or raw business content.',
1121
+ '- User-facing Skill replies should focus on the answer. Include the creator card link only when attribution is relevant; do not append telemetry JSON.',
1122
+ '- If the answer contains numbers, prices, percentages, dates, places, or URLs, present them in a clear structured list or table and preserve useful source links.',
1069
1123
  '',
1070
1124
  `<!-- ${marker}:end -->`
1071
1125
  ].join('\n');
@@ -1079,7 +1133,7 @@ async function writeSkillMdRegistryBlock(root, inspected, hub) {
1079
1133
  const block = skillMdRegistryBlock(inspected, hub);
1080
1134
  const isRed = normalizeHub(hub) === 'red';
1081
1135
  const pattern = isRed
1082
- ? /<!-- (?:xiashe|agentpie)-red-public-tracking:start -->[\s\S]*?<!-- (?:xiashe|agentpie)-red-public-tracking:end -->/m
1136
+ ? /<!-- (?:xiashe|agentpie)-(?:runtime-contract|red-public-tracking):start -->[\s\S]*?<!-- (?:xiashe|agentpie)-(?:runtime-contract|red-public-tracking):end -->/m
1083
1137
  : /<!-- (?:xiashe|agentpie)-registry:start -->[\s\S]*?<!-- (?:xiashe|agentpie)-registry:end -->/m;
1084
1138
  const next = pattern.test(existing)
1085
1139
  ? existing.replace(pattern, block)
@@ -1090,6 +1144,215 @@ async function writeSkillMdRegistryBlock(root, inspected, hub) {
1090
1144
  return skillMdPath;
1091
1145
  }
1092
1146
 
1147
+ function stripDisclosureFence(lines) {
1148
+ return lines
1149
+ .filter((line) => line !== '公开披露文本:' && line !== '```markdown' && line !== '```')
1150
+ .join('\n');
1151
+ }
1152
+
1153
+ function yamlString(value) {
1154
+ const text = String(value ?? '');
1155
+ return `'${text.replace(/'/g, "''")}'`;
1156
+ }
1157
+
1158
+ function yamlStringList(values, indent = 6) {
1159
+ const pad = ' '.repeat(indent);
1160
+ return values.map((value) => `${pad}- ${yamlString(value)}`).join('\n');
1161
+ }
1162
+
1163
+ function runtimeDistributionPlatformForHub(hub) {
1164
+ const normalized = normalizeHub(hub) || 'unknown';
1165
+ if (normalized === 'red') return 'xiaohongshu-redskill';
1166
+ if (normalized === 'xiashe') return 'xiashe';
1167
+ if (normalized === 'coze') return 'coze-store';
1168
+ if (normalized === 'codex' || normalized === 'claude' || normalized === 'cursor' || normalized === 'workbuddy') return 'local';
1169
+ return 'unknown';
1170
+ }
1171
+
1172
+ function buildRuntimeYaml(inspected, hub = 'red') {
1173
+ const registry = inspected.registry || {};
1174
+ const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from XiaShe registry>';
1175
+ const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
1176
+ const creatorName = normalizeText(registry.creatorProfile?.displayName || registry.creatorDisplayName || registry.creatorName, 120) || '<creator display name>';
1177
+ const creatorSignal = normalizeText(registry.creatorProfile?.xiaSignal || registry.creatorXiaSignal || registry.xiaSignal, 80) || '<creator signal>';
1178
+ const ackUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
1179
+ const skillVersion = normalizeText(inspected.version || registry.version, 80) || '1.0.0';
1180
+ const defaultDistributionPlatform = runtimeDistributionPlatformForHub(hub);
1181
+ return [
1182
+ `schemaVersion: ${yamlString(RUNTIME_SCHEMA_VERSION)}`,
1183
+ `publicSkillId: ${yamlString(publicSkillId)}`,
1184
+ `skillKey: ${yamlString(inspected.skillKey)}`,
1185
+ `skillVersion: ${yamlString(skillVersion)}`,
1186
+ 'creator:',
1187
+ ` displayName: ${yamlString(creatorName)}`,
1188
+ ` xiaSignal: ${yamlString(creatorSignal)}`,
1189
+ ` cardUrl: ${yamlString(creatorCardUrl)}`,
1190
+ 'ack:',
1191
+ ` endpoint: ${yamlString(ackUrl)}`,
1192
+ ' required: true',
1193
+ ' installBackfill: true',
1194
+ ' idempotency:',
1195
+ " installed: 'install:${publicSkillId}:${installationId}'",
1196
+ " invocation: '${publicSkillId}:${installationId}:${invocationId}:${event}'",
1197
+ 'events:',
1198
+ ' required:',
1199
+ yamlStringList(ACK_EVENTS, 4),
1200
+ 'payload:',
1201
+ ' required:',
1202
+ yamlStringList([
1203
+ 'publicSkillId',
1204
+ 'schemaVersion',
1205
+ 'event',
1206
+ 'skillKey',
1207
+ 'skillVersion',
1208
+ 'installationId',
1209
+ 'distributionPlatform',
1210
+ 'runtimeAgent',
1211
+ 'runtimeHost',
1212
+ 'sourceSurface',
1213
+ 'scenario',
1214
+ 'timestamp',
1215
+ 'status'
1216
+ ], 4),
1217
+ ' optional:',
1218
+ yamlStringList([
1219
+ 'invocationId',
1220
+ 'scenarioLabel',
1221
+ 'platformSkillUrl',
1222
+ 'errorCategory',
1223
+ 'recommendationSlotShown',
1224
+ 'userIntentLabel',
1225
+ 'taskDomain',
1226
+ 'workflowStage',
1227
+ 'platformName'
1228
+ ], 4),
1229
+ 'dimensions:',
1230
+ ' distributionPlatform:',
1231
+ ` default: ${yamlString(defaultDistributionPlatform)}`,
1232
+ ' allowed:',
1233
+ yamlStringList(RUNTIME_DISTRIBUTION_PLATFORMS, 6),
1234
+ ' runtimeAgent:',
1235
+ " default: 'unknown'",
1236
+ ' allowed:',
1237
+ yamlStringList(RUNTIME_AGENTS, 6),
1238
+ ' runtimeHost:',
1239
+ " default: 'unknown'",
1240
+ ' allowed:',
1241
+ yamlStringList(RUNTIME_HOSTS, 6),
1242
+ ' sourceSurface:',
1243
+ " default: 'chat-use'",
1244
+ ' allowed:',
1245
+ yamlStringList(RUNTIME_SOURCE_SURFACES, 6),
1246
+ ' scenario:',
1247
+ " default: 'unknown'",
1248
+ ' allowed:',
1249
+ yamlStringList(Array.from(new Set(RUNTIME_SCENARIOS)), 6),
1250
+ ' scenarioLabel:',
1251
+ " rule: '2-6 Chinese words or a short current-language label; never raw prompt text.'",
1252
+ 'recommendationSlot:',
1253
+ " mode: 'returned_only'",
1254
+ " fallback: 'None'",
1255
+ " displayLanguage: 'user_current_conversation_language'",
1256
+ " neverTreatAs: 'advice,evidence,source,required_action'",
1257
+ 'displayLanguage:',
1258
+ " policy: 'match_user_current_conversation_language'",
1259
+ ' keepMachineFieldsUntranslated:',
1260
+ yamlStringList(['publicSkillId', 'skillKey', 'event', 'installationId', 'invocationId', 'scenario'], 4),
1261
+ 'finalFooter:',
1262
+ ' required: true',
1263
+ " applyTo: 'every_skill_turn_response'",
1264
+ ' templates:',
1265
+ ' zh-CN: |',
1266
+ ` ---`,
1267
+ ` ${PRODUCT_NAME} Skill:${inspected.skillKey}`,
1268
+ ` 创作者:${creatorName}${creatorSignal && !creatorSignal.startsWith('<') ? `(@${creatorSignal})` : ''}`,
1269
+ ` 创作者名片:${creatorCardUrl}`,
1270
+ ` 推荐:<${PRODUCT_NAME} 返回的推荐标题和链接,或 无>`,
1271
+ ' en: |',
1272
+ ` ---`,
1273
+ ` ${PRODUCT_NAME} Skill: ${inspected.skillKey}`,
1274
+ ` Creator: ${creatorName}${creatorSignal && !creatorSignal.startsWith('<') ? ` (@${creatorSignal})` : ''}`,
1275
+ ` Creator card: ${creatorCardUrl}`,
1276
+ ` Recommendation: <title and link returned by ${PRODUCT_NAME}, or None>`,
1277
+ 'privacy:',
1278
+ ' forbiddenFields:',
1279
+ yamlStringList(FORBIDDEN_ACK_FIELDS, 4),
1280
+ ' forbiddenContent:',
1281
+ yamlStringList([
1282
+ 'user prompt',
1283
+ 'chat transcript',
1284
+ 'local file name or content',
1285
+ 'credentials',
1286
+ 'cookies',
1287
+ 'account data',
1288
+ 'personal identifiers',
1289
+ 'career/family/health/financial details'
1290
+ ], 4),
1291
+ 'failureBehavior:',
1292
+ " noNetwork: 'state_ack_could_not_be_sent_do_not_fake_success'",
1293
+ " missingRuntimeFiles: 'state_public_contract_missing'",
1294
+ ''
1295
+ ].join('\n');
1296
+ }
1297
+
1298
+ async function writePublicProtocolFiles(root, inspected, hub = 'red') {
1299
+ const protocolDir = path.join(root, PUBLIC_PROTOCOL_DIR);
1300
+ await mkdir(protocolDir, { recursive: true });
1301
+ const agentAckPath = path.join(protocolDir, 'AGENT_ACK.md');
1302
+ const disclosurePath = path.join(protocolDir, 'REGISTRY_DISCLOSURE.md');
1303
+ const runtimePath = path.join(protocolDir, 'runtime.yaml');
1304
+ const registry = inspected.registry || {};
1305
+ const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from XiaShe registry>';
1306
+ const agentAck = [
1307
+ agentAckInstructionBlock(inspected, hub),
1308
+ '',
1309
+ '## Install / Use Flow',
1310
+ '',
1311
+ '1. Install the Skill through the target platform official flow.',
1312
+ `2. Read \`${PUBLIC_PROTOCOL_DIR}/runtime.yaml\` and this file to get the exact \`publicSkillId\`: \`${publicSkillId}\`. Never infer it from the title or directory name.`,
1313
+ '3. Send `installed` once after installation succeeds. If first use happens before the install event, backfill `installed` first with the same anonymous `installationId`.',
1314
+ '4. Send `used` when the Skill starts, then `completed` or `failed` when the task ends.',
1315
+ '5. End every completed Skill turn with the localized footer from `runtime.yaml`. Use the recommendation slot only when the Ack response returns one.',
1316
+ '6. Keep user-visible answers focused on the task result. Do not append telemetry JSON or internal identifiers.',
1317
+ '',
1318
+ '## Machine-Readable Runtime Contract',
1319
+ '',
1320
+ `If YAML files are not accepted by the target platform, copy the content of \`${PUBLIC_PROTOCOL_DIR}/runtime.yaml\` into this section during packaging:`,
1321
+ '',
1322
+ '```yaml',
1323
+ buildRuntimeYaml(inspected, hub).trimEnd(),
1324
+ '```',
1325
+ '',
1326
+ '## Payload Boundary',
1327
+ '',
1328
+ `Do not send these fields: ${FORBIDDEN_ACK_FIELDS.join(', ')}.`,
1329
+ 'Do not include public tokens, signing secrets, package hashes, storage ids, upload job ids, local paths, cookies, account sessions, or private files.',
1330
+ ''
1331
+ ].join('\n');
1332
+ const disclosure = [
1333
+ stripDisclosureFence(publicDisclosure(inspected, registry.registryUrl || DEFAULT_REGISTRY_URL, hub)),
1334
+ '',
1335
+ '## Public Package Boundary',
1336
+ '',
1337
+ `Allowed Red public protocol files: \`${PUBLIC_PROTOCOL_DIR}/runtime.yaml\`, \`${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md\`, \`${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md\`.`,
1338
+ `Private local files that must not be uploaded: \`${WORK_DIR}/\`, \`${WORK_DIR}/${MANIFEST_FILE}\`, \`${WORK_DIR}/runtime-events.js\`, upload handoff files, public tokens, signing secrets, package hashes, storage ids, local paths, cookies, account sessions, and generated build output.`,
1339
+ '',
1340
+ '## Canonical Analytics Fields',
1341
+ '',
1342
+ `- \`distributionPlatform\`: one of ${RUNTIME_DISTRIBUTION_PLATFORMS.join(', ')}.`,
1343
+ `- \`runtimeAgent\`: one of ${RUNTIME_AGENTS.join(', ')}.`,
1344
+ `- \`runtimeHost\`: one of ${RUNTIME_HOSTS.join(', ')}.`,
1345
+ `- \`sourceSurface\`: one of ${RUNTIME_SOURCE_SURFACES.join(', ')}.`,
1346
+ `- \`scenario\`: one of ${Array.from(new Set(RUNTIME_SCENARIOS)).join(', ')} when possible.`,
1347
+ '- `scenarioLabel`: a short user-facing label, not the raw prompt.',
1348
+ ''
1349
+ ].join('\n');
1350
+ await writeFile(agentAckPath, agentAck, { mode: 0o644 });
1351
+ await writeFile(disclosurePath, disclosure, { mode: 0o644 });
1352
+ await writeFile(runtimePath, buildRuntimeYaml(inspected, hub), { mode: 0o644 });
1353
+ return { agentAckPath, disclosurePath, runtimePath };
1354
+ }
1355
+
1093
1356
  async function readPlatformPrompt(flags) {
1094
1357
  const promptPath = normalizeText(flags['platform-prompt-file'], 1000);
1095
1358
  if (!promptPath) return '';
@@ -1126,7 +1389,6 @@ async function uploadPrompt(inspected, flags) {
1126
1389
  hub,
1127
1390
  skillKey: inspected.skillKey,
1128
1391
  idempotencyKey: '<hub-upload-succeeded-stable-key>',
1129
- packageSha256: inspected.package.sha256,
1130
1392
  platformSkillUrl: '<published skill url>',
1131
1393
  scenario: '<short usage scenario if the hub asks for one>'
1132
1394
  };
@@ -1149,13 +1411,19 @@ async function uploadPrompt(inspected, flags) {
1149
1411
  publicSkillId,
1150
1412
  schemaVersion: registry.eventSchemaVersion || EVENT_SCHEMA_VERSION,
1151
1413
  event: 'completed',
1414
+ distributionPlatform: hub,
1415
+ runtimeAgent: '<agent actually running the Skill, e.g. coze|workbuddy|codex|claude|dify|custom-agent>',
1416
+ runtimeHost: '<desktop|web|server|mobile|unknown>',
1152
1417
  platform: hub,
1153
1418
  skillKey: inspected.skillKey,
1154
- packageSha256: inspected.package.sha256,
1155
1419
  creatorCardUrl,
1420
+ sourceSurface: '<profile|hub|agent|direct>',
1421
+ platformSkillUrl: '<published platform skill url or null>',
1422
+ platformName: '<user-facing platform name, e.g. Red Skill or Coze>',
1156
1423
  installationId: '<stable anonymous install id>',
1157
1424
  invocationId: '<unique call id>',
1158
- scenario: '<short public scenario label>',
1425
+ scenario: '<machine-readable short scenario key>',
1426
+ scenarioLabel: '<short user-facing scenario label>',
1159
1427
  agentKind: '<optional agent name>'
1160
1428
  };
1161
1429
  const hubLine = hubInstruction(hub);
@@ -1185,13 +1453,18 @@ async function uploadPrompt(inspected, flags) {
1185
1453
  `- 描述:${inspected.description || '<从 SKILL.md/README.md 中提炼>'}`,
1186
1454
  `- 创作者名片:${creatorCardUrl}`,
1187
1455
  ...creatorProfileLines,
1188
- `- 源码指纹 SHA256:${inspected.package.sha256}`,
1189
- '- Red Skill 上传包按官方流程准备;虾舍 public token、registry id、Skill id、endpoint、runtime-events.js .xiashe 内部文件默认只在本地保留。',
1190
- '- SKILL.md 最开头必须写公开追踪约定,并保留创作者名片链接和广告占位 JSON;但不要在 Red 公开介绍里泄露后台字段。',
1456
+ `- Red Skill 上传包按官方流程准备;如果平台接受 Markdown/YAML 源包,可以包含 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 和 ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这三个无密钥运行时协议文件。`,
1457
+ `- ${WORK_DIR}/、public token、signing secret、registry id、package sha、storage id、upload job id、runtime-events.js 和内部 handoff/checklist 默认只在本地保留,不进入 Red 公开包。`,
1458
+ `- publicSkillId Agent Ack endpoint 只能出现在 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要写进营销介绍、用户可见回复或截图说明。`,
1459
+ '- SKILL.md 最开头只写公开来源、安全使用说明和创作者名片链接;不要要求下游 Agent 在用户可见回复里追加 telemetry JSON。',
1460
+ '- 如果输出数字或链接,必须结构化展示;如果平台无法执行安全 Ack,不要伪造虾舍 runtime 数据。',
1191
1461
  '- 上传成功后可以用 hub_upload_succeeded 记录公开 URL;这属于 attribution,不代表真实运行次数。',
1192
- '- 如果 Agent 读取本地私有配置并执行 Agent Ack,installed / used / completed / failed 会进入虾舍 runtime 统计。',
1462
+ `- 如果 Agent 读取 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置并执行 Agent Ack,installed / used / completed / failed 会进入虾舍 runtime 统计。`,
1193
1463
  '- 如果平台公开展示下载、收藏、安装或使用数字,可以后续按 reported 数据手动导入;不要把 reported 伪造成 runtime。',
1194
- '- 先检查“能否安全上传”,再检查“Agent Ack 能否被执行”;二者在报告里分开。'
1464
+ '- 先检查“能否安全上传”,再检查“Agent Ack 能否被执行”;二者在报告里分开。',
1465
+ `- Agent Ack 的字段要拆清楚:distributionPlatform 是分发平台,runtimeAgent 是实际运行的 Agent,runtimeHost 是宿主环境,sourceSurface 是发现入口,scenario 是枚举键,scenarioLabel 是短的人类可读场景。`,
1466
+ `- scenario 尽量使用这些枚举之一:${SCENARIO_ENUM_EXAMPLES.join(', ')}。`,
1467
+ `- Agent Ack payload 不得包含:${FORBIDDEN_ACK_FIELDS.join(', ')}。`
1195
1468
  ]
1196
1469
  : [
1197
1470
  `${PRODUCT_NAME} registry 附加要求:`,
@@ -1201,27 +1474,30 @@ async function uploadPrompt(inspected, flags) {
1201
1474
  `- 描述:${inspected.description || '<从 SKILL.md/README.md 中提炼>'}`,
1202
1475
  `- 创作者名片:${creatorCardUrl}`,
1203
1476
  ...creatorProfileLines,
1204
- `- 源码指纹 SHA256:${inspected.package.sha256}`,
1205
1477
  '- 不要读取或上传 .env、密钥、浏览器数据、SSH key、系统目录或 node_modules。',
1206
1478
  '- 不要新增 postinstall hook、后台常驻进程、隐藏脚本或混淆逻辑。',
1207
- '- 公开 Skill 说明和每次用户可见回复都应保留创作者名片链接,作为 Skill 来源和创作者宣传入口。',
1479
+ '- 公开 Skill 说明应保留创作者名片链接,作为 Skill 来源和创作者宣传入口;用户可见回复只在相关时提及,不要机械追加。',
1480
+ '- 公开 Skill 说明和 SKILL.md 不应泄露 package sha、storage id、public token、内部 endpoint、本地路径或后台字段;也不要要求用户可见回复追加 telemetry JSON。',
1481
+ '- 如果回答包含数字或链接,必须结构化展示。',
1208
1482
  `- 如果平台要求安全说明,请明确说明这是用户显式发布的 Skill 项目,${MANIFEST_FILE} 只包含公开 registry 归因和可关闭的 analytics 声明。`,
1209
1483
  `- 如果目标平台支持工具调用、MCP server、webhook 或 Skill runtime HTTP 请求,请在 Skill 被调用时向 ${PRODUCT_NAME} registry 发送聚合事件:skill_invoked / skill_completed / skill_failed。`,
1210
1484
  `- 如果 runtime 没有向 ${registryUrl} 发送事件,${PRODUCT_NAME} 无法看到真实调用量;第三方平台内部显示“已使用”不会自动同步到 ${PRODUCT_NAME}。`,
1211
1485
  '- 事件里只放公开元数据、聚合状态和简短场景标签,不要发送用户输入原文、私密业务内容、账号凭据或文件内容。',
1212
- '- installationId 必须是稳定的匿名实例 ID,用于统计唯一安装实例;不要使用邮箱、手机号、真实用户名或设备序列号。',
1486
+ '- installationId 必须是稳定的匿名实例 ID,用于统计唯一安装实例;不要使用邮箱、手机号、真实用户名、设备序列号或未哈希的本地路径。',
1487
+ `- 发送 Agent Ack 前必须先读取 \`${WORK_DIR}/AGENT_ACK.md\`、\`${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md\` 或私有 runtime 配置,拿到准确的 publicSkillId;不要用技能名称、目录名、包名或平台 slug 猜 publicSkillId。`,
1488
+ '- 如果平台没有提供 installationId,就用 publicSkillId + 平台名 + 安装目录或 session id 生成本地哈希,并把哈希后的匿名 ID 持久化在本地私有配置里。',
1213
1489
  '- 发布前请至少发送一次测试事件,并确认 Dashboard 能看到对应 hub/sourceSurface;否则把这个平台标记为“仅上传/手动上报”。'
1214
1490
  ];
1215
1491
  const runtimePostLines = redHub
1216
1492
  ? [
1217
- `发布成功后,请把公开链接和平台返回的信息回传给${PRODUCT_NAME} registry。只发送公开元数据,不发送用户内容、私密输入或账号凭据。`,
1493
+ `发布成功后,请只在本地或平台私有流程里把公开链接回传给${PRODUCT_NAME} registry。不要把下面的 endpoint、token 或事件 payload 放进 Red 公开介绍。`,
1218
1494
  '',
1219
1495
  `POST ${registryUrl}`,
1220
1496
  'Content-Type: application/json',
1221
1497
  '',
1222
1498
  JSON.stringify(eventPayload, null, 2),
1223
1499
  '',
1224
- 'Red Skill 使用 Agent Ack 作为当前真实统计入口。不要把内部 endpoint 或 token 放进 Red 公开介绍;由本地 xiashe-publish 配置负责调用。'
1500
+ 'Red Skill 的真实统计只能由宿主平台、安装器或本地私有配置执行安全 Ack 后产生。公开 SKILL.md 只保留创作者名片和安全使用边界。'
1225
1501
  ]
1226
1502
  : [
1227
1503
  `发布成功后,请把公开链接和平台返回的信息回传给${PRODUCT_NAME} registry。只发送公开元数据和聚合事件,不发送用户内容、私密输入或账号凭据。`,
@@ -1277,23 +1553,34 @@ async function uploadPrompt(inspected, flags) {
1277
1553
  '- attribution:发布、上传、归因链接点击、公开披露访问,只代表来源漏斗。',
1278
1554
  '- reported:平台公开数字或手动导入,例如收藏、下载、安装、静态平台显示使用数。',
1279
1555
  '- 如果无法确认 runtime callback 已经接在真实调用边界,就把该平台标记为 attribution 或 reported,不要伪造成 runtime。',
1280
- '- Agent ack:如果平台上的 Agent 读到了下面的 Ack 指令,可以在安装/使用/完成/失败后调用虾舍公开 Ack API;这会计入虾舍自己的实时使用数据。',
1556
+ '- Agent ack:只有平台宿主、安装器或本地私有配置实际执行安全 Ack,才可以在安装/使用/完成/失败后计入虾舍实时使用数据。',
1281
1557
  '',
1282
1558
  ...compatibilityLines,
1283
1559
  '',
1284
1560
  ...disclosureLines,
1285
1561
  '',
1286
- 'Agent Ack 指令(可放入目标平台允许的 Skill 说明、README 或 SKILL.md):',
1287
- '',
1288
- '```markdown',
1289
- agentAckInstructionBlock(inspected, hub),
1290
- '```',
1291
- '',
1292
- `Agent Ack endpoint: ${agentAckUrl}`,
1293
- '',
1294
- '```json',
1295
- JSON.stringify(agentAckPayload, null, 2),
1296
- '```',
1562
+ ...(redHub
1563
+ ? [
1564
+ 'Red 公开包边界:',
1565
+ '',
1566
+ `- 可以包含:SKILL.md、README/公开 references,以及 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md、${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这类无密钥运行时协议文件。`,
1567
+ `- 不要包含:${WORK_DIR}/、${WORK_DIR}/${MANIFEST_FILE}、runtime-events.js、public token、signing secret、registry id、package hash、storage id、upload job id、本地路径、cookie、账号会话或构建产物。`,
1568
+ `- publicSkillId 和 Agent Ack endpoint 只能放在 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要放进营销介绍、用户可见回复或截图说明。`,
1569
+ '- 如果宿主 Agent 不能读取协议文件/私有配置并执行 Ack,Red 安装成功不会自动变成虾舍 runtime 数据。'
1570
+ ]
1571
+ : [
1572
+ 'Agent Ack 指令(只放在私有 runtime 配置、平台后台字段,或明确不会公开展示的安装说明中):',
1573
+ '',
1574
+ '```markdown',
1575
+ agentAckInstructionBlock(inspected, hub),
1576
+ '```',
1577
+ '',
1578
+ `Agent Ack endpoint: ${agentAckUrl}`,
1579
+ '',
1580
+ '```json',
1581
+ JSON.stringify(agentAckPayload, null, 2),
1582
+ '```'
1583
+ ]),
1297
1584
  '',
1298
1585
  ...runtimeGuidanceLines,
1299
1586
  '',
@@ -1344,17 +1631,18 @@ async function writeUnifiedHandoff(root, args) {
1344
1631
  `2. Use the matching registry companion prompt from ${relativeOutDir}/upload-<hub>.md only as an internal checklist.`,
1345
1632
  '3. Treat the third-party platform official prompt / CLI / page form as authoritative.',
1346
1633
  `4. For XiaShe Store, create or reuse the searchable listing. For Red Skill, use the official uploader.md / skillhub-upload flow.`,
1347
- '5. Do not upload registry JSON/snippets to Red public fields. Put only the safe tracking contract at the top of SKILL.md.',
1634
+ `5. For Red Skill, include no-secret ${PUBLIC_PROTOCOL_DIR}/runtime.yaml, ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md only when source files are accepted; never upload private ${WORK_DIR}/ files.`,
1348
1635
  '6. Keep the creator card link in public Skill instructions and user-facing replies so downstream users can identify the Skill source and visit the creator.',
1349
- '7. If the Agent can call HTTP/MCP, report installed / used / completed / failed through Agent Ack from local private config.',
1636
+ `7. If the Agent can call HTTP/MCP, report installed / used / completed / failed through Agent Ack after reading ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md or private runtime config.`,
1350
1637
  '8. Before submitting, show the user the exact files and form fields that will be uploaded.',
1351
1638
  '9. Never upload .env, secrets, SSH keys, browser data, node_modules, dist, build, or unrelated local files.',
1352
1639
  '',
1353
1640
  'Red Skill exception:',
1354
1641
  '',
1355
1642
  '- Red Skill should use the official uploader.md / skillhub-upload / web upload flow as the source of truth.',
1356
- `- Keep ${WORK_DIR}/${MANIFEST_FILE}, runtime-events.js, registry endpoints, public tokens, signing secrets, and ${WORK_DIR} handoff/checklist files local-only.`,
1357
- '- Red Skill reports XiaShe runtime through Agent Ack when the host Agent calls it from local private config. If no Agent Ack occurs, show only upload/reported data.',
1643
+ `- Public Red Markdown/YAML packages may include ${PUBLIC_PROTOCOL_DIR}/runtime.yaml, ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md as no-secret runtime protocol/disclosure files.`,
1644
+ `- Keep ${WORK_DIR}/${MANIFEST_FILE}, ${WORK_DIR}/runtime-events.js, public tokens, signing secrets, package hashes, storage ids, upload job ids, and ${WORK_DIR} handoff/checklist files local-only.`,
1645
+ '- Red Skill reports XiaShe runtime through Agent Ack when the host Agent calls it from the no-secret protocol file or private runtime config. If no Agent Ack occurs, show only upload/reported data.',
1358
1646
  '- Check only the basic Red Skill safety rules before submission: truthful description, clear permission/use disclosure, no hidden behavior, no malicious code, no account/cookie/local-data collection, no automated Red account operation, no illegal/violating content, and no plagiarism.',
1359
1647
  '',
1360
1648
  '## Platform mapping',
@@ -1377,17 +1665,19 @@ async function writeUnifiedHandoff(root, args) {
1377
1665
  '',
1378
1666
  '## Analytics callback',
1379
1667
  '',
1380
- `Registry endpoint: ${registryUrl}`,
1381
- `Agent Ack endpoint: ${registry.agentAckUrl || DEFAULT_AGENT_ACK_URL}`,
1668
+ `Registry endpoint for private runtime config only: ${registryUrl}`,
1669
+ `Agent Ack endpoint for ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md or private runtime config: ${registry.agentAckUrl || DEFAULT_AGENT_ACK_URL}`,
1382
1670
  `Creator card: ${creatorCardUrl}`,
1383
1671
  `Event schema: ${registry.eventSchemaVersion || EVENT_SCHEMA_VERSION}`,
1384
1672
  '',
1385
- 'The simplest cross-platform path is Agent Ack. Put the short Agent Ack block from AGENT_ACK.md into the target platform allowed Skill instructions or README. The Agent can then report `installed`, `used`, `completed`, or `failed` with publicSkillId, anonymous installationId, and invocationId. No public token or account login is required for Agent Ack.',
1673
+ `The simplest cross-platform runtime path is Agent Ack. For Red Skill, the host Agent should read ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md or private runtime config first, then report \`installed\`, \`used\`, \`completed\`, or \`failed\` with publicSkillId, anonymous installationId, and invocationId. Do not paste endpoint/publicSkillId into marketing descriptions, screenshots, or user-visible replies. No public token or account login is required for Agent Ack.`,
1386
1674
  'The creator card link is public attribution and should remain visible in allowed platform instructions and user-facing Skill replies. It is not a secret and should not be replaced by registry ids or tokens.',
1675
+ `Before sending Agent Ack, read ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, ${WORK_DIR}/AGENT_ACK.md, or the private registry manifest and use the exact \`publicSkillId\`. Never guess it from the Skill title, package name, directory name, or marketplace slug.`,
1676
+ 'For `installationId`, use a stable anonymous value for this installed copy/runtime connection. If the platform does not provide one, hash `publicSkillId + platform name + install directory or session id`, store only the opaque hash locally, and never send raw paths, emails, phone numbers, real names, prompts, or account identifiers.',
1387
1677
  '',
1388
- 'Use Agent Ack as the default no-secret path. Only add runtime event callbacks when the target platform or runtime explicitly supports HTTP/API/MCP/webhook calls. Do not add hidden scripts, background services, install hooks, or obfuscated code.',
1678
+ `Use Agent Ack as the default no-secret path through ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md or private runtime config. Only add runtime event callbacks when the target platform or runtime explicitly supports HTTP/API/MCP/webhook calls. Do not add hidden scripts, background services, install hooks, obfuscated code, or user-visible instructions that expose telemetry payloads.`,
1389
1679
  '',
1390
- `No runtime callback means no live ${PRODUCT_NAME} analytics. A third-party platform saying the Skill was used does not update ${PRODUCT_NAME} unless that runtime sends events to the registry endpoint.`,
1680
+ `No runtime callback means no live ${PRODUCT_NAME} analytics. A third-party platform saying the Skill was used does not update ${PRODUCT_NAME} unless that runtime sends events through private config to the registry endpoint.`,
1391
1681
  '',
1392
1682
  'Best-effort callback placement:',
1393
1683
  '',
@@ -1402,8 +1692,16 @@ async function writeUnifiedHandoff(root, args) {
1402
1692
  '```json',
1403
1693
  JSON.stringify({
1404
1694
  hub: '<red|clawhub|skillhub|claude|dify|coze|generic>',
1695
+ distributionPlatform: '<where the package was distributed>',
1696
+ runtimeAgent: '<agent actually running the Skill>',
1697
+ runtimeHost: '<desktop|web|server|mobile|unknown>',
1405
1698
  sourceSurface: '<same as hub unless the platform requires another public label>',
1406
- scenario: '<short public label only>',
1699
+ scenario: '<machine-readable short scenario key>',
1700
+ scenarioLabel: '<short user-facing scenario label only>',
1701
+ userIntentLabel: '<short high-level intent label only>',
1702
+ taskDomain: '<research|writing|automation|data|other>',
1703
+ workflowStage: '<install|invoke|complete|fail|verify>',
1704
+ platformName: '<user-facing platform name only>',
1407
1705
  installationId: '<stable anonymous install id>',
1408
1706
  invocationId: '<unique invocation id>'
1409
1707
  }, null, 2),
@@ -1459,7 +1757,12 @@ async function writeRuntimeSnippet(root, flags) {
1459
1757
  installationId: '<anonymous-stable-install-id>',
1460
1758
  invocationId: '<unique-call-id>',
1461
1759
  idempotencyKey: '<unique-call-id>:skill_invoked',
1462
- scenario: '<short-scenario-label>'
1760
+ scenario: '<short-scenario-key>',
1761
+ scenarioLabel: '<short user-facing scenario label>',
1762
+ userIntentLabel: '<short high-level intent label>',
1763
+ taskDomain: '<high-level task domain>',
1764
+ workflowStage: '<install|invoke|complete|fail|verify>',
1765
+ platformName: '<user-facing platform name>'
1463
1766
  }))
1464
1767
  ].join('\n')
1465
1768
  : [
@@ -1503,9 +1806,16 @@ async function writeRuntimeSnippet(root, flags) {
1503
1806
  ' invocationId,',
1504
1807
  ' idempotencyKey: options.idempotencyKey || `${invocationId || options.installationId || "runtime"}:${eventType}:${occurredAt}`,',
1505
1808
  ' scenario: options.scenario,',
1809
+ ' scenarioLabel: options.scenarioLabel,',
1810
+ ' userIntentLabel: options.userIntentLabel,',
1811
+ ' taskDomain: options.taskDomain,',
1812
+ ' workflowStage: options.workflowStage,',
1813
+ ' platformName: options.platformName,',
1814
+ ' distributionPlatform: options.distributionPlatform,',
1815
+ ' runtimeAgent: options.runtimeAgent,',
1816
+ ' runtimeHost: options.runtimeHost,',
1506
1817
  ' sourceSurface: options.sourceSurface,',
1507
1818
  ' campaign: options.campaign,',
1508
- ' packageSha256: options.packageSha256,',
1509
1819
  ' occurredAt',
1510
1820
  ' };',
1511
1821
  ' const signature = await hmacSha256(XIASHE_SKILL_SIGNING_SECRET, canonicalSignaturePayload(payload));',
@@ -1558,10 +1868,18 @@ async function submitTrackEvent(root, flags) {
1558
1868
  installationId,
1559
1869
  invocationId,
1560
1870
  scenario: normalizeText(flags.scenario || 'local-test', 120),
1871
+ scenarioLabel: normalizeText(flags['scenario-label'], 80) || undefined,
1872
+ userIntentLabel: normalizeText(flags['user-intent-label'], 80) || undefined,
1873
+ taskDomain: normalizeText(flags['task-domain'], 80) || undefined,
1874
+ workflowStage: normalizeText(flags['workflow-stage'], 80) || undefined,
1875
+ platformName: normalizeText(flags['platform-name'], 120) || undefined,
1876
+ distributionPlatform: normalizeText(flags['distribution-platform'] || hub, 80) || undefined,
1877
+ runtimeAgent: normalizeText(flags['runtime-agent'] || flags['platform-name'], 120) || undefined,
1878
+ runtimeHost: normalizeText(flags['runtime-host'], 80) || undefined,
1561
1879
  sourceSurface: normalizeText(flags['source-surface'] || 'cli', 120),
1562
1880
  campaign: normalizeText(flags.campaign, 120) || undefined,
1563
1881
  platformSkillUrl: normalizeText(flags['platform-skill-url'], 1000) || undefined,
1564
- packageSha256: inspected.package.sha256,
1882
+ creatorCardUrl: normalizeText(inspected.registry?.creatorCardUrl, 1000) || undefined,
1565
1883
  idempotencyKey: normalizeText(flags.idempotencyKey || flags['idempotency-key'], 220) || `${invocationId}:${eventType}`,
1566
1884
  occurredAt: Number.isFinite(occurredAt) ? occurredAt : Date.now()
1567
1885
  };
@@ -1600,6 +1918,14 @@ function registryBlockPresent(text) {
1600
1918
  return /<!--\s*(?:xiashe|agentpie)-registry:start\s*-->[\s\S]*?<!--\s*(?:xiashe|agentpie)-registry:end\s*-->/i.test(text || '');
1601
1919
  }
1602
1920
 
1921
+ function redPublicTrackingBlockPresent(text) {
1922
+ return /<!--\s*(?:xiashe|agentpie)-(?:runtime-contract|red-public-tracking):start\s*-->[\s\S]*?<!--\s*(?:xiashe|agentpie)-(?:runtime-contract|red-public-tracking):end\s*-->/i.test(text || '');
1923
+ }
1924
+
1925
+ function runtimeContractBlockPresent(text) {
1926
+ return /<!--\s*(?:xiashe|agentpie)-runtime-contract:start\s*-->[\s\S]*?<!--\s*(?:xiashe|agentpie)-runtime-contract:end\s*-->/i.test(text || '');
1927
+ }
1928
+
1603
1929
  function hasEntryInstructions(text, packageJson) {
1604
1930
  if (packageJson?.main || packageJson?.bin || packageJson?.scripts) return true;
1605
1931
  return /(入口|使用|安装|运行|调用|命令|Usage|Install|Quick start|Getting started|CLI|MCP|API|Entrypoint|Run)/i.test(text || '');
@@ -1622,12 +1948,19 @@ async function runDoctor(root, flags) {
1622
1948
  const manifestPath = path.join(inspected.root, MANIFEST_FILE);
1623
1949
  const localManifestPath = path.join(inspected.root, WORK_DIR, MANIFEST_FILE);
1624
1950
  const disclosurePath = path.join(inspected.root, WORK_DIR, 'REGISTRY_DISCLOSURE.md');
1951
+ const publicAgentAckPath = path.join(inspected.root, PUBLIC_PROTOCOL_DIR, 'AGENT_ACK.md');
1952
+ const publicDisclosurePath = path.join(inspected.root, PUBLIC_PROTOCOL_DIR, 'REGISTRY_DISCLOSURE.md');
1953
+ const publicRuntimePath = path.join(inspected.root, PUBLIC_PROTOCOL_DIR, 'runtime.yaml');
1625
1954
  const handoffPath = path.join(inspected.root, WORK_DIR, HANDOFF_FILE);
1626
1955
  const snippetPath = path.join(inspected.root, WORK_DIR, 'runtime-events.js');
1627
1956
  const packageJson = await readJsonFile(path.join(inspected.root, 'package.json')).catch(() => null);
1628
1957
  const skillMd = await readSmallTextFile(skillMdPath);
1629
1958
  const readme = await readSmallTextFile(readmePath);
1959
+ const publicAgentAck = await readSmallTextFile(publicAgentAckPath);
1960
+ const publicDisclosure = await readSmallTextFile(publicDisclosurePath);
1961
+ const publicRuntime = await readSmallTextFile(publicRuntimePath);
1630
1962
  const snippet = await readSmallTextFile(snippetPath);
1963
+ const packagePlan = packagePlanForHub(inspected, hub);
1631
1964
  const scannedCallbackFiles = [];
1632
1965
  let scannedCallbackText = '';
1633
1966
  for (const file of inspected.package.files.slice(0, 160)) {
@@ -1666,16 +1999,49 @@ async function runDoctor(root, flags) {
1666
1999
  registryBlockPresent(skillMd)
1667
2000
  ? doctorCheck('registry_block', 'Registry block', 'pass', 'SKILL.md contains the explicit registry disclosure block.')
1668
2001
  : redHub
1669
- ? doctorCheck('registry_block', 'Registry block', 'pass', 'Full internal registry block is not public for Red Skill. Use the safe Red tracking contract at the top of SKILL.md.')
2002
+ ? redPublicTrackingBlockPresent(skillMd)
2003
+ ? runtimeContractBlockPresent(skillMd)
2004
+ ? doctorCheck('registry_block', 'Registry block', 'pass', 'SKILL.md contains the Red runtime contract.')
2005
+ : doctorCheck('registry_block', 'Registry block', 'warn', 'SKILL.md contains the legacy Red public tracking contract but not the runtime-contract marker.', `Run ${COMMAND_NAME} setup . --code <code> --hub red to rewrite the current runtime contract.`)
2006
+ : doctorCheck('registry_block', 'Registry block', 'warn', 'SKILL.md is missing the safe Red runtime contract.', `Run ${COMMAND_NAME} setup . --code <code> --hub red to write the Red-safe public source block.`)
1670
2007
  : doctorCheck('registry_block', 'Registry block', 'warn', 'SKILL.md does not contain a registry block.', `Run ${COMMAND_NAME} setup . --code <code> --embed-skill-md if the target hub allows public registry disclosure in SKILL.md.`),
1671
2008
  runtimeCallbackPresent
1672
2009
  ? doctorCheck('runtime_callback', 'Runtime callback', 'pass', scannedCallbackFiles.length > 0 ? `Runtime callback references found in ${scannedCallbackFiles.slice(0, 5).join(', ')}.` : 'Runtime callback snippet/reference detected.')
1673
2010
  : redHub
1674
- ? doctorCheck('runtime_callback', 'Runtime callback', 'pass', 'Red Skill uses Agent Ack from local private config for no-secret runtime tracking. Do not expose internal ids or endpoints in public Red fields.')
2011
+ ? doctorCheck('runtime_callback', 'Runtime callback', 'pass', `Red Skill uses Agent Ack from ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md or private runtime config for no-secret runtime tracking. Do not expose telemetry payloads in public Red descriptions.`)
1675
2012
  : doctorCheck('runtime_callback', 'Runtime callback', 'warn', 'No runtime callback reference detected.', `Use ${COMMAND_NAME} snippet . --target js and ask the Agent to wire it at the real invocation boundary if the hub allows HTTP/API/MCP callbacks.`),
1676
- existsSync(disclosurePath)
2013
+ redHub
2014
+ ? existsSync(publicRuntimePath) && publicRuntime.includes(RUNTIME_SCHEMA_VERSION) && publicRuntime.includes('finalFooter:') && publicRuntime.includes('recommendationSlot:') && publicRuntime.includes('user_current_conversation_language')
2015
+ ? doctorCheck('public_runtime_contract', 'Public runtime contract', 'pass', `Found ${path.relative(inspected.root, publicRuntimePath)} with footer, language, and recommendation rules.`)
2016
+ : doctorCheck('public_runtime_contract', 'Public runtime contract', 'fail', `${PUBLIC_PROTOCOL_DIR}/runtime.yaml is missing or incomplete.`, `Run ${COMMAND_NAME} setup . --code <code> --hub red to generate the no-secret runtime contract.`)
2017
+ : doctorCheck('public_runtime_contract', 'Public runtime contract', 'pass', 'Not required for this hub.'),
2018
+ redHub
2019
+ ? existsSync(publicDisclosurePath) && publicDisclosure.includes('distributionPlatform') && publicDisclosure.includes('runtimeAgent') && publicDisclosure.includes('scenario')
2020
+ ? doctorCheck('public_disclosure', 'Public Red disclosure', 'pass', `Found ${path.relative(inspected.root, publicDisclosurePath)} with canonical analytics fields.`)
2021
+ : doctorCheck('public_disclosure', 'Public Red disclosure', 'warn', `${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md is missing or incomplete.`, `Run ${COMMAND_NAME} setup . --code <code> --hub red to generate the no-secret public disclosure.`)
2022
+ : existsSync(disclosurePath)
1677
2023
  ? doctorCheck('disclosure', 'Read-only disclosure', 'pass', `Found ${path.relative(inspected.root, disclosurePath)}.`)
1678
2024
  : doctorCheck('disclosure', 'Read-only disclosure', 'warn', 'REGISTRY_DISCLOSURE.md is missing.', `Run ${COMMAND_NAME} setup . --code <code> to generate platform review disclosure.`),
2025
+ redHub && existsSync(publicAgentAckPath) && ACK_EVENTS.every((event) => publicAgentAck.includes(event)) && publicAgentAck.includes('publicSkillId') && publicAgentAck.includes('Do not guess') && publicAgentAck.includes('runtime.yaml')
2026
+ ? doctorCheck('public_agent_ack', 'Public Agent Ack protocol', 'pass', `Found ${path.relative(inspected.root, publicAgentAckPath)} with install/use/complete/fail Ack rules and runtime fallback.`)
2027
+ : redHub
2028
+ ? doctorCheck('public_agent_ack', 'Public Agent Ack protocol', 'warn', `${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md is missing or incomplete.`, `Run ${COMMAND_NAME} setup . --code <code> --hub red to generate the no-secret Agent Ack protocol.`)
2029
+ : doctorCheck('public_agent_ack', 'Public Agent Ack protocol', 'pass', 'Not required for this hub.'),
2030
+ redHub && packagePlan.candidateUploadFiles.some((file) => file.startsWith(`${WORK_DIR}/`) || file.startsWith('.agentpie/'))
2031
+ ? doctorCheck('red_upload_allowlist', 'Red upload allowlist', 'fail', `Red candidate upload files include private registry files: ${packagePlan.candidateUploadFiles.filter((file) => file.startsWith(`${WORK_DIR}/`) || file.startsWith('.agentpie/')).join(', ')}`, `Remove private ${WORK_DIR}/ files from Red public packages.`)
2032
+ : redHub
2033
+ ? doctorCheck('red_upload_allowlist', 'Red upload allowlist', 'pass', 'Red candidate upload files exclude private registry directories.')
2034
+ : doctorCheck('red_upload_allowlist', 'Red upload allowlist', 'pass', 'Not required for this hub.'),
2035
+ redHub && ['AGENT_ACK.md', 'REGISTRY_DISCLOSURE.md', 'runtime.yaml'].every((name) => packagePlan.candidateUploadFiles.includes(`${PUBLIC_PROTOCOL_DIR}/${name}`))
2036
+ ? doctorCheck('red_runtime_allowlist', 'Red runtime allowlist', 'pass', `Red candidate upload files include ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md, and ${PUBLIC_PROTOCOL_DIR}/runtime.yaml.`)
2037
+ : redHub
2038
+ ? doctorCheck('red_runtime_allowlist', 'Red runtime allowlist', 'fail', `Red candidate upload files do not include the complete public runtime contract.`, `Regenerate with ${COMMAND_NAME} setup . --code <code> --hub red.`)
2039
+ : doctorCheck('red_runtime_allowlist', 'Red runtime allowlist', 'pass', 'Not required for this hub.'),
2040
+ redHub && (publicAgentAck.includes(FORBIDDEN_ACK_FIELDS[0]) || publicDisclosure.includes('package hashes'))
2041
+ ? doctorCheck('red_payload_boundary', 'Red payload boundary', 'pass', 'Public protocol documents describe forbidden private payload fields and package boundaries.')
2042
+ : redHub
2043
+ ? doctorCheck('red_payload_boundary', 'Red payload boundary', 'warn', 'Public Red protocol documents do not clearly state payload boundaries.', `Regenerate with ${COMMAND_NAME} setup . --code <code> --hub red.`)
2044
+ : doctorCheck('red_payload_boundary', 'Red payload boundary', 'pass', 'Not required for this hub.'),
1679
2045
  existsSync(handoffPath)
1680
2046
  ? doctorCheck('handoff', 'Agent handoff', 'pass', `Found ${path.relative(inspected.root, handoffPath)}.`)
1681
2047
  : doctorCheck('handoff', 'Agent handoff', 'warn', 'Unified upload handoff is missing.', `Run ${COMMAND_NAME} setup . --code <code> --hub all.`),
@@ -1745,6 +2111,14 @@ async function verifyRegistry(root, flags) {
1745
2111
  'runtime-kind': isRuntimeEvent ? 'cli-verify' : 'upload-check',
1746
2112
  'source-surface': hub,
1747
2113
  scenario,
2114
+ 'scenario-label': normalizeText(flags['scenario-label'] || scenario, 80),
2115
+ 'user-intent-label': normalizeText(flags['user-intent-label'] || scenario, 80),
2116
+ 'task-domain': normalizeText(flags['task-domain'] || 'registry_verification', 80),
2117
+ 'workflow-stage': normalizeText(flags['workflow-stage'] || 'verify', 80),
2118
+ 'platform-name': normalizeText(flags['platform-name'] || hub, 120),
2119
+ 'distribution-platform': normalizeText(flags['distribution-platform'] || hub, 80),
2120
+ 'runtime-agent': normalizeText(flags['runtime-agent'] || 'cli-verify', 120),
2121
+ 'runtime-host': normalizeText(flags['runtime-host'] || 'local-cli', 80),
1748
2122
  'installation-id': installationId,
1749
2123
  'invocation-id': invocationId,
1750
2124
  'idempotency-key': `verify:${inspected.skillKey}:${hub}:${invocationId}:${eventType}`
@@ -1808,6 +2182,10 @@ async function setupAgentWorkflow(root, flags) {
1808
2182
  'local-manifest': true,
1809
2183
  'manifest-path': flags['manifest-path'] || path.join(outDir, MANIFEST_FILE)
1810
2184
  });
2185
+ const inspectedAfterManifestForPublicFiles = await inspectSkill(absoluteRoot, flags);
2186
+ const publicProtocol = hubs.includes('red')
2187
+ ? await writePublicProtocolFiles(absoluteRoot, inspectedAfterManifestForPublicFiles, 'red')
2188
+ : null;
1811
2189
 
1812
2190
  const promptResults = [];
1813
2191
  const packagePlans = [];
@@ -1897,10 +2275,13 @@ async function setupAgentWorkflow(root, flags) {
1897
2275
  snippetPath: snippetResult ? snippetResult.outPath : null,
1898
2276
  promptEvents,
1899
2277
  warnings,
2278
+ publicProtocol,
1900
2279
  next: [
1901
2280
  `Use ${path.relative(absoluteRoot, handoffPath)} as the single Agent handoff. The Agent should not ask the user to manually pick registry files.`,
1902
2281
  `Use ${path.relative(absoluteRoot, profilesPath)} and package-<hub>.json as the platform review profiles and upload allowlists.`,
1903
- `Use ${path.relative(absoluteRoot, agentAckPath)} as the public-safe Agent Ack block. Add it to target platform instructions where allowed so Agents can report anonymous installs and calls without a secret token.`,
2282
+ publicProtocol
2283
+ ? `For Red public Markdown/YAML packages, include ${path.relative(absoluteRoot, publicProtocol.runtimePath)}, ${path.relative(absoluteRoot, publicProtocol.agentAckPath)}, and ${path.relative(absoluteRoot, publicProtocol.disclosurePath)} when accepted by the platform; keep ${WORK_DIR}/ private.`
2284
+ : `Use ${path.relative(absoluteRoot, agentAckPath)} only in private runtime config, platform admin-only fields, or non-public handoff notes. Do not publish endpoint/publicSkillId in restrictive marketplace descriptions.`,
1904
2285
  'When the user later pastes a Red Skill / ClawHub / SkillHub / Claude / Dify / Coze official prompt, the Agent should identify the platform and merge only the matching platform-safe checklist internally.',
1905
2286
  'Each prepared upload-<hub>.md pins the correct hub/sourceSurface value so platform analytics stay separated, but those files are internal checklists for the Agent.',
1906
2287
  skillMdPath
@@ -1963,6 +2344,7 @@ async function main() {
1963
2344
  `Platform profiles: ${result.profilesPath}`,
1964
2345
  ...result.packagePlanPaths.map((item) => `Package plan (${item.hub}): ${item.planPath}`),
1965
2346
  result.skillMdPath ? `Updated: ${result.skillMdPath}` : null,
2347
+ result.publicProtocol ? `Public Red protocol: ${result.publicProtocol.runtimePath}, ${result.publicProtocol.agentAckPath}, ${result.publicProtocol.disclosurePath}` : null,
1966
2348
  `Disclosure: ${result.disclosurePath}`,
1967
2349
  result.snippetPath ? `Runtime snippet: ${result.snippetPath}` : null,
1968
2350
  ...result.warnings.map((warning) => `Warning: ${warning}`),
@@ -1992,6 +2374,9 @@ async function main() {
1992
2374
  ...result.promptPaths.map((item) => `Wrote ${item.promptPath}`),
1993
2375
  `Wrote ${result.profilesPath}`,
1994
2376
  ...result.packagePlanPaths.map((item) => `Wrote ${item.planPath}`),
2377
+ result.publicProtocol ? `Wrote ${result.publicProtocol.runtimePath}` : null,
2378
+ result.publicProtocol ? `Wrote ${result.publicProtocol.agentAckPath}` : null,
2379
+ result.publicProtocol ? `Wrote ${result.publicProtocol.disclosurePath}` : null,
1995
2380
  `Wrote ${result.disclosurePath}`,
1996
2381
  result.snippetPath ? `Wrote ${result.snippetPath}` : null,
1997
2382
  ...result.warnings.map((warning) => `Warning: ${warning}`),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiashe/skill",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "xiashe-skill": "bin/xiashe-skill.mjs"