@xiashe/skill 0.1.16 → 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 private `.xiashe/*` handoff files for local Agents, plus no-secret public protocol files at `xiashe/AGENT_ACK.md` and `xiashe/REGISTRY_DISCLOSURE.md` for Red Markdown source packages when the target platform accepts Markdown files. 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.
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.16';
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');
@@ -25,9 +25,16 @@ const DEFAULT_CLAIM_URL = process.env.XIASHE_SKILL_CLAIM_URL || `${DEFAULT_BASE_
25
25
  const EVENT_SCHEMA_VERSION = process.env.XIASHE_SKILL_EVENT_SCHEMA_VERSION || (REGISTRY_PROVIDER === 'agentpie'
26
26
  ? 'agentpie.skill.event.v1'
27
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');
28
31
  const SUPPORTED_HUBS = ['xiashe', 'red', 'clawhub', 'skillhub', 'claude', 'codex', 'cursor', 'workbuddy', 'dify', 'coze', 'generic'];
29
32
  const DEFAULT_SETUP_HUBS = ['xiashe', 'red'];
30
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'];
31
38
  const SCENARIO_ENUM_EXAMPLES = [
32
39
  'research_digest',
33
40
  'paper_search',
@@ -39,6 +46,19 @@ const SCENARIO_ENUM_EXAMPLES = [
39
46
  'knowledge_lookup',
40
47
  'other'
41
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
+ ];
42
62
  const FORBIDDEN_ACK_FIELDS = [
43
63
  'prompt',
44
64
  'chatTranscript',
@@ -69,10 +89,10 @@ const PLATFORM_REVIEW_PROFILES = {
69
89
  label: 'Red Skill',
70
90
  packageMode: 'markdown_or_text_only',
71
91
  manifestPolicy: 'local_only',
72
- disclosurePlacement: ['platform description field', 'README', 'SKILL.md', `${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md`, `${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.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`],
73
93
  runtimeDataPolicy: 'agent_ack_supported',
74
94
  defaultDataSource: 'runtime',
75
- 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}/AGENT_ACK.md + ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md protocol files in the public package when Markdown 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.`,
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.`,
76
96
  xiasheLocalOnlyFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, `${WORK_DIR}/runtime-events.js`, 'internal handoff/checklists'],
77
97
  forbiddenPublicFiles: ['credentials', 'cookies', 'browser/account sessions', 'public tokens', 'signing secrets', 'hidden telemetry'],
78
98
  safetyChecklist: [
@@ -224,6 +244,14 @@ Options:
224
244
  --installation-id <id> Stable anonymous install id for runtime analytics.
225
245
  --invocation-id <id> Unique invocation id for one skill call.
226
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.
227
255
  --campaign <label> Campaign or promotion label.
228
256
  --platform-skill-url <url> Published Skill URL for hub_upload_succeeded events.
229
257
  --platform-prompt-file <p> Official third-party hub prompt file to embed in the handoff.
@@ -453,6 +481,13 @@ function isPublicDocFile(filePath) {
453
481
  /\.(md|mdx|txt)$/i.test(filePath);
454
482
  }
455
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
+
456
491
  function isLikelyEntrypointFile(filePath) {
457
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);
458
493
  }
@@ -484,7 +519,7 @@ function packagePlanForHub(inspected, hub) {
484
519
  continue;
485
520
  }
486
521
  if (profile.packageMode === 'markdown_or_text_only') {
487
- if (isPublicDocFile(file.path)) {
522
+ if (isPublicDocFile(file.path) || isPublicRuntimeProtocolFile(file.path)) {
488
523
  candidateUploadFiles.push(file.path);
489
524
  }
490
525
  continue;
@@ -533,7 +568,7 @@ function packagePlanForHub(inspected, hub) {
533
568
  ? `Keep ${WORK_DIR}/${MANIFEST_FILE} local by default. Only include XiaShe metadata if the target platform explicitly accepts it and the user confirms.`
534
569
  : 'Confirm platform file rules before uploading auxiliary registry metadata.',
535
570
  hub === 'red'
536
- ? `For Red Skill, include only public source files plus no-secret ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md when Markdown 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.`
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.`
537
572
  : '',
538
573
  profile.runtimeDataPolicy === 'attribution_only_by_default'
539
574
  ? 'Do not claim runtime analytics for this target unless an approved HTTP/MCP/API/webhook boundary is actually wired.'
@@ -886,7 +921,7 @@ function uploadCompatibilityLines(hub, manifestFile) {
886
921
  'Red Skill 兼容性要求:',
887
922
  '- 以小红书官方 `uploader.md`、`skillhub-upload` 或页面上传流程为准;不要用虾舍 prompt 替代官方流程。',
888
923
  '- 上传包必须在 SKILL.md 最开头写清楚创作者名片、公开来源和安全使用边界。',
889
- `- 如果平台接受 Markdown 源包,可以包含 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 和 ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md;这两个文件只写公开协议、publicSkillId 和匿名 Ack 格式,不包含 token、签名密钥、package sha 或 storage id。`,
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。`,
890
925
  '- 不要要求用户可见回复追加广告占位、telemetry JSON、包指纹、后台字段或本地路径。',
891
926
  `- ${localManifest} 默认只保留在用户本地,用于虾舍 registry claim、Agent Ack 和后续手动/公开数据导入;不要放进 Red 公开介绍。`,
892
927
  '- public token、signing secret、registry id、package sha、storage id、runtime-events.js、.xiashe 内部 handoff/checklist 不进入 Red 公开介绍或公开包。',
@@ -926,7 +961,7 @@ function publicDisclosure(inspected, registryUrl, hub = 'generic') {
926
961
  creatorCardUrl ? `Creator card: ${creatorCardUrl}` : 'Creator card: <creator card link from XiaShe dashboard>',
927
962
  ...creatorLines.filter((line) => !line.startsWith('- Creator card:')),
928
963
  '',
929
- `- Public protocol files, when included: ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md.`,
964
+ `- Public protocol files, when included: ${PUBLIC_PROTOCOL_DIR}/runtime.yaml, ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md.`,
930
965
  '- Data mode for Red Skill upload: Agent Ack runtime only when the host Agent calls a no-secret Ack protocol; upload/reported otherwise.',
931
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.',
932
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.',
@@ -958,8 +993,9 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
958
993
  const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from local registry manifest>';
959
994
  const ackUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
960
995
  const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
961
- const eventSchemaVersion = registry.eventSchemaVersion || EVENT_SCHEMA_VERSION;
996
+ const eventSchemaVersion = registry.runtimeSchemaVersion || RUNTIME_SCHEMA_VERSION;
962
997
  const normalizedHub = normalizeHub(hub) || hub || 'generic';
998
+ const distributionPlatform = runtimeDistributionPlatformForHub(normalizedHub);
963
999
  return [
964
1000
  `### ${PRODUCT_NAME} Agent Ack`,
965
1001
  '',
@@ -969,10 +1005,14 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
969
1005
  `- Public Skill ID: ${publicSkillId}`,
970
1006
  `- Creator card: ${creatorCardUrl}`,
971
1007
  ...creatorProfileSummaryLines(registry, 'en').filter((line) => !line.startsWith('- Creator card:')),
972
- `- Event schema: ${eventSchemaVersion}`,
1008
+ `- Runtime schema: ${eventSchemaVersion}`,
973
1009
  `- Platform hub: ${normalizedHub}`,
1010
+ `- Distribution platform: ${distributionPlatform}`,
974
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.',
975
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`.',
976
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.',
977
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.`,
978
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.`,
@@ -980,7 +1020,12 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
980
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.',
981
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.',
982
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.',
983
- `- Canonical scenario examples: ${SCENARIO_ENUM_EXAMPLES.join(', ')}. Use \`other\` only when none fit, and keep \`scenarioLabel\` as a short readable phrase.`,
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.',
984
1029
  `- Forbidden payload fields: ${FORBIDDEN_ACK_FIELDS.join(', ')}.`,
985
1030
  '',
986
1031
  'When an Agent installs or uses this Skill, it may POST:',
@@ -990,9 +1035,9 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
990
1035
  publicSkillId,
991
1036
  schemaVersion: eventSchemaVersion,
992
1037
  event: 'completed',
993
- distributionPlatform: normalizedHub,
994
- runtimeAgent: '<coze|workbuddy|codex|claude|dify|custom-agent>',
995
- runtimeHost: '<desktop|web|server|mobile|unknown>',
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>',
996
1041
  platform: normalizedHub,
997
1042
  skillKey: inspected.skillKey,
998
1043
  creatorCardUrl,
@@ -1000,7 +1045,10 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
1000
1045
  invocationId: '<unique call id>',
1001
1046
  scenario: '<machine-readable short scenario key>',
1002
1047
  scenarioLabel: '<short user-facing scenario label, e.g. 论文监控>',
1003
- sourceSurface: '<profile|hub|agent|direct>',
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>',
1004
1052
  platformSkillUrl: '<published platform skill url or null>',
1005
1053
  platformName: '<user-facing platform name, e.g. Red Skill or Coze>',
1006
1054
  agentKind: '<agent name, optional>'
@@ -1016,17 +1064,19 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
1016
1064
 
1017
1065
  function redPublicTrackingContractBlock(inspected) {
1018
1066
  const registry = inspected.registry || {};
1019
- const marker = `${REGISTRY_PROVIDER}-red-public-tracking`;
1067
+ const marker = `${REGISTRY_PROVIDER}-runtime-contract`;
1020
1068
  const cardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
1021
1069
  return [
1022
1070
  `<!-- ${marker}:start -->`,
1023
1071
  `## ${PRODUCT_NAME} 公开来源与安全使用`,
1024
1072
  '',
1025
- `这个 Skill 来自 ${PRODUCT_NAME} 创作者。公开包只保留创作者来源、安全使用说明,以及 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md / ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这类无密钥协议文件;安装、调用、完成和失败等统计应由宿主平台或本地配置在后台匿名回传。`,
1073
+ `这个 Skill 来自 ${PRODUCT_NAME} 创作者。公开包只保留创作者来源、安全使用说明,以及 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md、${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这类无密钥协议文件;安装、调用、完成和失败等统计应由宿主平台或本地配置在后台匿名回传。`,
1026
1074
  '',
1027
1075
  '公开使用要求:',
1028
1076
  '- 如果回答里出现数字、金额、比例、时间、地点或外部链接,要用清晰列表或表格结构化展示,并保留原始链接。',
1029
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 响应返回的内容,没有返回时写“无”。`,
1030
1080
  '- platformSource、scenario 等归因字段只属于 private runtime config 或平台私有字段;不要放进公开介绍或用户可见回复。',
1031
1081
  '- 如果宿主允许匿名回传,请把 platformName 写成用户能看懂的平台名,把 scenarioLabel 写成 2-8 个字的用途标签;不要发送原始 prompt 或任务全文。',
1032
1082
  `- scenario 使用稳定枚举,例如 ${SCENARIO_ENUM_EXAMPLES.slice(0, 6).join('、')};scenarioLabel 才写用户能看懂的短词。`,
@@ -1039,7 +1089,7 @@ function redPublicTrackingContractBlock(inspected) {
1039
1089
  '隐私与公开展示边界:',
1040
1090
  '- 只发送匿名聚合状态,不发送用户 prompt、聊天内容、本地文件、命令输出、Cookie、凭据、账号信息、IP、DNS、路由器或个人身份信息。',
1041
1091
  '- 不要把 private registry id、public token、package sha、storage id、upload job id、签名密钥、内部 handoff 或后台字段写进 Red Skill 公开介绍。',
1042
- `- publicSkillId 和 Agent Ack endpoint 只能保留在 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要写入营销介绍、用户回复或截图文案。`,
1092
+ `- publicSkillId 和 Agent Ack endpoint 只能保留在 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要写入营销介绍、用户回复或截图文案。`,
1043
1093
  `- 如果当前 Agent 无法调用 HTTP/MCP,就保留上述公开约定,不伪造 ${PRODUCT_NAME} runtime 数据。`,
1044
1094
  '',
1045
1095
  `<!-- ${marker}:end -->`
@@ -1083,7 +1133,7 @@ async function writeSkillMdRegistryBlock(root, inspected, hub) {
1083
1133
  const block = skillMdRegistryBlock(inspected, hub);
1084
1134
  const isRed = normalizeHub(hub) === 'red';
1085
1135
  const pattern = isRed
1086
- ? /<!-- (?: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
1087
1137
  : /<!-- (?:xiashe|agentpie)-registry:start -->[\s\S]*?<!-- (?:xiashe|agentpie)-registry:end -->/m;
1088
1138
  const next = pattern.test(existing)
1089
1139
  ? existing.replace(pattern, block)
@@ -1100,11 +1150,157 @@ function stripDisclosureFence(lines) {
1100
1150
  .join('\n');
1101
1151
  }
1102
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
+
1103
1298
  async function writePublicProtocolFiles(root, inspected, hub = 'red') {
1104
1299
  const protocolDir = path.join(root, PUBLIC_PROTOCOL_DIR);
1105
1300
  await mkdir(protocolDir, { recursive: true });
1106
1301
  const agentAckPath = path.join(protocolDir, 'AGENT_ACK.md');
1107
1302
  const disclosurePath = path.join(protocolDir, 'REGISTRY_DISCLOSURE.md');
1303
+ const runtimePath = path.join(protocolDir, 'runtime.yaml');
1108
1304
  const registry = inspected.registry || {};
1109
1305
  const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from XiaShe registry>';
1110
1306
  const agentAck = [
@@ -1113,10 +1309,19 @@ async function writePublicProtocolFiles(root, inspected, hub = 'red') {
1113
1309
  '## Install / Use Flow',
1114
1310
  '',
1115
1311
  '1. Install the Skill through the target platform official flow.',
1116
- `2. Read this file to get the exact \`publicSkillId\`: \`${publicSkillId}\`. Never infer it from the title or directory name.`,
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.`,
1117
1313
  '3. Send `installed` once after installation succeeds. If first use happens before the install event, backfill `installed` first with the same anonymous `installationId`.',
1118
1314
  '4. Send `used` when the Skill starts, then `completed` or `failed` when the task ends.',
1119
- '5. Keep user-visible answers focused on the task result. Do not append telemetry JSON or internal identifiers.',
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
+ '```',
1120
1325
  '',
1121
1326
  '## Payload Boundary',
1122
1327
  '',
@@ -1129,22 +1334,23 @@ async function writePublicProtocolFiles(root, inspected, hub = 'red') {
1129
1334
  '',
1130
1335
  '## Public Package Boundary',
1131
1336
  '',
1132
- `Allowed Red public protocol files: \`${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md\`, \`${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md\`.`,
1337
+ `Allowed Red public protocol files: \`${PUBLIC_PROTOCOL_DIR}/runtime.yaml\`, \`${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md\`, \`${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md\`.`,
1133
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.`,
1134
1339
  '',
1135
1340
  '## Canonical Analytics Fields',
1136
1341
  '',
1137
- '- `distributionPlatform`: where the package was distributed, such as `red` or `xiashe`.',
1138
- '- `runtimeAgent`: the Agent actually running it, such as `coze`, `workbuddy`, `codex`, `claude`, `dify`, or `custom-agent`.',
1139
- '- `runtimeHost`: the host context, such as `desktop`, `web`, `server`, `mobile`, or `unknown`.',
1140
- '- `sourceSurface`: where the user discovered or opened it, such as `creator-card`, `red`, `xiashe`, `direct`, or `agent`.',
1141
- `- \`scenario\`: one of ${SCENARIO_ENUM_EXAMPLES.join(', ')} when possible.`,
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.`,
1142
1347
  '- `scenarioLabel`: a short user-facing label, not the raw prompt.',
1143
1348
  ''
1144
1349
  ].join('\n');
1145
1350
  await writeFile(agentAckPath, agentAck, { mode: 0o644 });
1146
1351
  await writeFile(disclosurePath, disclosure, { mode: 0o644 });
1147
- return { agentAckPath, disclosurePath };
1352
+ await writeFile(runtimePath, buildRuntimeYaml(inspected, hub), { mode: 0o644 });
1353
+ return { agentAckPath, disclosurePath, runtimePath };
1148
1354
  }
1149
1355
 
1150
1356
  async function readPlatformPrompt(flags) {
@@ -1247,7 +1453,7 @@ async function uploadPrompt(inspected, flags) {
1247
1453
  `- 描述:${inspected.description || '<从 SKILL.md/README.md 中提炼>'}`,
1248
1454
  `- 创作者名片:${creatorCardUrl}`,
1249
1455
  ...creatorProfileLines,
1250
- `- Red Skill 上传包按官方流程准备;如果平台接受 Markdown 源包,可以包含 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 和 ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这两个无密钥协议文件。`,
1456
+ `- Red Skill 上传包按官方流程准备;如果平台接受 Markdown/YAML 源包,可以包含 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 和 ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这三个无密钥运行时协议文件。`,
1251
1457
  `- ${WORK_DIR}/、public token、signing secret、registry id、package sha、storage id、upload job id、runtime-events.js 和内部 handoff/checklist 默认只在本地保留,不进入 Red 公开包。`,
1252
1458
  `- publicSkillId 和 Agent Ack endpoint 只能出现在 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要写进营销介绍、用户可见回复或截图说明。`,
1253
1459
  '- SKILL.md 最开头只写公开来源、安全使用说明和创作者名片链接;不要要求下游 Agent 在用户可见回复里追加 telemetry JSON。',
@@ -1357,7 +1563,7 @@ async function uploadPrompt(inspected, flags) {
1357
1563
  ? [
1358
1564
  'Red 公开包边界:',
1359
1565
  '',
1360
- `- 可以包含:SKILL.md、README/公开 references,以及 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md、${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这类无密钥协议文件。`,
1566
+ `- 可以包含:SKILL.md、README/公开 references,以及 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md、${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这类无密钥运行时协议文件。`,
1361
1567
  `- 不要包含:${WORK_DIR}/、${WORK_DIR}/${MANIFEST_FILE}、runtime-events.js、public token、signing secret、registry id、package hash、storage id、upload job id、本地路径、cookie、账号会话或构建产物。`,
1362
1568
  `- publicSkillId 和 Agent Ack endpoint 只能放在 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要放进营销介绍、用户可见回复或截图说明。`,
1363
1569
  '- 如果宿主 Agent 不能读取协议文件/私有配置并执行 Ack,Red 安装成功不会自动变成虾舍 runtime 数据。'
@@ -1425,7 +1631,7 @@ async function writeUnifiedHandoff(root, args) {
1425
1631
  `2. Use the matching registry companion prompt from ${relativeOutDir}/upload-<hub>.md only as an internal checklist.`,
1426
1632
  '3. Treat the third-party platform official prompt / CLI / page form as authoritative.',
1427
1633
  `4. For XiaShe Store, create or reuse the searchable listing. For Red Skill, use the official uploader.md / skillhub-upload flow.`,
1428
- `5. For Red Skill, include no-secret ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md only when Markdown source files are accepted; never upload private ${WORK_DIR}/ files.`,
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.`,
1429
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.',
1430
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.`,
1431
1637
  '8. Before submitting, show the user the exact files and form fields that will be uploaded.',
@@ -1434,7 +1640,7 @@ async function writeUnifiedHandoff(root, args) {
1434
1640
  'Red Skill exception:',
1435
1641
  '',
1436
1642
  '- Red Skill should use the official uploader.md / skillhub-upload / web upload flow as the source of truth.',
1437
- `- Public Red Markdown packages may include ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md as no-secret protocol/disclosure files.`,
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.`,
1438
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.`,
1439
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.',
1440
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.',
@@ -1492,6 +1698,9 @@ async function writeUnifiedHandoff(root, args) {
1492
1698
  sourceSurface: '<same as hub unless the platform requires another public label>',
1493
1699
  scenario: '<machine-readable short scenario key>',
1494
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>',
1495
1704
  platformName: '<user-facing platform name only>',
1496
1705
  installationId: '<stable anonymous install id>',
1497
1706
  invocationId: '<unique invocation id>'
@@ -1550,6 +1759,9 @@ async function writeRuntimeSnippet(root, flags) {
1550
1759
  idempotencyKey: '<unique-call-id>:skill_invoked',
1551
1760
  scenario: '<short-scenario-key>',
1552
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>',
1553
1765
  platformName: '<user-facing platform name>'
1554
1766
  }))
1555
1767
  ].join('\n')
@@ -1595,7 +1807,13 @@ async function writeRuntimeSnippet(root, flags) {
1595
1807
  ' idempotencyKey: options.idempotencyKey || `${invocationId || options.installationId || "runtime"}:${eventType}:${occurredAt}`,',
1596
1808
  ' scenario: options.scenario,',
1597
1809
  ' scenarioLabel: options.scenarioLabel,',
1810
+ ' userIntentLabel: options.userIntentLabel,',
1811
+ ' taskDomain: options.taskDomain,',
1812
+ ' workflowStage: options.workflowStage,',
1598
1813
  ' platformName: options.platformName,',
1814
+ ' distributionPlatform: options.distributionPlatform,',
1815
+ ' runtimeAgent: options.runtimeAgent,',
1816
+ ' runtimeHost: options.runtimeHost,',
1599
1817
  ' sourceSurface: options.sourceSurface,',
1600
1818
  ' campaign: options.campaign,',
1601
1819
  ' occurredAt',
@@ -1650,6 +1868,14 @@ async function submitTrackEvent(root, flags) {
1650
1868
  installationId,
1651
1869
  invocationId,
1652
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,
1653
1879
  sourceSurface: normalizeText(flags['source-surface'] || 'cli', 120),
1654
1880
  campaign: normalizeText(flags.campaign, 120) || undefined,
1655
1881
  platformSkillUrl: normalizeText(flags['platform-skill-url'], 1000) || undefined,
@@ -1693,7 +1919,11 @@ function registryBlockPresent(text) {
1693
1919
  }
1694
1920
 
1695
1921
  function redPublicTrackingBlockPresent(text) {
1696
- return /<!--\s*(?:xiashe|agentpie)-red-public-tracking:start\s*-->[\s\S]*?<!--\s*(?:xiashe|agentpie)-red-public-tracking:end\s*-->/i.test(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 || '');
1697
1927
  }
1698
1928
 
1699
1929
  function hasEntryInstructions(text, packageJson) {
@@ -1720,6 +1950,7 @@ async function runDoctor(root, flags) {
1720
1950
  const disclosurePath = path.join(inspected.root, WORK_DIR, 'REGISTRY_DISCLOSURE.md');
1721
1951
  const publicAgentAckPath = path.join(inspected.root, PUBLIC_PROTOCOL_DIR, 'AGENT_ACK.md');
1722
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');
1723
1954
  const handoffPath = path.join(inspected.root, WORK_DIR, HANDOFF_FILE);
1724
1955
  const snippetPath = path.join(inspected.root, WORK_DIR, 'runtime-events.js');
1725
1956
  const packageJson = await readJsonFile(path.join(inspected.root, 'package.json')).catch(() => null);
@@ -1727,6 +1958,7 @@ async function runDoctor(root, flags) {
1727
1958
  const readme = await readSmallTextFile(readmePath);
1728
1959
  const publicAgentAck = await readSmallTextFile(publicAgentAckPath);
1729
1960
  const publicDisclosure = await readSmallTextFile(publicDisclosurePath);
1961
+ const publicRuntime = await readSmallTextFile(publicRuntimePath);
1730
1962
  const snippet = await readSmallTextFile(snippetPath);
1731
1963
  const packagePlan = packagePlanForHub(inspected, hub);
1732
1964
  const scannedCallbackFiles = [];
@@ -1768,14 +2000,21 @@ async function runDoctor(root, flags) {
1768
2000
  ? doctorCheck('registry_block', 'Registry block', 'pass', 'SKILL.md contains the explicit registry disclosure block.')
1769
2001
  : redHub
1770
2002
  ? redPublicTrackingBlockPresent(skillMd)
1771
- ? doctorCheck('registry_block', 'Registry block', 'pass', 'SKILL.md contains the safe Red public tracking contract.')
1772
- : doctorCheck('registry_block', 'Registry block', 'warn', 'SKILL.md is missing the safe Red public tracking contract.', `Run ${COMMAND_NAME} setup . --code <code> --hub red to write the Red-safe public source block.`)
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.`)
1773
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.`),
1774
2008
  runtimeCallbackPresent
1775
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.')
1776
2010
  : redHub
1777
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.`)
1778
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.`),
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.'),
1779
2018
  redHub
1780
2019
  ? existsSync(publicDisclosurePath) && publicDisclosure.includes('distributionPlatform') && publicDisclosure.includes('runtimeAgent') && publicDisclosure.includes('scenario')
1781
2020
  ? doctorCheck('public_disclosure', 'Public Red disclosure', 'pass', `Found ${path.relative(inspected.root, publicDisclosurePath)} with canonical analytics fields.`)
@@ -1783,8 +2022,8 @@ async function runDoctor(root, flags) {
1783
2022
  : existsSync(disclosurePath)
1784
2023
  ? doctorCheck('disclosure', 'Read-only disclosure', 'pass', `Found ${path.relative(inspected.root, disclosurePath)}.`)
1785
2024
  : doctorCheck('disclosure', 'Read-only disclosure', 'warn', 'REGISTRY_DISCLOSURE.md is missing.', `Run ${COMMAND_NAME} setup . --code <code> to generate platform review disclosure.`),
1786
- redHub && existsSync(publicAgentAckPath) && ACK_EVENTS.every((event) => publicAgentAck.includes(event)) && publicAgentAck.includes('publicSkillId') && publicAgentAck.includes('Do not guess')
1787
- ? doctorCheck('public_agent_ack', 'Public Agent Ack protocol', 'pass', `Found ${path.relative(inspected.root, publicAgentAckPath)} with install/use/complete/fail Ack rules.`)
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.`)
1788
2027
  : redHub
1789
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.`)
1790
2029
  : doctorCheck('public_agent_ack', 'Public Agent Ack protocol', 'pass', 'Not required for this hub.'),
@@ -1793,6 +2032,11 @@ async function runDoctor(root, flags) {
1793
2032
  : redHub
1794
2033
  ? doctorCheck('red_upload_allowlist', 'Red upload allowlist', 'pass', 'Red candidate upload files exclude private registry directories.')
1795
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.'),
1796
2040
  redHub && (publicAgentAck.includes(FORBIDDEN_ACK_FIELDS[0]) || publicDisclosure.includes('package hashes'))
1797
2041
  ? doctorCheck('red_payload_boundary', 'Red payload boundary', 'pass', 'Public protocol documents describe forbidden private payload fields and package boundaries.')
1798
2042
  : redHub
@@ -1867,6 +2111,14 @@ async function verifyRegistry(root, flags) {
1867
2111
  'runtime-kind': isRuntimeEvent ? 'cli-verify' : 'upload-check',
1868
2112
  'source-surface': hub,
1869
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),
1870
2122
  'installation-id': installationId,
1871
2123
  'invocation-id': invocationId,
1872
2124
  'idempotency-key': `verify:${inspected.skillKey}:${hub}:${invocationId}:${eventType}`
@@ -2028,7 +2280,7 @@ async function setupAgentWorkflow(root, flags) {
2028
2280
  `Use ${path.relative(absoluteRoot, handoffPath)} as the single Agent handoff. The Agent should not ask the user to manually pick registry files.`,
2029
2281
  `Use ${path.relative(absoluteRoot, profilesPath)} and package-<hub>.json as the platform review profiles and upload allowlists.`,
2030
2282
  publicProtocol
2031
- ? `For Red public Markdown packages, include ${path.relative(absoluteRoot, publicProtocol.agentAckPath)} and ${path.relative(absoluteRoot, publicProtocol.disclosurePath)} when accepted by the platform; keep ${WORK_DIR}/ private.`
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.`
2032
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.`,
2033
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.',
2034
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.',
@@ -2092,7 +2344,7 @@ async function main() {
2092
2344
  `Platform profiles: ${result.profilesPath}`,
2093
2345
  ...result.packagePlanPaths.map((item) => `Package plan (${item.hub}): ${item.planPath}`),
2094
2346
  result.skillMdPath ? `Updated: ${result.skillMdPath}` : null,
2095
- result.publicProtocol ? `Public Red protocol: ${result.publicProtocol.agentAckPath}, ${result.publicProtocol.disclosurePath}` : null,
2347
+ result.publicProtocol ? `Public Red protocol: ${result.publicProtocol.runtimePath}, ${result.publicProtocol.agentAckPath}, ${result.publicProtocol.disclosurePath}` : null,
2096
2348
  `Disclosure: ${result.disclosurePath}`,
2097
2349
  result.snippetPath ? `Runtime snippet: ${result.snippetPath}` : null,
2098
2350
  ...result.warnings.map((warning) => `Warning: ${warning}`),
@@ -2122,6 +2374,7 @@ async function main() {
2122
2374
  ...result.promptPaths.map((item) => `Wrote ${item.promptPath}`),
2123
2375
  `Wrote ${result.profilesPath}`,
2124
2376
  ...result.packagePlanPaths.map((item) => `Wrote ${item.planPath}`),
2377
+ result.publicProtocol ? `Wrote ${result.publicProtocol.runtimePath}` : null,
2125
2378
  result.publicProtocol ? `Wrote ${result.publicProtocol.agentAckPath}` : null,
2126
2379
  result.publicProtocol ? `Wrote ${result.publicProtocol.disclosurePath}` : null,
2127
2380
  `Wrote ${result.disclosurePath}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiashe/skill",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "xiashe-skill": "bin/xiashe-skill.mjs"