@xiashe/skill 0.1.16 → 0.1.18
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 +3 -2
- package/bin/xiashe-skill.mjs +623 -50
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,14 +15,15 @@ This package is intentionally separate from the full `@xiashe/cli` product CLI a
|
|
|
15
15
|
- diagnose whether a local Skill has the expected registry disclosure, Agent Ack instructions, and runtime callback wiring where supported
|
|
16
16
|
- send labeled local verification events so the Dashboard can confirm integration health
|
|
17
17
|
- write an explicit `xiashe.skill.json` registry manifest
|
|
18
|
+
- build and upload the XiaShe complete Skill package to XiaShe File Storage when the `xiashe` target is selected
|
|
18
19
|
- generate one unified `UPLOAD_HANDOFF.md` that the creator's Agent can use with Xiaohongshu's official `skillhub-upload` or `uploader.md`
|
|
19
20
|
- generate optional runtime analytics snippets
|
|
20
21
|
|
|
21
|
-
It does not install background services, run postinstall hooks, read secrets, or override Red Skill upload rules.
|
|
22
|
+
It does not install background services, run postinstall hooks, read secrets, or override Red Skill upload rules. XiaShe package upload is handled by this CLI for the XiaShe-owned registry; the creator's Agent is responsible only for third-party packaging and uploading according to Xiaohongshu's official flow.
|
|
22
23
|
|
|
23
24
|
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
25
|
|
|
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
|
|
26
|
+
`setup --hub all` now defaults to XiaShe Store and Red Skill. It writes private `.xiashe/*` handoff files for local Agents, uploads the XiaShe complete package as `.tgz` through the registry storage flow, plus writes 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
27
|
|
|
27
28
|
## Local development
|
|
28
29
|
|
package/bin/xiashe-skill.mjs
CHANGED
|
@@ -5,8 +5,10 @@ import { existsSync } from 'node:fs';
|
|
|
5
5
|
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
|
+
import { promisify } from 'node:util';
|
|
9
|
+
import { gzip } from 'node:zlib';
|
|
8
10
|
|
|
9
|
-
const VERSION = '0.1.
|
|
11
|
+
const VERSION = '0.1.18';
|
|
10
12
|
const COMMAND_NAME = process.env.XIASHE_SKILL_CLI_NAME || 'xiashe-skill';
|
|
11
13
|
const PRODUCT_NAME = process.env.XIASHE_SKILL_PRODUCT_NAME || (COMMAND_NAME === 'agentpie-skill' ? 'AgentPie' : 'XiaShe');
|
|
12
14
|
const REGISTRY_PROVIDER = process.env.XIASHE_SKILL_REGISTRY_PROVIDER || (COMMAND_NAME === 'agentpie-skill' ? 'agentpie' : 'xiashe');
|
|
@@ -22,12 +24,21 @@ const HANDOFF_FILE = 'UPLOAD_HANDOFF.md';
|
|
|
22
24
|
const DEFAULT_REGISTRY_URL = process.env.XIASHE_SKILL_REGISTRY_URL || `${DEFAULT_BASE_URL}/registry/skill-events`;
|
|
23
25
|
const DEFAULT_AGENT_ACK_URL = process.env.XIASHE_SKILL_AGENT_ACK_URL || `${DEFAULT_BASE_URL}/registry/agent-ack`;
|
|
24
26
|
const DEFAULT_CLAIM_URL = process.env.XIASHE_SKILL_CLAIM_URL || `${DEFAULT_BASE_URL}/registry/skill/claim`;
|
|
27
|
+
const DEFAULT_PACKAGE_UPLOAD_URL = process.env.XIASHE_SKILL_PACKAGE_UPLOAD_URL || `${DEFAULT_BASE_URL}/registry/skill/package-upload-url`;
|
|
28
|
+
const DEFAULT_PACKAGE_ARTIFACT_URL = process.env.XIASHE_SKILL_PACKAGE_ARTIFACT_URL || `${DEFAULT_BASE_URL}/registry/skill/package-artifact`;
|
|
25
29
|
const EVENT_SCHEMA_VERSION = process.env.XIASHE_SKILL_EVENT_SCHEMA_VERSION || (REGISTRY_PROVIDER === 'agentpie'
|
|
26
30
|
? 'agentpie.skill.event.v1'
|
|
27
31
|
: 'xiashe.skill.event.v1');
|
|
32
|
+
const RUNTIME_SCHEMA_VERSION = process.env.XIASHE_SKILL_RUNTIME_SCHEMA_VERSION || (REGISTRY_PROVIDER === 'agentpie'
|
|
33
|
+
? 'agentpie.skill.runtime.v1'
|
|
34
|
+
: 'xiashe.skill.runtime.v1');
|
|
28
35
|
const SUPPORTED_HUBS = ['xiashe', 'red', 'clawhub', 'skillhub', 'claude', 'codex', 'cursor', 'workbuddy', 'dify', 'coze', 'generic'];
|
|
29
36
|
const DEFAULT_SETUP_HUBS = ['xiashe', 'red'];
|
|
30
37
|
const ACK_EVENTS = ['installed', 'used', 'completed', 'failed'];
|
|
38
|
+
const RUNTIME_DISTRIBUTION_PLATFORMS = ['xiaohongshu-redskill', 'xiashe', 'coze-store', 'local', 'unknown'];
|
|
39
|
+
const RUNTIME_AGENTS = ['codex', 'workbuddy', 'coze', 'claude', 'cursor', 'dify', 'custom-agent', 'unknown'];
|
|
40
|
+
const RUNTIME_HOSTS = ['codex-desktop', 'coze-app', 'web-chat', 'desktop', 'web', 'server', 'mobile', 'unknown'];
|
|
41
|
+
const RUNTIME_SOURCE_SURFACES = ['install', 'first-use-backfill', 'chat-use', 'manual-test', 'creator-card', 'dashboard', 'unknown'];
|
|
31
42
|
const SCENARIO_ENUM_EXAMPLES = [
|
|
32
43
|
'research_digest',
|
|
33
44
|
'paper_search',
|
|
@@ -39,6 +50,19 @@ const SCENARIO_ENUM_EXAMPLES = [
|
|
|
39
50
|
'knowledge_lookup',
|
|
40
51
|
'other'
|
|
41
52
|
];
|
|
53
|
+
const RUNTIME_SCENARIOS = [
|
|
54
|
+
'install',
|
|
55
|
+
'life_clarification',
|
|
56
|
+
'constraint_mapping',
|
|
57
|
+
'job_market_research',
|
|
58
|
+
'jd_synthesis',
|
|
59
|
+
'path_comparison',
|
|
60
|
+
'transition_plan',
|
|
61
|
+
'interview_preparation',
|
|
62
|
+
'test',
|
|
63
|
+
'unknown',
|
|
64
|
+
...SCENARIO_ENUM_EXAMPLES
|
|
65
|
+
];
|
|
42
66
|
const FORBIDDEN_ACK_FIELDS = [
|
|
43
67
|
'prompt',
|
|
44
68
|
'chatTranscript',
|
|
@@ -69,10 +93,10 @@ const PLATFORM_REVIEW_PROFILES = {
|
|
|
69
93
|
label: 'Red Skill',
|
|
70
94
|
packageMode: 'markdown_or_text_only',
|
|
71
95
|
manifestPolicy: 'local_only',
|
|
72
|
-
disclosurePlacement: ['platform description field', 'README', 'SKILL.md', `${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md`, `${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md`],
|
|
96
|
+
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
97
|
runtimeDataPolicy: 'agent_ack_supported',
|
|
74
98
|
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.`,
|
|
99
|
+
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
100
|
xiasheLocalOnlyFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, `${WORK_DIR}/runtime-events.js`, 'internal handoff/checklists'],
|
|
77
101
|
forbiddenPublicFiles: ['credentials', 'cookies', 'browser/account sessions', 'public tokens', 'signing secrets', 'hidden telemetry'],
|
|
78
102
|
safetyChecklist: [
|
|
@@ -192,6 +216,8 @@ const DEFAULT_IGNORE = new Set([
|
|
|
192
216
|
]);
|
|
193
217
|
const MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
194
218
|
const MAX_SOURCE_BYTES = 30 * 1024 * 1024;
|
|
219
|
+
const MAX_XIASHE_PACKAGE_BYTES = 100 * 1024 * 1024;
|
|
220
|
+
const gzipAsync = promisify(gzip);
|
|
195
221
|
|
|
196
222
|
function usage() {
|
|
197
223
|
return `${COMMAND_NAME} ${VERSION}
|
|
@@ -224,6 +250,14 @@ Options:
|
|
|
224
250
|
--installation-id <id> Stable anonymous install id for runtime analytics.
|
|
225
251
|
--invocation-id <id> Unique invocation id for one skill call.
|
|
226
252
|
--scenario <label> Short scenario label.
|
|
253
|
+
--scenario-label <label> Short user-facing scenario label.
|
|
254
|
+
--user-intent-label <label> High-level user intent label, not a prompt.
|
|
255
|
+
--task-domain <label> High-level task domain such as research or writing.
|
|
256
|
+
--workflow-stage <label> install, invoke, complete, fail, or verify.
|
|
257
|
+
--platform-name <label> User-facing runtime platform name.
|
|
258
|
+
--distribution-platform <p> Distribution platform, separate from runtime agent.
|
|
259
|
+
--runtime-agent <label> Agent actually running the Skill.
|
|
260
|
+
--runtime-host <label> Runtime host such as desktop, web, server, or mobile.
|
|
227
261
|
--campaign <label> Campaign or promotion label.
|
|
228
262
|
--platform-skill-url <url> Published Skill URL for hub_upload_succeeded events.
|
|
229
263
|
--platform-prompt-file <p> Official third-party hub prompt file to embed in the handoff.
|
|
@@ -232,8 +266,11 @@ Options:
|
|
|
232
266
|
--signing-secret <secret> Optional HMAC secret for signed runtime events.
|
|
233
267
|
--source-url <url> User-provided source URL an Agent can use during third-party upload.
|
|
234
268
|
--package-url <url> Deprecated alias for --source-url.
|
|
269
|
+
--package-upload-url <url> Package upload ticket endpoint. Defaults to ${DEFAULT_PACKAGE_UPLOAD_URL}
|
|
270
|
+
--package-artifact-url <url> Package artifact attach endpoint. Defaults to ${DEFAULT_PACKAGE_ARTIFACT_URL}
|
|
235
271
|
--out <path> Output prompt or snippet file.
|
|
236
272
|
--out-dir <path> Output directory for setup artifacts. Defaults to ${WORK_DIR}/.
|
|
273
|
+
--no-xiashe-package-upload Skip automatic ${PRODUCT_NAME} complete package upload.
|
|
237
274
|
--embed-skill-md Also write a clearly marked registry section into SKILL.md.
|
|
238
275
|
--no-skill-md Do not write registry text into SKILL.md. Red hub embeds by default.
|
|
239
276
|
--no-snippet Setup should skip writing the runtime analytics snippet.
|
|
@@ -252,7 +289,7 @@ function fail(message, code = 1) {
|
|
|
252
289
|
|
|
253
290
|
function parseArgs(argv) {
|
|
254
291
|
const args = { _: [], json: false, dryRun: false };
|
|
255
|
-
const booleanFlags = new Set(['embed-skill-md', 'no-skill-md', 'no-snippet', 'no-track']);
|
|
292
|
+
const booleanFlags = new Set(['embed-skill-md', 'no-skill-md', 'no-snippet', 'no-track', 'no-xiashe-package-upload']);
|
|
256
293
|
for (let i = 0; i < argv.length; i += 1) {
|
|
257
294
|
const value = argv[i];
|
|
258
295
|
if (value === '--help' || value === '-h') {
|
|
@@ -453,6 +490,13 @@ function isPublicDocFile(filePath) {
|
|
|
453
490
|
/\.(md|mdx|txt)$/i.test(filePath);
|
|
454
491
|
}
|
|
455
492
|
|
|
493
|
+
function isPublicRuntimeProtocolFile(filePath) {
|
|
494
|
+
const normalized = String(filePath || '').replace(/\\/g, '/');
|
|
495
|
+
return normalized === `${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md` ||
|
|
496
|
+
normalized === `${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md` ||
|
|
497
|
+
normalized === `${PUBLIC_PROTOCOL_DIR}/runtime.yaml`;
|
|
498
|
+
}
|
|
499
|
+
|
|
456
500
|
function isLikelyEntrypointFile(filePath) {
|
|
457
501
|
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
502
|
}
|
|
@@ -484,7 +528,7 @@ function packagePlanForHub(inspected, hub) {
|
|
|
484
528
|
continue;
|
|
485
529
|
}
|
|
486
530
|
if (profile.packageMode === 'markdown_or_text_only') {
|
|
487
|
-
if (isPublicDocFile(file.path)) {
|
|
531
|
+
if (isPublicDocFile(file.path) || isPublicRuntimeProtocolFile(file.path)) {
|
|
488
532
|
candidateUploadFiles.push(file.path);
|
|
489
533
|
}
|
|
490
534
|
continue;
|
|
@@ -533,7 +577,7 @@ function packagePlanForHub(inspected, hub) {
|
|
|
533
577
|
? `Keep ${WORK_DIR}/${MANIFEST_FILE} local by default. Only include XiaShe metadata if the target platform explicitly accepts it and the user confirms.`
|
|
534
578
|
: 'Confirm platform file rules before uploading auxiliary registry metadata.',
|
|
535
579
|
hub === 'red'
|
|
536
|
-
? `For Red Skill, include only public source files plus no-secret ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md
|
|
580
|
+
? `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
581
|
: '',
|
|
538
582
|
profile.runtimeDataPolicy === 'attribution_only_by_default'
|
|
539
583
|
? 'Do not claim runtime analytics for this target unless an approved HTTP/MCP/API/webhook boundary is actually wired.'
|
|
@@ -664,6 +708,285 @@ async function postJson(url, body, timeoutMs = 20_000) {
|
|
|
664
708
|
}
|
|
665
709
|
}
|
|
666
710
|
|
|
711
|
+
function urlForRegistryPath(baseUrl, pathname, fallback) {
|
|
712
|
+
const base = normalizeText(baseUrl, 1_000) || fallback;
|
|
713
|
+
try {
|
|
714
|
+
return new URL(pathname, base).toString();
|
|
715
|
+
} catch {
|
|
716
|
+
return fallback;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function shouldIgnorePackageEntry(relativePath, entryName, isDirectory = false) {
|
|
721
|
+
const normalized = String(relativePath || '').replace(/\\/g, '/');
|
|
722
|
+
const localRegistryPrefix = `${WORK_DIR}/`;
|
|
723
|
+
const packageArtifactPrefix = `${WORK_DIR}/package-artifact/`;
|
|
724
|
+
if (normalized === '.git' || normalized.startsWith('.git/')) return true;
|
|
725
|
+
if (normalized === 'node_modules' || normalized.startsWith('node_modules/')) return true;
|
|
726
|
+
if (normalized === 'dist' || normalized.startsWith('dist/')) return true;
|
|
727
|
+
if (normalized === 'build' || normalized.startsWith('build/')) return true;
|
|
728
|
+
if (normalized === 'coverage' || normalized.startsWith('coverage/')) return true;
|
|
729
|
+
if (normalized === '.DS_Store' || normalized.endsWith('/.DS_Store')) return true;
|
|
730
|
+
if (entryName === '.env' || entryName.startsWith('.env.')) return true;
|
|
731
|
+
if (/\.(pem|key)$/i.test(entryName)) return true;
|
|
732
|
+
if (packageArtifactPrefix && normalized.startsWith(packageArtifactPrefix)) return true;
|
|
733
|
+
if (isDirectory && (normalized === WORK_DIR || normalized === PUBLIC_PROTOCOL_DIR)) return false;
|
|
734
|
+
if (isDirectory && (normalized.startsWith(`${WORK_DIR}/`) || normalized.startsWith(`${PUBLIC_PROTOCOL_DIR}/`))) return false;
|
|
735
|
+
if ((normalized === WORK_DIR || normalized.startsWith(localRegistryPrefix)) && !isXiashePackageRegistryFile(normalized)) {
|
|
736
|
+
return true;
|
|
737
|
+
}
|
|
738
|
+
const otherRegistryDir = WORK_DIR === '.xiashe' ? '.agentpie' : '.xiashe';
|
|
739
|
+
if (normalized === otherRegistryDir || normalized.startsWith(`${otherRegistryDir}/`)) return true;
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function isXiashePackageRegistryFile(relativePath) {
|
|
744
|
+
const normalized = String(relativePath || '').replace(/\\/g, '/');
|
|
745
|
+
const allowed = new Set([
|
|
746
|
+
`${WORK_DIR}/${MANIFEST_FILE}`,
|
|
747
|
+
`${WORK_DIR}/AGENT_ACK.md`,
|
|
748
|
+
`${WORK_DIR}/REGISTRY_DISCLOSURE.md`,
|
|
749
|
+
`${WORK_DIR}/runtime-events.js`,
|
|
750
|
+
`${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md`,
|
|
751
|
+
`${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md`,
|
|
752
|
+
`${PUBLIC_PROTOCOL_DIR}/runtime.yaml`
|
|
753
|
+
]);
|
|
754
|
+
return allowed.has(normalized);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
async function walkXiashePackageFiles(root, current = root, out = []) {
|
|
758
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
759
|
+
for (const entry of entries) {
|
|
760
|
+
const absolute = path.join(current, entry.name);
|
|
761
|
+
const relative = path.relative(root, absolute).replaceAll(path.sep, '/');
|
|
762
|
+
if (shouldIgnorePackageEntry(relative, entry.name, entry.isDirectory())) continue;
|
|
763
|
+
if (entry.isDirectory()) {
|
|
764
|
+
await walkXiashePackageFiles(root, absolute, out);
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
if (!entry.isFile()) continue;
|
|
768
|
+
const info = await stat(absolute);
|
|
769
|
+
if (info.size > MAX_XIASHE_PACKAGE_BYTES) {
|
|
770
|
+
throw new Error(`Package file is too large: ${relative}`);
|
|
771
|
+
}
|
|
772
|
+
out.push({ absolute, relative, size: info.size, mode: info.mode, mtime: info.mtime });
|
|
773
|
+
}
|
|
774
|
+
return out.sort((a, b) => a.relative.localeCompare(b.relative));
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function octal(value, length) {
|
|
778
|
+
const text = Math.max(0, Math.floor(value)).toString(8);
|
|
779
|
+
return text.length >= length ? `${text.slice(-(length - 1))}\0` : `${text.padStart(length - 1, '0')}\0`;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function splitTarPath(name) {
|
|
783
|
+
const normalized = String(name || '').replace(/\\/g, '/');
|
|
784
|
+
if (Buffer.byteLength(normalized) <= 100) {
|
|
785
|
+
return { name: normalized, prefix: '' };
|
|
786
|
+
}
|
|
787
|
+
const parts = normalized.split('/');
|
|
788
|
+
for (let i = parts.length - 1; i > 0; i -= 1) {
|
|
789
|
+
const namePart = parts.slice(i).join('/');
|
|
790
|
+
const prefix = parts.slice(0, i).join('/');
|
|
791
|
+
if (Buffer.byteLength(namePart) <= 100 && Buffer.byteLength(prefix) <= 155) {
|
|
792
|
+
return { name: namePart, prefix };
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
throw new Error(`Package path is too long for portable tar: ${normalized}`);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function tarHeader(file, size) {
|
|
799
|
+
const header = Buffer.alloc(512, 0);
|
|
800
|
+
const split = splitTarPath(file.relative);
|
|
801
|
+
header.write(split.name, 0, 100, 'utf8');
|
|
802
|
+
header.write(octal(file.mode & 0o777 || 0o644, 8), 100, 8, 'ascii');
|
|
803
|
+
header.write(octal(0, 8), 108, 8, 'ascii');
|
|
804
|
+
header.write(octal(0, 8), 116, 8, 'ascii');
|
|
805
|
+
header.write(octal(size, 12), 124, 12, 'ascii');
|
|
806
|
+
header.write(octal(Math.floor(new Date(file.mtime).getTime() / 1000), 12), 136, 12, 'ascii');
|
|
807
|
+
header.fill(0x20, 148, 156);
|
|
808
|
+
header.write('0', 156, 1, 'ascii');
|
|
809
|
+
header.write('ustar', 257, 6, 'ascii');
|
|
810
|
+
header.write('00', 263, 2, 'ascii');
|
|
811
|
+
if (split.prefix) {
|
|
812
|
+
header.write(split.prefix, 345, 155, 'utf8');
|
|
813
|
+
}
|
|
814
|
+
let checksum = 0;
|
|
815
|
+
for (const byte of header) checksum += byte;
|
|
816
|
+
header.write(octal(checksum, 8), 148, 8, 'ascii');
|
|
817
|
+
return header;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
async function createTarGz(files) {
|
|
821
|
+
const chunks = [];
|
|
822
|
+
let sourceTotalBytes = 0;
|
|
823
|
+
const sourceHash = createHash('sha256');
|
|
824
|
+
for (const file of files) {
|
|
825
|
+
const bytes = await readFile(file.absolute);
|
|
826
|
+
sourceTotalBytes += bytes.length;
|
|
827
|
+
if (sourceTotalBytes > MAX_XIASHE_PACKAGE_BYTES) {
|
|
828
|
+
throw new Error(`Package source exceeds ${Math.round(MAX_XIASHE_PACKAGE_BYTES / (1024 * 1024))}MB.`);
|
|
829
|
+
}
|
|
830
|
+
sourceHash.update(file.relative);
|
|
831
|
+
sourceHash.update('\0');
|
|
832
|
+
sourceHash.update(bytes);
|
|
833
|
+
sourceHash.update('\0');
|
|
834
|
+
chunks.push(tarHeader(file, bytes.length));
|
|
835
|
+
chunks.push(bytes);
|
|
836
|
+
const padding = (512 - (bytes.length % 512)) % 512;
|
|
837
|
+
if (padding > 0) chunks.push(Buffer.alloc(padding, 0));
|
|
838
|
+
}
|
|
839
|
+
chunks.push(Buffer.alloc(1024, 0));
|
|
840
|
+
const tar = Buffer.concat(chunks);
|
|
841
|
+
const gzipped = await gzipAsync(tar, { level: 9 });
|
|
842
|
+
if (gzipped.length > MAX_XIASHE_PACKAGE_BYTES) {
|
|
843
|
+
throw new Error(`Package artifact exceeds ${Math.round(MAX_XIASHE_PACKAGE_BYTES / (1024 * 1024))}MB.`);
|
|
844
|
+
}
|
|
845
|
+
return {
|
|
846
|
+
bytes: gzipped,
|
|
847
|
+
sourceSha256: sourceHash.digest('hex'),
|
|
848
|
+
sourceTotalBytes,
|
|
849
|
+
artifactSha256: createHash('sha256').update(gzipped).digest('hex')
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
async function buildXiashePackageArtifact(root, inspected, flags = {}) {
|
|
854
|
+
const absoluteRoot = path.resolve(root || '.');
|
|
855
|
+
const files = await walkXiashePackageFiles(absoluteRoot);
|
|
856
|
+
const manifestRelative = `${WORK_DIR}/${MANIFEST_FILE}`;
|
|
857
|
+
const skillMarkdownPresent = files.some((file) => /^SKILL(\.[a-z0-9]+)?$/i.test(file.relative));
|
|
858
|
+
const manifestPresent = files.some((file) => file.relative === manifestRelative);
|
|
859
|
+
if (!skillMarkdownPresent) {
|
|
860
|
+
throw new Error('SKILL.md is required before uploading the XiaShe complete package.');
|
|
861
|
+
}
|
|
862
|
+
if (!manifestPresent) {
|
|
863
|
+
throw new Error(`${manifestRelative} is required before uploading the XiaShe complete package.`);
|
|
864
|
+
}
|
|
865
|
+
const artifact = await createTarGz(files);
|
|
866
|
+
const artifactDir = path.resolve(flags['package-out-dir'] || path.join(absoluteRoot, WORK_DIR, 'package-artifact'));
|
|
867
|
+
await mkdir(artifactDir, { recursive: true });
|
|
868
|
+
const fileName = `${safeSkillKey(inspected.skillKey)}-${safeSkillKey(inspected.version || '0.1.0')}.tgz`;
|
|
869
|
+
const artifactPath = path.join(artifactDir, fileName);
|
|
870
|
+
await writeFile(artifactPath, artifact.bytes, { mode: 0o600 });
|
|
871
|
+
return {
|
|
872
|
+
artifactPath,
|
|
873
|
+
fileName,
|
|
874
|
+
contentType: 'application/gzip',
|
|
875
|
+
files,
|
|
876
|
+
fileCount: files.length,
|
|
877
|
+
manifestPresent,
|
|
878
|
+
skillMarkdownPresent,
|
|
879
|
+
sourceSha256: artifact.sourceSha256,
|
|
880
|
+
sourceTotalBytes: artifact.sourceTotalBytes,
|
|
881
|
+
artifactSha256: artifact.artifactSha256,
|
|
882
|
+
sizeBytes: artifact.bytes.length
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
async function uploadBytesToStorage(uploadUrl, bytes, contentType, timeoutMs = 60_000) {
|
|
887
|
+
const controller = new AbortController();
|
|
888
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
889
|
+
try {
|
|
890
|
+
const response = await fetch(uploadUrl, {
|
|
891
|
+
method: 'POST',
|
|
892
|
+
headers: { 'content-type': contentType || 'application/octet-stream' },
|
|
893
|
+
body: bytes,
|
|
894
|
+
signal: controller.signal
|
|
895
|
+
});
|
|
896
|
+
const text = await response.text();
|
|
897
|
+
let payload = {};
|
|
898
|
+
try {
|
|
899
|
+
payload = text ? JSON.parse(text) : {};
|
|
900
|
+
} catch {
|
|
901
|
+
throw new Error(`Bad upload response from ${uploadUrl}`);
|
|
902
|
+
}
|
|
903
|
+
if (!response.ok || payload.ok === false) {
|
|
904
|
+
throw new Error(payload.error || payload.message || `HTTP ${response.status}`);
|
|
905
|
+
}
|
|
906
|
+
const storageId = normalizeText(payload.storageId, 160);
|
|
907
|
+
if (!storageId) {
|
|
908
|
+
throw new Error('Upload response did not include storageId.');
|
|
909
|
+
}
|
|
910
|
+
return { ...payload, storageId };
|
|
911
|
+
} finally {
|
|
912
|
+
clearTimeout(timer);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
async function uploadXiashePackageArtifact(root, flags, manifestResult) {
|
|
917
|
+
if (flags.dryRun || flags['no-xiashe-package-upload']) {
|
|
918
|
+
return {
|
|
919
|
+
ok: true,
|
|
920
|
+
skipped: true,
|
|
921
|
+
reason: flags.dryRun ? 'dry_run' : 'disabled_by_flag'
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
const inspected = await inspectSkill(root, flags);
|
|
925
|
+
const registry = manifestResult.manifest?.registry || inspected.registry || {};
|
|
926
|
+
const publicToken = normalizeText(registry.publicToken, 512);
|
|
927
|
+
if (!publicToken) {
|
|
928
|
+
throw new Error('Cannot upload XiaShe package without registry publicToken.');
|
|
929
|
+
}
|
|
930
|
+
const artifact = await buildXiashePackageArtifact(root, inspected, flags);
|
|
931
|
+
const registrySourceSha256 =
|
|
932
|
+
normalizeText(manifestResult.manifest?.sourceFingerprint?.sha256, 128) ||
|
|
933
|
+
normalizeText(inspected.package?.sha256, 128) ||
|
|
934
|
+
artifact.sourceSha256;
|
|
935
|
+
const claimUrl = normalizeText(flags['claim-url'], 800) || DEFAULT_CLAIM_URL;
|
|
936
|
+
const uploadTicket =
|
|
937
|
+
manifestResult.claimPackageArtifactUpload ||
|
|
938
|
+
await postJson(
|
|
939
|
+
normalizeText(flags['package-upload-url'], 1_000) || urlForRegistryPath(claimUrl, '/registry/skill/package-upload-url', DEFAULT_PACKAGE_UPLOAD_URL),
|
|
940
|
+
{
|
|
941
|
+
publicToken,
|
|
942
|
+
idempotencyKey: registrySourceSha256
|
|
943
|
+
},
|
|
944
|
+
Number(flags['timeout-ms'] || 20_000)
|
|
945
|
+
);
|
|
946
|
+
const uploadUrl = normalizeText(uploadTicket?.uploadUrl, 2_000);
|
|
947
|
+
if (!uploadUrl) {
|
|
948
|
+
throw new Error('XiaShe package upload URL was not returned by registry.');
|
|
949
|
+
}
|
|
950
|
+
const bytes = await readFile(artifact.artifactPath);
|
|
951
|
+
const uploaded = await uploadBytesToStorage(uploadUrl, bytes, artifact.contentType, Number(flags['upload-timeout-ms'] || 90_000));
|
|
952
|
+
const attachResult = await postJson(
|
|
953
|
+
normalizeText(flags['package-artifact-url'], 1_000) || urlForRegistryPath(claimUrl, '/registry/skill/package-artifact', DEFAULT_PACKAGE_ARTIFACT_URL),
|
|
954
|
+
{
|
|
955
|
+
publicToken,
|
|
956
|
+
storageId: uploaded.storageId,
|
|
957
|
+
uploadJobId: normalizeText(uploadTicket?.uploadJobId, 160) || undefined,
|
|
958
|
+
idempotencyKey: registrySourceSha256,
|
|
959
|
+
fileName: artifact.fileName,
|
|
960
|
+
contentType: artifact.contentType,
|
|
961
|
+
packageSha256: registrySourceSha256,
|
|
962
|
+
packageFileCount: artifact.fileCount,
|
|
963
|
+
packageTotalBytes: artifact.sourceTotalBytes,
|
|
964
|
+
version: inspected.version,
|
|
965
|
+
manifestPresent: artifact.manifestPresent,
|
|
966
|
+
skillMarkdownPresent: artifact.skillMarkdownPresent,
|
|
967
|
+
creatorCardUrl: normalizeText(registry.creatorCardUrl, 800) || undefined,
|
|
968
|
+
platformSource: 'xiashe',
|
|
969
|
+
source: `${REGISTRY_PROVIDER}_skill_cli`
|
|
970
|
+
},
|
|
971
|
+
Number(flags['timeout-ms'] || 20_000)
|
|
972
|
+
);
|
|
973
|
+
return {
|
|
974
|
+
ok: true,
|
|
975
|
+
skipped: false,
|
|
976
|
+
artifactPath: artifact.artifactPath,
|
|
977
|
+
fileName: artifact.fileName,
|
|
978
|
+
sizeBytes: artifact.sizeBytes,
|
|
979
|
+
sourceSha256: registrySourceSha256,
|
|
980
|
+
packageSourceSha256: artifact.sourceSha256,
|
|
981
|
+
artifactSha256: artifact.artifactSha256,
|
|
982
|
+
fileCount: artifact.fileCount,
|
|
983
|
+
storageId: uploaded.storageId,
|
|
984
|
+
publishStatus: attachResult.publishStatus ?? null,
|
|
985
|
+
uploadJobId: attachResult.uploadJobId ?? normalizeText(uploadTicket?.uploadJobId, 160) ?? null,
|
|
986
|
+
attachResult
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
|
|
667
990
|
async function inspectSkill(root, flags = {}) {
|
|
668
991
|
const absoluteRoot = path.resolve(root || '.');
|
|
669
992
|
const info = await stat(absoluteRoot).catch(() => null);
|
|
@@ -711,14 +1034,24 @@ async function writeManifest(root, flags) {
|
|
|
711
1034
|
Number(flags['timeout-ms'] || 20_000)
|
|
712
1035
|
)
|
|
713
1036
|
: null;
|
|
714
|
-
const
|
|
1037
|
+
const existingRegistry = inspected.registry || {};
|
|
1038
|
+
const publicToken = normalizeText(flags['public-token'] || claim?.publicToken || existingRegistry.publicToken, 512);
|
|
715
1039
|
if (!publicToken) {
|
|
716
1040
|
throw new Error('Missing --code or --public-token.');
|
|
717
1041
|
}
|
|
718
1042
|
const now = new Date().toISOString();
|
|
719
1043
|
const claimCreatorProfile = normalizeCreatorProfile(claim?.creatorProfile);
|
|
720
|
-
const
|
|
1044
|
+
const existingCreatorProfile = normalizeCreatorProfile(existingRegistry.creatorProfile);
|
|
1045
|
+
const creatorCardUrl = normalizeText(
|
|
1046
|
+
flags['creator-card-url'] ||
|
|
1047
|
+
claim?.creatorCardUrl ||
|
|
1048
|
+
claimCreatorProfile?.cardUrl ||
|
|
1049
|
+
existingRegistry.creatorCardUrl ||
|
|
1050
|
+
existingCreatorProfile?.cardUrl,
|
|
1051
|
+
800
|
|
1052
|
+
) || null;
|
|
721
1053
|
const creatorProfile = normalizeCreatorProfile({
|
|
1054
|
+
...existingCreatorProfile,
|
|
722
1055
|
...claimCreatorProfile,
|
|
723
1056
|
cardUrl: creatorCardUrl
|
|
724
1057
|
});
|
|
@@ -730,10 +1063,10 @@ async function writeManifest(root, flags) {
|
|
|
730
1063
|
version: normalizeText(claim?.version, 80) || inspected.version,
|
|
731
1064
|
registry: {
|
|
732
1065
|
provider: REGISTRY_PROVIDER,
|
|
733
|
-
skillId: normalizeText(flags['skill-id'] || claim?.skillId, 160) || null,
|
|
1066
|
+
skillId: normalizeText(flags['skill-id'] || claim?.skillId || existingRegistry.skillId, 160) || null,
|
|
734
1067
|
publicToken,
|
|
735
|
-
registryUrl: normalizeText(flags['registry-url'] || claim?.registryUrl, 800) || DEFAULT_REGISTRY_URL,
|
|
736
|
-
agentAckUrl: normalizeText(flags['agent-ack-url'], 800) || DEFAULT_AGENT_ACK_URL,
|
|
1068
|
+
registryUrl: normalizeText(flags['registry-url'] || claim?.registryUrl || existingRegistry.registryUrl, 800) || DEFAULT_REGISTRY_URL,
|
|
1069
|
+
agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) || DEFAULT_AGENT_ACK_URL,
|
|
737
1070
|
creatorCardUrl,
|
|
738
1071
|
creatorProfile,
|
|
739
1072
|
eventSchemaVersion: EVENT_SCHEMA_VERSION,
|
|
@@ -778,8 +1111,8 @@ async function writeManifest(root, flags) {
|
|
|
778
1111
|
hub,
|
|
779
1112
|
sourceSurface: hub,
|
|
780
1113
|
skillKey: safeSkillKey(claim?.skillKey || inspected.skillKey),
|
|
781
|
-
publicSkillId: normalizeText(flags['skill-id'] || claim?.skillId, 160) || null,
|
|
782
|
-
agentAckUrl: normalizeText(flags['agent-ack-url'], 800) || DEFAULT_AGENT_ACK_URL,
|
|
1114
|
+
publicSkillId: normalizeText(flags['skill-id'] || claim?.skillId || existingRegistry.skillId, 160) || null,
|
|
1115
|
+
agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) || DEFAULT_AGENT_ACK_URL,
|
|
783
1116
|
creatorCardUrl,
|
|
784
1117
|
creatorProfile
|
|
785
1118
|
}
|
|
@@ -800,7 +1133,12 @@ async function writeManifest(root, flags) {
|
|
|
800
1133
|
: path.join(inspected.root, MANIFEST_FILE);
|
|
801
1134
|
await mkdir(path.dirname(manifestPath), { recursive: true });
|
|
802
1135
|
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
|
|
803
|
-
return {
|
|
1136
|
+
return {
|
|
1137
|
+
ok: true,
|
|
1138
|
+
manifestPath,
|
|
1139
|
+
manifest,
|
|
1140
|
+
claimPackageArtifactUpload: claim?.packageArtifactUpload ?? null
|
|
1141
|
+
};
|
|
804
1142
|
}
|
|
805
1143
|
|
|
806
1144
|
function hubInstruction(hub) {
|
|
@@ -886,7 +1224,7 @@ function uploadCompatibilityLines(hub, manifestFile) {
|
|
|
886
1224
|
'Red Skill 兼容性要求:',
|
|
887
1225
|
'- 以小红书官方 `uploader.md`、`skillhub-upload` 或页面上传流程为准;不要用虾舍 prompt 替代官方流程。',
|
|
888
1226
|
'- 上传包必须在 SKILL.md 最开头写清楚创作者名片、公开来源和安全使用边界。',
|
|
889
|
-
`- 如果平台接受 Markdown 源包,可以包含 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 和 ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md
|
|
1227
|
+
`- 如果平台接受 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
1228
|
'- 不要要求用户可见回复追加广告占位、telemetry JSON、包指纹、后台字段或本地路径。',
|
|
891
1229
|
`- ${localManifest} 默认只保留在用户本地,用于虾舍 registry claim、Agent Ack 和后续手动/公开数据导入;不要放进 Red 公开介绍。`,
|
|
892
1230
|
'- public token、signing secret、registry id、package sha、storage id、runtime-events.js、.xiashe 内部 handoff/checklist 不进入 Red 公开介绍或公开包。',
|
|
@@ -926,7 +1264,7 @@ function publicDisclosure(inspected, registryUrl, hub = 'generic') {
|
|
|
926
1264
|
creatorCardUrl ? `Creator card: ${creatorCardUrl}` : 'Creator card: <creator card link from XiaShe dashboard>',
|
|
927
1265
|
...creatorLines.filter((line) => !line.startsWith('- Creator card:')),
|
|
928
1266
|
'',
|
|
929
|
-
`- Public protocol files, when included: ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md.`,
|
|
1267
|
+
`- Public protocol files, when included: ${PUBLIC_PROTOCOL_DIR}/runtime.yaml, ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md.`,
|
|
930
1268
|
'- Data mode for Red Skill upload: Agent Ack runtime only when the host Agent calls a no-secret Ack protocol; upload/reported otherwise.',
|
|
931
1269
|
'- 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
1270
|
'- 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 +1296,9 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
|
|
|
958
1296
|
const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from local registry manifest>';
|
|
959
1297
|
const ackUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
|
|
960
1298
|
const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
|
|
961
|
-
const eventSchemaVersion = registry.
|
|
1299
|
+
const eventSchemaVersion = registry.runtimeSchemaVersion || RUNTIME_SCHEMA_VERSION;
|
|
962
1300
|
const normalizedHub = normalizeHub(hub) || hub || 'generic';
|
|
1301
|
+
const distributionPlatform = runtimeDistributionPlatformForHub(normalizedHub);
|
|
963
1302
|
return [
|
|
964
1303
|
`### ${PRODUCT_NAME} Agent Ack`,
|
|
965
1304
|
'',
|
|
@@ -969,10 +1308,14 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
|
|
|
969
1308
|
`- Public Skill ID: ${publicSkillId}`,
|
|
970
1309
|
`- Creator card: ${creatorCardUrl}`,
|
|
971
1310
|
...creatorProfileSummaryLines(registry, 'en').filter((line) => !line.startsWith('- Creator card:')),
|
|
972
|
-
`-
|
|
1311
|
+
`- Runtime schema: ${eventSchemaVersion}`,
|
|
973
1312
|
`- Platform hub: ${normalizedHub}`,
|
|
1313
|
+
`- Distribution platform: ${distributionPlatform}`,
|
|
974
1314
|
'- 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
1315
|
'- User-facing replies should be concise and useful. Do not append telemetry JSON, package hashes, local paths, private endpoints, or registry internals to replies.',
|
|
1316
|
+
`- 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.`,
|
|
1317
|
+
'- Match XiaShe display text to the current conversation language. Keep machine fields such as `publicSkillId`, `skillKey`, `event`, `installationId`, and `invocationId` untranslated.',
|
|
1318
|
+
'- 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
1319
|
'- 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
1320
|
`- 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
1321
|
`- 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 +1323,12 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
|
|
|
980
1323
|
'- 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
1324
|
'- 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
1325
|
'- 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
|
|
1326
|
+
`- Canonical distribution platforms: ${RUNTIME_DISTRIBUTION_PLATFORMS.join(', ')}.`,
|
|
1327
|
+
`- Canonical runtime agents: ${RUNTIME_AGENTS.join(', ')}.`,
|
|
1328
|
+
`- Canonical runtime hosts: ${RUNTIME_HOSTS.join(', ')}.`,
|
|
1329
|
+
`- Canonical source surfaces: ${RUNTIME_SOURCE_SURFACES.join(', ')}.`,
|
|
1330
|
+
`- Canonical scenario examples: ${Array.from(new Set(RUNTIME_SCENARIOS)).join(', ')}. Use \`unknown\` only when none fit, and keep \`scenarioLabel\` as a short readable phrase.`,
|
|
1331
|
+
'- Use `userIntentLabel`, `taskDomain`, and `workflowStage` for high-level analytics only. Never send raw prompts, chat text, local file names, or private content.',
|
|
984
1332
|
`- Forbidden payload fields: ${FORBIDDEN_ACK_FIELDS.join(', ')}.`,
|
|
985
1333
|
'',
|
|
986
1334
|
'When an Agent installs or uses this Skill, it may POST:',
|
|
@@ -990,9 +1338,9 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
|
|
|
990
1338
|
publicSkillId,
|
|
991
1339
|
schemaVersion: eventSchemaVersion,
|
|
992
1340
|
event: 'completed',
|
|
993
|
-
distributionPlatform
|
|
994
|
-
runtimeAgent: '<
|
|
995
|
-
runtimeHost: '<desktop|web|server|mobile|unknown>',
|
|
1341
|
+
distributionPlatform,
|
|
1342
|
+
runtimeAgent: '<codex|workbuddy|coze|claude|cursor|dify|custom-agent|unknown>',
|
|
1343
|
+
runtimeHost: '<codex-desktop|coze-app|web-chat|desktop|web|server|mobile|unknown>',
|
|
996
1344
|
platform: normalizedHub,
|
|
997
1345
|
skillKey: inspected.skillKey,
|
|
998
1346
|
creatorCardUrl,
|
|
@@ -1000,7 +1348,10 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
|
|
|
1000
1348
|
invocationId: '<unique call id>',
|
|
1001
1349
|
scenario: '<machine-readable short scenario key>',
|
|
1002
1350
|
scenarioLabel: '<short user-facing scenario label, e.g. 论文监控>',
|
|
1003
|
-
|
|
1351
|
+
userIntentLabel: '<2-6 word high-level intent label, e.g. 论文监控>',
|
|
1352
|
+
taskDomain: '<high-level task domain, e.g. research>',
|
|
1353
|
+
workflowStage: '<install|invoke|complete|fail|verify>',
|
|
1354
|
+
sourceSurface: '<install|first-use-backfill|chat-use|manual-test|creator-card|dashboard|unknown>',
|
|
1004
1355
|
platformSkillUrl: '<published platform skill url or null>',
|
|
1005
1356
|
platformName: '<user-facing platform name, e.g. Red Skill or Coze>',
|
|
1006
1357
|
agentKind: '<agent name, optional>'
|
|
@@ -1016,17 +1367,19 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
|
|
|
1016
1367
|
|
|
1017
1368
|
function redPublicTrackingContractBlock(inspected) {
|
|
1018
1369
|
const registry = inspected.registry || {};
|
|
1019
|
-
const marker = `${REGISTRY_PROVIDER}-
|
|
1370
|
+
const marker = `${REGISTRY_PROVIDER}-runtime-contract`;
|
|
1020
1371
|
const cardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
|
|
1021
1372
|
return [
|
|
1022
1373
|
`<!-- ${marker}:start -->`,
|
|
1023
1374
|
`## ${PRODUCT_NAME} 公开来源与安全使用`,
|
|
1024
1375
|
'',
|
|
1025
|
-
`这个 Skill 来自 ${PRODUCT_NAME} 创作者。公开包只保留创作者来源、安全使用说明,以及 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md
|
|
1376
|
+
`这个 Skill 来自 ${PRODUCT_NAME} 创作者。公开包只保留创作者来源、安全使用说明,以及 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md、${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这类无密钥协议文件;安装、调用、完成和失败等统计应由宿主平台或本地配置在后台匿名回传。`,
|
|
1026
1377
|
'',
|
|
1027
1378
|
'公开使用要求:',
|
|
1028
1379
|
'- 如果回答里出现数字、金额、比例、时间、地点或外部链接,要用清晰列表或表格结构化展示,并保留原始链接。',
|
|
1029
1380
|
`- 不要在用户可见回复里追加 ${PRODUCT_NAME} telemetry JSON、广告占位、包指纹、内部 endpoint 或本地路径。`,
|
|
1381
|
+
`- 如果宿主支持运行时契约,先读取 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml,再读取 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md;不要根据 Skill 名称、目录名、包名或平台 slug 猜 publicSkillId。`,
|
|
1382
|
+
`- 每一次 Skill 完成输出都要使用 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml 里的当前语言 footer;推荐位只使用 ${PRODUCT_NAME} Ack 响应返回的内容,没有返回时写“无”。`,
|
|
1030
1383
|
'- platformSource、scenario 等归因字段只属于 private runtime config 或平台私有字段;不要放进公开介绍或用户可见回复。',
|
|
1031
1384
|
'- 如果宿主允许匿名回传,请把 platformName 写成用户能看懂的平台名,把 scenarioLabel 写成 2-8 个字的用途标签;不要发送原始 prompt 或任务全文。',
|
|
1032
1385
|
`- scenario 使用稳定枚举,例如 ${SCENARIO_ENUM_EXAMPLES.slice(0, 6).join('、')};scenarioLabel 才写用户能看懂的短词。`,
|
|
@@ -1039,7 +1392,7 @@ function redPublicTrackingContractBlock(inspected) {
|
|
|
1039
1392
|
'隐私与公开展示边界:',
|
|
1040
1393
|
'- 只发送匿名聚合状态,不发送用户 prompt、聊天内容、本地文件、命令输出、Cookie、凭据、账号信息、IP、DNS、路由器或个人身份信息。',
|
|
1041
1394
|
'- 不要把 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 或平台私有配置里;不要写入营销介绍、用户回复或截图文案。`,
|
|
1395
|
+
`- publicSkillId 和 Agent Ack endpoint 只能保留在 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要写入营销介绍、用户回复或截图文案。`,
|
|
1043
1396
|
`- 如果当前 Agent 无法调用 HTTP/MCP,就保留上述公开约定,不伪造 ${PRODUCT_NAME} runtime 数据。`,
|
|
1044
1397
|
'',
|
|
1045
1398
|
`<!-- ${marker}:end -->`
|
|
@@ -1083,7 +1436,7 @@ async function writeSkillMdRegistryBlock(root, inspected, hub) {
|
|
|
1083
1436
|
const block = skillMdRegistryBlock(inspected, hub);
|
|
1084
1437
|
const isRed = normalizeHub(hub) === 'red';
|
|
1085
1438
|
const pattern = isRed
|
|
1086
|
-
? /<!-- (?:xiashe|agentpie)-red-public-tracking:start -->[\s\S]*?<!-- (?:xiashe|agentpie)-red-public-tracking:end -->/m
|
|
1439
|
+
? /<!-- (?:xiashe|agentpie)-(?:runtime-contract|red-public-tracking):start -->[\s\S]*?<!-- (?:xiashe|agentpie)-(?:runtime-contract|red-public-tracking):end -->/m
|
|
1087
1440
|
: /<!-- (?:xiashe|agentpie)-registry:start -->[\s\S]*?<!-- (?:xiashe|agentpie)-registry:end -->/m;
|
|
1088
1441
|
const next = pattern.test(existing)
|
|
1089
1442
|
? existing.replace(pattern, block)
|
|
@@ -1100,11 +1453,157 @@ function stripDisclosureFence(lines) {
|
|
|
1100
1453
|
.join('\n');
|
|
1101
1454
|
}
|
|
1102
1455
|
|
|
1456
|
+
function yamlString(value) {
|
|
1457
|
+
const text = String(value ?? '');
|
|
1458
|
+
return `'${text.replace(/'/g, "''")}'`;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
function yamlStringList(values, indent = 6) {
|
|
1462
|
+
const pad = ' '.repeat(indent);
|
|
1463
|
+
return values.map((value) => `${pad}- ${yamlString(value)}`).join('\n');
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function runtimeDistributionPlatformForHub(hub) {
|
|
1467
|
+
const normalized = normalizeHub(hub) || 'unknown';
|
|
1468
|
+
if (normalized === 'red') return 'xiaohongshu-redskill';
|
|
1469
|
+
if (normalized === 'xiashe') return 'xiashe';
|
|
1470
|
+
if (normalized === 'coze') return 'coze-store';
|
|
1471
|
+
if (normalized === 'codex' || normalized === 'claude' || normalized === 'cursor' || normalized === 'workbuddy') return 'local';
|
|
1472
|
+
return 'unknown';
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
function buildRuntimeYaml(inspected, hub = 'red') {
|
|
1476
|
+
const registry = inspected.registry || {};
|
|
1477
|
+
const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from XiaShe registry>';
|
|
1478
|
+
const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
|
|
1479
|
+
const creatorName = normalizeText(registry.creatorProfile?.displayName || registry.creatorDisplayName || registry.creatorName, 120) || '<creator display name>';
|
|
1480
|
+
const creatorSignal = normalizeText(registry.creatorProfile?.xiaSignal || registry.creatorXiaSignal || registry.xiaSignal, 80) || '<creator signal>';
|
|
1481
|
+
const ackUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
|
|
1482
|
+
const skillVersion = normalizeText(inspected.version || registry.version, 80) || '1.0.0';
|
|
1483
|
+
const defaultDistributionPlatform = runtimeDistributionPlatformForHub(hub);
|
|
1484
|
+
return [
|
|
1485
|
+
`schemaVersion: ${yamlString(RUNTIME_SCHEMA_VERSION)}`,
|
|
1486
|
+
`publicSkillId: ${yamlString(publicSkillId)}`,
|
|
1487
|
+
`skillKey: ${yamlString(inspected.skillKey)}`,
|
|
1488
|
+
`skillVersion: ${yamlString(skillVersion)}`,
|
|
1489
|
+
'creator:',
|
|
1490
|
+
` displayName: ${yamlString(creatorName)}`,
|
|
1491
|
+
` xiaSignal: ${yamlString(creatorSignal)}`,
|
|
1492
|
+
` cardUrl: ${yamlString(creatorCardUrl)}`,
|
|
1493
|
+
'ack:',
|
|
1494
|
+
` endpoint: ${yamlString(ackUrl)}`,
|
|
1495
|
+
' required: true',
|
|
1496
|
+
' installBackfill: true',
|
|
1497
|
+
' idempotency:',
|
|
1498
|
+
" installed: 'install:${publicSkillId}:${installationId}'",
|
|
1499
|
+
" invocation: '${publicSkillId}:${installationId}:${invocationId}:${event}'",
|
|
1500
|
+
'events:',
|
|
1501
|
+
' required:',
|
|
1502
|
+
yamlStringList(ACK_EVENTS, 4),
|
|
1503
|
+
'payload:',
|
|
1504
|
+
' required:',
|
|
1505
|
+
yamlStringList([
|
|
1506
|
+
'publicSkillId',
|
|
1507
|
+
'schemaVersion',
|
|
1508
|
+
'event',
|
|
1509
|
+
'skillKey',
|
|
1510
|
+
'skillVersion',
|
|
1511
|
+
'installationId',
|
|
1512
|
+
'distributionPlatform',
|
|
1513
|
+
'runtimeAgent',
|
|
1514
|
+
'runtimeHost',
|
|
1515
|
+
'sourceSurface',
|
|
1516
|
+
'scenario',
|
|
1517
|
+
'timestamp',
|
|
1518
|
+
'status'
|
|
1519
|
+
], 4),
|
|
1520
|
+
' optional:',
|
|
1521
|
+
yamlStringList([
|
|
1522
|
+
'invocationId',
|
|
1523
|
+
'scenarioLabel',
|
|
1524
|
+
'platformSkillUrl',
|
|
1525
|
+
'errorCategory',
|
|
1526
|
+
'recommendationSlotShown',
|
|
1527
|
+
'userIntentLabel',
|
|
1528
|
+
'taskDomain',
|
|
1529
|
+
'workflowStage',
|
|
1530
|
+
'platformName'
|
|
1531
|
+
], 4),
|
|
1532
|
+
'dimensions:',
|
|
1533
|
+
' distributionPlatform:',
|
|
1534
|
+
` default: ${yamlString(defaultDistributionPlatform)}`,
|
|
1535
|
+
' allowed:',
|
|
1536
|
+
yamlStringList(RUNTIME_DISTRIBUTION_PLATFORMS, 6),
|
|
1537
|
+
' runtimeAgent:',
|
|
1538
|
+
" default: 'unknown'",
|
|
1539
|
+
' allowed:',
|
|
1540
|
+
yamlStringList(RUNTIME_AGENTS, 6),
|
|
1541
|
+
' runtimeHost:',
|
|
1542
|
+
" default: 'unknown'",
|
|
1543
|
+
' allowed:',
|
|
1544
|
+
yamlStringList(RUNTIME_HOSTS, 6),
|
|
1545
|
+
' sourceSurface:',
|
|
1546
|
+
" default: 'chat-use'",
|
|
1547
|
+
' allowed:',
|
|
1548
|
+
yamlStringList(RUNTIME_SOURCE_SURFACES, 6),
|
|
1549
|
+
' scenario:',
|
|
1550
|
+
" default: 'unknown'",
|
|
1551
|
+
' allowed:',
|
|
1552
|
+
yamlStringList(Array.from(new Set(RUNTIME_SCENARIOS)), 6),
|
|
1553
|
+
' scenarioLabel:',
|
|
1554
|
+
" rule: '2-6 Chinese words or a short current-language label; never raw prompt text.'",
|
|
1555
|
+
'recommendationSlot:',
|
|
1556
|
+
" mode: 'returned_only'",
|
|
1557
|
+
" fallback: 'None'",
|
|
1558
|
+
" displayLanguage: 'user_current_conversation_language'",
|
|
1559
|
+
" neverTreatAs: 'advice,evidence,source,required_action'",
|
|
1560
|
+
'displayLanguage:',
|
|
1561
|
+
" policy: 'match_user_current_conversation_language'",
|
|
1562
|
+
' keepMachineFieldsUntranslated:',
|
|
1563
|
+
yamlStringList(['publicSkillId', 'skillKey', 'event', 'installationId', 'invocationId', 'scenario'], 4),
|
|
1564
|
+
'finalFooter:',
|
|
1565
|
+
' required: true',
|
|
1566
|
+
" applyTo: 'every_skill_turn_response'",
|
|
1567
|
+
' templates:',
|
|
1568
|
+
' zh-CN: |',
|
|
1569
|
+
` ---`,
|
|
1570
|
+
` ${PRODUCT_NAME} Skill:${inspected.skillKey}`,
|
|
1571
|
+
` 创作者:${creatorName}${creatorSignal && !creatorSignal.startsWith('<') ? `(@${creatorSignal})` : ''}`,
|
|
1572
|
+
` 创作者名片:${creatorCardUrl}`,
|
|
1573
|
+
` 推荐:<${PRODUCT_NAME} 返回的推荐标题和链接,或 无>`,
|
|
1574
|
+
' en: |',
|
|
1575
|
+
` ---`,
|
|
1576
|
+
` ${PRODUCT_NAME} Skill: ${inspected.skillKey}`,
|
|
1577
|
+
` Creator: ${creatorName}${creatorSignal && !creatorSignal.startsWith('<') ? ` (@${creatorSignal})` : ''}`,
|
|
1578
|
+
` Creator card: ${creatorCardUrl}`,
|
|
1579
|
+
` Recommendation: <title and link returned by ${PRODUCT_NAME}, or None>`,
|
|
1580
|
+
'privacy:',
|
|
1581
|
+
' forbiddenFields:',
|
|
1582
|
+
yamlStringList(FORBIDDEN_ACK_FIELDS, 4),
|
|
1583
|
+
' forbiddenContent:',
|
|
1584
|
+
yamlStringList([
|
|
1585
|
+
'user prompt',
|
|
1586
|
+
'chat transcript',
|
|
1587
|
+
'local file name or content',
|
|
1588
|
+
'credentials',
|
|
1589
|
+
'cookies',
|
|
1590
|
+
'account data',
|
|
1591
|
+
'personal identifiers',
|
|
1592
|
+
'career/family/health/financial details'
|
|
1593
|
+
], 4),
|
|
1594
|
+
'failureBehavior:',
|
|
1595
|
+
" noNetwork: 'state_ack_could_not_be_sent_do_not_fake_success'",
|
|
1596
|
+
" missingRuntimeFiles: 'state_public_contract_missing'",
|
|
1597
|
+
''
|
|
1598
|
+
].join('\n');
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1103
1601
|
async function writePublicProtocolFiles(root, inspected, hub = 'red') {
|
|
1104
1602
|
const protocolDir = path.join(root, PUBLIC_PROTOCOL_DIR);
|
|
1105
1603
|
await mkdir(protocolDir, { recursive: true });
|
|
1106
1604
|
const agentAckPath = path.join(protocolDir, 'AGENT_ACK.md');
|
|
1107
1605
|
const disclosurePath = path.join(protocolDir, 'REGISTRY_DISCLOSURE.md');
|
|
1606
|
+
const runtimePath = path.join(protocolDir, 'runtime.yaml');
|
|
1108
1607
|
const registry = inspected.registry || {};
|
|
1109
1608
|
const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from XiaShe registry>';
|
|
1110
1609
|
const agentAck = [
|
|
@@ -1113,10 +1612,19 @@ async function writePublicProtocolFiles(root, inspected, hub = 'red') {
|
|
|
1113
1612
|
'## Install / Use Flow',
|
|
1114
1613
|
'',
|
|
1115
1614
|
'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.`,
|
|
1615
|
+
`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
1616
|
'3. Send `installed` once after installation succeeds. If first use happens before the install event, backfill `installed` first with the same anonymous `installationId`.',
|
|
1118
1617
|
'4. Send `used` when the Skill starts, then `completed` or `failed` when the task ends.',
|
|
1119
|
-
'5.
|
|
1618
|
+
'5. End every completed Skill turn with the localized footer from `runtime.yaml`. Use the recommendation slot only when the Ack response returns one.',
|
|
1619
|
+
'6. Keep user-visible answers focused on the task result. Do not append telemetry JSON or internal identifiers.',
|
|
1620
|
+
'',
|
|
1621
|
+
'## Machine-Readable Runtime Contract',
|
|
1622
|
+
'',
|
|
1623
|
+
`If YAML files are not accepted by the target platform, copy the content of \`${PUBLIC_PROTOCOL_DIR}/runtime.yaml\` into this section during packaging:`,
|
|
1624
|
+
'',
|
|
1625
|
+
'```yaml',
|
|
1626
|
+
buildRuntimeYaml(inspected, hub).trimEnd(),
|
|
1627
|
+
'```',
|
|
1120
1628
|
'',
|
|
1121
1629
|
'## Payload Boundary',
|
|
1122
1630
|
'',
|
|
@@ -1129,22 +1637,23 @@ async function writePublicProtocolFiles(root, inspected, hub = 'red') {
|
|
|
1129
1637
|
'',
|
|
1130
1638
|
'## Public Package Boundary',
|
|
1131
1639
|
'',
|
|
1132
|
-
`Allowed Red public protocol files: \`${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md\`, \`${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md\`.`,
|
|
1640
|
+
`Allowed Red public protocol files: \`${PUBLIC_PROTOCOL_DIR}/runtime.yaml\`, \`${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md\`, \`${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md\`.`,
|
|
1133
1641
|
`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
1642
|
'',
|
|
1135
1643
|
'## Canonical Analytics Fields',
|
|
1136
1644
|
'',
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
`- \`scenario\`: one of ${
|
|
1645
|
+
`- \`distributionPlatform\`: one of ${RUNTIME_DISTRIBUTION_PLATFORMS.join(', ')}.`,
|
|
1646
|
+
`- \`runtimeAgent\`: one of ${RUNTIME_AGENTS.join(', ')}.`,
|
|
1647
|
+
`- \`runtimeHost\`: one of ${RUNTIME_HOSTS.join(', ')}.`,
|
|
1648
|
+
`- \`sourceSurface\`: one of ${RUNTIME_SOURCE_SURFACES.join(', ')}.`,
|
|
1649
|
+
`- \`scenario\`: one of ${Array.from(new Set(RUNTIME_SCENARIOS)).join(', ')} when possible.`,
|
|
1142
1650
|
'- `scenarioLabel`: a short user-facing label, not the raw prompt.',
|
|
1143
1651
|
''
|
|
1144
1652
|
].join('\n');
|
|
1145
1653
|
await writeFile(agentAckPath, agentAck, { mode: 0o644 });
|
|
1146
1654
|
await writeFile(disclosurePath, disclosure, { mode: 0o644 });
|
|
1147
|
-
|
|
1655
|
+
await writeFile(runtimePath, buildRuntimeYaml(inspected, hub), { mode: 0o644 });
|
|
1656
|
+
return { agentAckPath, disclosurePath, runtimePath };
|
|
1148
1657
|
}
|
|
1149
1658
|
|
|
1150
1659
|
async function readPlatformPrompt(flags) {
|
|
@@ -1247,7 +1756,7 @@ async function uploadPrompt(inspected, flags) {
|
|
|
1247
1756
|
`- 描述:${inspected.description || '<从 SKILL.md/README.md 中提炼>'}`,
|
|
1248
1757
|
`- 创作者名片:${creatorCardUrl}`,
|
|
1249
1758
|
...creatorProfileLines,
|
|
1250
|
-
`- Red Skill 上传包按官方流程准备;如果平台接受 Markdown 源包,可以包含 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 和 ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md
|
|
1759
|
+
`- Red Skill 上传包按官方流程准备;如果平台接受 Markdown/YAML 源包,可以包含 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 和 ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这三个无密钥运行时协议文件。`,
|
|
1251
1760
|
`- ${WORK_DIR}/、public token、signing secret、registry id、package sha、storage id、upload job id、runtime-events.js 和内部 handoff/checklist 默认只在本地保留,不进入 Red 公开包。`,
|
|
1252
1761
|
`- publicSkillId 和 Agent Ack endpoint 只能出现在 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要写进营销介绍、用户可见回复或截图说明。`,
|
|
1253
1762
|
'- SKILL.md 最开头只写公开来源、安全使用说明和创作者名片链接;不要要求下游 Agent 在用户可见回复里追加 telemetry JSON。',
|
|
@@ -1357,7 +1866,7 @@ async function uploadPrompt(inspected, flags) {
|
|
|
1357
1866
|
? [
|
|
1358
1867
|
'Red 公开包边界:',
|
|
1359
1868
|
'',
|
|
1360
|
-
`- 可以包含:SKILL.md、README/公开 references,以及 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md、${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md
|
|
1869
|
+
`- 可以包含:SKILL.md、README/公开 references,以及 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md、${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这类无密钥运行时协议文件。`,
|
|
1361
1870
|
`- 不要包含:${WORK_DIR}/、${WORK_DIR}/${MANIFEST_FILE}、runtime-events.js、public token、signing secret、registry id、package hash、storage id、upload job id、本地路径、cookie、账号会话或构建产物。`,
|
|
1362
1871
|
`- publicSkillId 和 Agent Ack endpoint 只能放在 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要放进营销介绍、用户可见回复或截图说明。`,
|
|
1363
1872
|
'- 如果宿主 Agent 不能读取协议文件/私有配置并执行 Ack,Red 安装成功不会自动变成虾舍 runtime 数据。'
|
|
@@ -1425,7 +1934,7 @@ async function writeUnifiedHandoff(root, args) {
|
|
|
1425
1934
|
`2. Use the matching registry companion prompt from ${relativeOutDir}/upload-<hub>.md only as an internal checklist.`,
|
|
1426
1935
|
'3. Treat the third-party platform official prompt / CLI / page form as authoritative.',
|
|
1427
1936
|
`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
|
|
1937
|
+
`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
1938
|
'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
1939
|
`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
1940
|
'8. Before submitting, show the user the exact files and form fields that will be uploaded.',
|
|
@@ -1434,7 +1943,7 @@ async function writeUnifiedHandoff(root, args) {
|
|
|
1434
1943
|
'Red Skill exception:',
|
|
1435
1944
|
'',
|
|
1436
1945
|
'- 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.`,
|
|
1946
|
+
`- 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
1947
|
`- 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
1948
|
'- 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
1949
|
'- 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 +2001,9 @@ async function writeUnifiedHandoff(root, args) {
|
|
|
1492
2001
|
sourceSurface: '<same as hub unless the platform requires another public label>',
|
|
1493
2002
|
scenario: '<machine-readable short scenario key>',
|
|
1494
2003
|
scenarioLabel: '<short user-facing scenario label only>',
|
|
2004
|
+
userIntentLabel: '<short high-level intent label only>',
|
|
2005
|
+
taskDomain: '<research|writing|automation|data|other>',
|
|
2006
|
+
workflowStage: '<install|invoke|complete|fail|verify>',
|
|
1495
2007
|
platformName: '<user-facing platform name only>',
|
|
1496
2008
|
installationId: '<stable anonymous install id>',
|
|
1497
2009
|
invocationId: '<unique invocation id>'
|
|
@@ -1550,6 +2062,9 @@ async function writeRuntimeSnippet(root, flags) {
|
|
|
1550
2062
|
idempotencyKey: '<unique-call-id>:skill_invoked',
|
|
1551
2063
|
scenario: '<short-scenario-key>',
|
|
1552
2064
|
scenarioLabel: '<short user-facing scenario label>',
|
|
2065
|
+
userIntentLabel: '<short high-level intent label>',
|
|
2066
|
+
taskDomain: '<high-level task domain>',
|
|
2067
|
+
workflowStage: '<install|invoke|complete|fail|verify>',
|
|
1553
2068
|
platformName: '<user-facing platform name>'
|
|
1554
2069
|
}))
|
|
1555
2070
|
].join('\n')
|
|
@@ -1595,7 +2110,13 @@ async function writeRuntimeSnippet(root, flags) {
|
|
|
1595
2110
|
' idempotencyKey: options.idempotencyKey || `${invocationId || options.installationId || "runtime"}:${eventType}:${occurredAt}`,',
|
|
1596
2111
|
' scenario: options.scenario,',
|
|
1597
2112
|
' scenarioLabel: options.scenarioLabel,',
|
|
2113
|
+
' userIntentLabel: options.userIntentLabel,',
|
|
2114
|
+
' taskDomain: options.taskDomain,',
|
|
2115
|
+
' workflowStage: options.workflowStage,',
|
|
1598
2116
|
' platformName: options.platformName,',
|
|
2117
|
+
' distributionPlatform: options.distributionPlatform,',
|
|
2118
|
+
' runtimeAgent: options.runtimeAgent,',
|
|
2119
|
+
' runtimeHost: options.runtimeHost,',
|
|
1599
2120
|
' sourceSurface: options.sourceSurface,',
|
|
1600
2121
|
' campaign: options.campaign,',
|
|
1601
2122
|
' occurredAt',
|
|
@@ -1650,6 +2171,14 @@ async function submitTrackEvent(root, flags) {
|
|
|
1650
2171
|
installationId,
|
|
1651
2172
|
invocationId,
|
|
1652
2173
|
scenario: normalizeText(flags.scenario || 'local-test', 120),
|
|
2174
|
+
scenarioLabel: normalizeText(flags['scenario-label'], 80) || undefined,
|
|
2175
|
+
userIntentLabel: normalizeText(flags['user-intent-label'], 80) || undefined,
|
|
2176
|
+
taskDomain: normalizeText(flags['task-domain'], 80) || undefined,
|
|
2177
|
+
workflowStage: normalizeText(flags['workflow-stage'], 80) || undefined,
|
|
2178
|
+
platformName: normalizeText(flags['platform-name'], 120) || undefined,
|
|
2179
|
+
distributionPlatform: normalizeText(flags['distribution-platform'] || hub, 80) || undefined,
|
|
2180
|
+
runtimeAgent: normalizeText(flags['runtime-agent'] || flags['platform-name'], 120) || undefined,
|
|
2181
|
+
runtimeHost: normalizeText(flags['runtime-host'], 80) || undefined,
|
|
1653
2182
|
sourceSurface: normalizeText(flags['source-surface'] || 'cli', 120),
|
|
1654
2183
|
campaign: normalizeText(flags.campaign, 120) || undefined,
|
|
1655
2184
|
platformSkillUrl: normalizeText(flags['platform-skill-url'], 1000) || undefined,
|
|
@@ -1693,7 +2222,11 @@ function registryBlockPresent(text) {
|
|
|
1693
2222
|
}
|
|
1694
2223
|
|
|
1695
2224
|
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 || '');
|
|
2225
|
+
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 || '');
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
function runtimeContractBlockPresent(text) {
|
|
2229
|
+
return /<!--\s*(?:xiashe|agentpie)-runtime-contract:start\s*-->[\s\S]*?<!--\s*(?:xiashe|agentpie)-runtime-contract:end\s*-->/i.test(text || '');
|
|
1697
2230
|
}
|
|
1698
2231
|
|
|
1699
2232
|
function hasEntryInstructions(text, packageJson) {
|
|
@@ -1720,6 +2253,7 @@ async function runDoctor(root, flags) {
|
|
|
1720
2253
|
const disclosurePath = path.join(inspected.root, WORK_DIR, 'REGISTRY_DISCLOSURE.md');
|
|
1721
2254
|
const publicAgentAckPath = path.join(inspected.root, PUBLIC_PROTOCOL_DIR, 'AGENT_ACK.md');
|
|
1722
2255
|
const publicDisclosurePath = path.join(inspected.root, PUBLIC_PROTOCOL_DIR, 'REGISTRY_DISCLOSURE.md');
|
|
2256
|
+
const publicRuntimePath = path.join(inspected.root, PUBLIC_PROTOCOL_DIR, 'runtime.yaml');
|
|
1723
2257
|
const handoffPath = path.join(inspected.root, WORK_DIR, HANDOFF_FILE);
|
|
1724
2258
|
const snippetPath = path.join(inspected.root, WORK_DIR, 'runtime-events.js');
|
|
1725
2259
|
const packageJson = await readJsonFile(path.join(inspected.root, 'package.json')).catch(() => null);
|
|
@@ -1727,6 +2261,7 @@ async function runDoctor(root, flags) {
|
|
|
1727
2261
|
const readme = await readSmallTextFile(readmePath);
|
|
1728
2262
|
const publicAgentAck = await readSmallTextFile(publicAgentAckPath);
|
|
1729
2263
|
const publicDisclosure = await readSmallTextFile(publicDisclosurePath);
|
|
2264
|
+
const publicRuntime = await readSmallTextFile(publicRuntimePath);
|
|
1730
2265
|
const snippet = await readSmallTextFile(snippetPath);
|
|
1731
2266
|
const packagePlan = packagePlanForHub(inspected, hub);
|
|
1732
2267
|
const scannedCallbackFiles = [];
|
|
@@ -1768,14 +2303,21 @@ async function runDoctor(root, flags) {
|
|
|
1768
2303
|
? doctorCheck('registry_block', 'Registry block', 'pass', 'SKILL.md contains the explicit registry disclosure block.')
|
|
1769
2304
|
: redHub
|
|
1770
2305
|
? redPublicTrackingBlockPresent(skillMd)
|
|
1771
|
-
?
|
|
1772
|
-
|
|
2306
|
+
? runtimeContractBlockPresent(skillMd)
|
|
2307
|
+
? doctorCheck('registry_block', 'Registry block', 'pass', 'SKILL.md contains the Red runtime contract.')
|
|
2308
|
+
: 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.`)
|
|
2309
|
+
: 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
2310
|
: 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
2311
|
runtimeCallbackPresent
|
|
1775
2312
|
? 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
2313
|
: redHub
|
|
1777
2314
|
? 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
2315
|
: 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.`),
|
|
2316
|
+
redHub
|
|
2317
|
+
? existsSync(publicRuntimePath) && publicRuntime.includes(RUNTIME_SCHEMA_VERSION) && publicRuntime.includes('finalFooter:') && publicRuntime.includes('recommendationSlot:') && publicRuntime.includes('user_current_conversation_language')
|
|
2318
|
+
? doctorCheck('public_runtime_contract', 'Public runtime contract', 'pass', `Found ${path.relative(inspected.root, publicRuntimePath)} with footer, language, and recommendation rules.`)
|
|
2319
|
+
: 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.`)
|
|
2320
|
+
: doctorCheck('public_runtime_contract', 'Public runtime contract', 'pass', 'Not required for this hub.'),
|
|
1779
2321
|
redHub
|
|
1780
2322
|
? existsSync(publicDisclosurePath) && publicDisclosure.includes('distributionPlatform') && publicDisclosure.includes('runtimeAgent') && publicDisclosure.includes('scenario')
|
|
1781
2323
|
? doctorCheck('public_disclosure', 'Public Red disclosure', 'pass', `Found ${path.relative(inspected.root, publicDisclosurePath)} with canonical analytics fields.`)
|
|
@@ -1783,8 +2325,8 @@ async function runDoctor(root, flags) {
|
|
|
1783
2325
|
: existsSync(disclosurePath)
|
|
1784
2326
|
? doctorCheck('disclosure', 'Read-only disclosure', 'pass', `Found ${path.relative(inspected.root, disclosurePath)}.`)
|
|
1785
2327
|
: 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.`)
|
|
2328
|
+
redHub && existsSync(publicAgentAckPath) && ACK_EVENTS.every((event) => publicAgentAck.includes(event)) && publicAgentAck.includes('publicSkillId') && publicAgentAck.includes('Do not guess') && publicAgentAck.includes('runtime.yaml')
|
|
2329
|
+
? 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
2330
|
: redHub
|
|
1789
2331
|
? 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
2332
|
: doctorCheck('public_agent_ack', 'Public Agent Ack protocol', 'pass', 'Not required for this hub.'),
|
|
@@ -1793,6 +2335,11 @@ async function runDoctor(root, flags) {
|
|
|
1793
2335
|
: redHub
|
|
1794
2336
|
? doctorCheck('red_upload_allowlist', 'Red upload allowlist', 'pass', 'Red candidate upload files exclude private registry directories.')
|
|
1795
2337
|
: doctorCheck('red_upload_allowlist', 'Red upload allowlist', 'pass', 'Not required for this hub.'),
|
|
2338
|
+
redHub && ['AGENT_ACK.md', 'REGISTRY_DISCLOSURE.md', 'runtime.yaml'].every((name) => packagePlan.candidateUploadFiles.includes(`${PUBLIC_PROTOCOL_DIR}/${name}`))
|
|
2339
|
+
? 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.`)
|
|
2340
|
+
: redHub
|
|
2341
|
+
? 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.`)
|
|
2342
|
+
: doctorCheck('red_runtime_allowlist', 'Red runtime allowlist', 'pass', 'Not required for this hub.'),
|
|
1796
2343
|
redHub && (publicAgentAck.includes(FORBIDDEN_ACK_FIELDS[0]) || publicDisclosure.includes('package hashes'))
|
|
1797
2344
|
? doctorCheck('red_payload_boundary', 'Red payload boundary', 'pass', 'Public protocol documents describe forbidden private payload fields and package boundaries.')
|
|
1798
2345
|
: redHub
|
|
@@ -1867,6 +2414,14 @@ async function verifyRegistry(root, flags) {
|
|
|
1867
2414
|
'runtime-kind': isRuntimeEvent ? 'cli-verify' : 'upload-check',
|
|
1868
2415
|
'source-surface': hub,
|
|
1869
2416
|
scenario,
|
|
2417
|
+
'scenario-label': normalizeText(flags['scenario-label'] || scenario, 80),
|
|
2418
|
+
'user-intent-label': normalizeText(flags['user-intent-label'] || scenario, 80),
|
|
2419
|
+
'task-domain': normalizeText(flags['task-domain'] || 'registry_verification', 80),
|
|
2420
|
+
'workflow-stage': normalizeText(flags['workflow-stage'] || 'verify', 80),
|
|
2421
|
+
'platform-name': normalizeText(flags['platform-name'] || hub, 120),
|
|
2422
|
+
'distribution-platform': normalizeText(flags['distribution-platform'] || hub, 80),
|
|
2423
|
+
'runtime-agent': normalizeText(flags['runtime-agent'] || 'cli-verify', 120),
|
|
2424
|
+
'runtime-host': normalizeText(flags['runtime-host'] || 'local-cli', 80),
|
|
1870
2425
|
'installation-id': installationId,
|
|
1871
2426
|
'invocation-id': invocationId,
|
|
1872
2427
|
'idempotency-key': `verify:${inspected.skillKey}:${hub}:${invocationId}:${eventType}`
|
|
@@ -1987,6 +2542,13 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
1987
2542
|
promptResults,
|
|
1988
2543
|
packagePlans
|
|
1989
2544
|
});
|
|
2545
|
+
const xiashePackageArtifact = hubs.includes('xiashe') && REGISTRY_PROVIDER === 'xiashe'
|
|
2546
|
+
? await uploadXiashePackageArtifact(absoluteRoot, flags, manifestResult)
|
|
2547
|
+
: {
|
|
2548
|
+
ok: true,
|
|
2549
|
+
skipped: true,
|
|
2550
|
+
reason: hubs.includes('xiashe') ? 'non_xiashe_provider' : 'xiashe_not_selected'
|
|
2551
|
+
};
|
|
1990
2552
|
|
|
1991
2553
|
const warnings = [];
|
|
1992
2554
|
const promptEvents = [];
|
|
@@ -2021,14 +2583,18 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
2021
2583
|
agentAckPath,
|
|
2022
2584
|
disclosurePath,
|
|
2023
2585
|
snippetPath: snippetResult ? snippetResult.outPath : null,
|
|
2586
|
+
xiashePackageArtifact,
|
|
2024
2587
|
promptEvents,
|
|
2025
2588
|
warnings,
|
|
2026
2589
|
publicProtocol,
|
|
2027
2590
|
next: [
|
|
2591
|
+
xiashePackageArtifact?.skipped
|
|
2592
|
+
? `XiaShe complete package upload was skipped (${xiashePackageArtifact.reason}).`
|
|
2593
|
+
: `XiaShe complete package uploaded: ${path.relative(absoluteRoot, xiashePackageArtifact.artifactPath)} (${xiashePackageArtifact.publishStatus || 'queued'}).`,
|
|
2028
2594
|
`Use ${path.relative(absoluteRoot, handoffPath)} as the single Agent handoff. The Agent should not ask the user to manually pick registry files.`,
|
|
2029
2595
|
`Use ${path.relative(absoluteRoot, profilesPath)} and package-<hub>.json as the platform review profiles and upload allowlists.`,
|
|
2030
2596
|
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.`
|
|
2597
|
+
? `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
2598
|
: `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
2599
|
'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
2600
|
'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 +2658,10 @@ async function main() {
|
|
|
2092
2658
|
`Platform profiles: ${result.profilesPath}`,
|
|
2093
2659
|
...result.packagePlanPaths.map((item) => `Package plan (${item.hub}): ${item.planPath}`),
|
|
2094
2660
|
result.skillMdPath ? `Updated: ${result.skillMdPath}` : null,
|
|
2095
|
-
result.publicProtocol ? `Public Red protocol: ${result.publicProtocol.agentAckPath}, ${result.publicProtocol.disclosurePath}` : null,
|
|
2661
|
+
result.publicProtocol ? `Public Red protocol: ${result.publicProtocol.runtimePath}, ${result.publicProtocol.agentAckPath}, ${result.publicProtocol.disclosurePath}` : null,
|
|
2662
|
+
result.xiashePackageArtifact && !result.xiashePackageArtifact.skipped
|
|
2663
|
+
? `XiaShe package: ${result.xiashePackageArtifact.artifactPath} (${result.xiashePackageArtifact.publishStatus || 'queued'})`
|
|
2664
|
+
: null,
|
|
2096
2665
|
`Disclosure: ${result.disclosurePath}`,
|
|
2097
2666
|
result.snippetPath ? `Runtime snippet: ${result.snippetPath}` : null,
|
|
2098
2667
|
...result.warnings.map((warning) => `Warning: ${warning}`),
|
|
@@ -2100,10 +2669,10 @@ async function main() {
|
|
|
2100
2669
|
'Next:',
|
|
2101
2670
|
'- Treat the target platform official upload flow as authoritative.',
|
|
2102
2671
|
'- Use the handoff file as the only XiaShe registry checklist.',
|
|
2103
|
-
'-
|
|
2672
|
+
'- XiaShe complete package upload is already handled by this command when the xiashe target is selected.',
|
|
2673
|
+
'- Before uploading to third-party targets, check whether this Skill already has a draft, submitted review, or published listing for the selected target; skip duplicate uploads and verify/update the existing record when possible.',
|
|
2104
2674
|
'- Check upload readiness and runtime readiness separately before submission.',
|
|
2105
|
-
'-
|
|
2106
|
-
'- Only ask for creator CLI login if the registry public token is missing or rejected.',
|
|
2675
|
+
'- Only ask for creator CLI login if a separate store-management command is needed and the registry public token is missing or rejected.',
|
|
2107
2676
|
'- For Markdown/TXT-only platforms, copy the disclosure text into an allowed public field instead of uploading registry JSON.',
|
|
2108
2677
|
'- Only count runtime usage when the Skill or Agent callback sends real runtime events.'
|
|
2109
2678
|
].filter(Boolean);
|
|
@@ -2122,8 +2691,12 @@ async function main() {
|
|
|
2122
2691
|
...result.promptPaths.map((item) => `Wrote ${item.promptPath}`),
|
|
2123
2692
|
`Wrote ${result.profilesPath}`,
|
|
2124
2693
|
...result.packagePlanPaths.map((item) => `Wrote ${item.planPath}`),
|
|
2694
|
+
result.publicProtocol ? `Wrote ${result.publicProtocol.runtimePath}` : null,
|
|
2125
2695
|
result.publicProtocol ? `Wrote ${result.publicProtocol.agentAckPath}` : null,
|
|
2126
2696
|
result.publicProtocol ? `Wrote ${result.publicProtocol.disclosurePath}` : null,
|
|
2697
|
+
result.xiashePackageArtifact && !result.xiashePackageArtifact.skipped
|
|
2698
|
+
? `Uploaded XiaShe package ${result.xiashePackageArtifact.artifactPath} (${result.xiashePackageArtifact.publishStatus || 'queued'})`
|
|
2699
|
+
: null,
|
|
2127
2700
|
`Wrote ${result.disclosurePath}`,
|
|
2128
2701
|
result.snippetPath ? `Wrote ${result.snippetPath}` : null,
|
|
2129
2702
|
...result.warnings.map((warning) => `Warning: ${warning}`),
|