引入 AGC 随包资源的单一声明与生成门禁

- 新增 build_support/package-layout.json:Codex 三元表与组件白名单、上游候选路径、第三方声明来源、插件随包子目录与跳过规则、平台与 feature 门槛的唯一人工声明
- 新增 scripts/check-package-layout.mjs:由声明生成 build_support/package-layout.generated.rs,并校验目标唯一、可执行组件属于白名单、每个目标恰有一条第三方声明来源、codex.version 与应用锁定的 @openai/codex 一致
- 新增 build_support/package_layout.rs:声明类型、插件与平台门槛判定、逐块 sha256、随包 Codex 目录只读校验及其单测
- codex_bundle.rs 的布局与版本常量改由声明提供,公开接口与取值不变;src/main.rs 在测试期引入该模块以复用既有单测
- package.json 新增 bundled-resources:check/sync 并接进 typecheck 链,根 package.json 提供 agc:* 别名
This commit is contained in:
2026-09-27 18:35:57 +08:00
parent 756297d2df
commit 21c3bd7a2b
8 changed files with 1426 additions and 43 deletions
+5 -1
View File
@@ -13,6 +13,10 @@
"skill-pack:check": "node scripts/check-skill-pack.mjs",
"skill-pack:sync": "node scripts/check-skill-pack.mjs --write",
"skill-pack:test": "node --test scripts/check-skill-pack.test.mjs",
"bundled-resources:check": "node scripts/check-package-layout.mjs",
"bundled-resources:sync": "node scripts/check-package-layout.mjs --write",
"bundled-resources:prepare": "node scripts/prepare-bundled-resources.mjs",
"bundled-resources:test": "node --test scripts/prepare-bundled-resources.test.mjs",
"llm-status": "node scripts/run-cli-with-config.mjs --llm-status",
"agent-task": "node scripts/run-cli-with-config.mjs --agent-task",
"config": "node scripts/game-creator-config-wizard.mjs",
@@ -24,7 +28,7 @@
"agent-runtime:supervisor-swarm-tool-plan-handoff-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-tool-plan-handoff-runner-kill",
"agent-runtime:steer-real-e2e": "node scripts/agent-runtime-steer-real-e2e.mjs",
"agent-runtime:steer-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite steer-runner-kill",
"typecheck": "tsc -p tsconfig.json --noEmit && npm run skill-pack:check && node scripts/check-config.mjs"
"typecheck": "tsc -p tsconfig.json --noEmit && npm run skill-pack:check && npm run bundled-resources:check && node scripts/check-config.mjs"
},
"dependencies": {
"@cubone/react-file-manager": "^1.35.0",
+477
View File
@@ -0,0 +1,477 @@
#!/usr/bin/env node
// 随包资源声明门禁:把 build_support/package-layout.json(唯一人工声明)渲染成
// build_support/package-layout.generated.rs(Rust 编译期常量),并校验声明结构与不变量。
//
// 用法:
// node scripts/check-package-layout.mjs 校验生成结果是否与声明一致(不一致 exit 1)
// node scripts/check-package-layout.mjs --write 重新生成
//
// 设计约束:Rust 侧不解析 JSON(避免运行期解析与生命周期妥协),只使用本脚本产出的常量;
// Node 侧准备步骤直接读同一份 JSON。因此本门禁是“单一声明”的机械保障。
import { readFileSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
const APP_ROOT = path.resolve(SCRIPT_DIR, '..');
const SRC_TAURI = path.join(APP_ROOT, 'src-tauri');
const DECLARATION_PATH = path.join(
SRC_TAURI,
'build_support/package-layout.json',
);
const GENERATED_PATH = path.join(
SRC_TAURI,
'build_support/package-layout.generated.rs',
);
const EXPECTED_SCHEMA = 'agc-package-layout.v1';
class DeclarationError extends Error {}
function fail(message) {
throw new DeclarationError(message);
}
function expectObject(value, at) {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
fail(`${at} 必须是对象`);
}
return value;
}
function expectArray(value, at) {
if (!Array.isArray(value)) {
fail(`${at} 必须是数组`);
}
return value;
}
function expectString(value, at) {
if (typeof value !== 'string' || value.length === 0) {
fail(`${at} 必须是非空字符串`);
}
return value;
}
function expectBoolean(value, at) {
if (typeof value !== 'boolean') {
fail(`${at} 必须是布尔值`);
}
return value;
}
function expectStringArray(value, at) {
return expectArray(value, at).map((item, index) =>
expectString(item, `${at}[${index}]`),
);
}
function optionalStringArray(source, key, at) {
if (source[key] === undefined) {
return [];
}
return expectStringArray(source[key], `${at}.${key}`);
}
function expectInteger(value, at) {
if (!Number.isInteger(value) || value < 0) {
fail(`${at} 必须是非负整数`);
}
return value;
}
// JSON 字符串字面量与 Rust 字符串字面量几乎一致,唯一差异是控制字符的 \uXXXX 与 \u{XX}。
function rustString(value) {
return JSON.stringify(value).replace(/\\u([0-9a-fA-F]{4})/g, '\\u{$1}');
}
function rustStrings(values) {
return `&[${values.map(rustString).join(', ')}]`;
}
function indent(level) {
return ' '.repeat(level);
}
function renderStruct(name, fields, level = 0) {
const body = fields
.map(([key, rendered]) => `${indent(level + 1)}${key}: ${rendered},`)
.join('\n');
return `${name} {\n${body}\n${indent(level)}}`;
}
function parseDeclaration(raw) {
const root = expectObject(JSON.parse(raw), 'root');
if (root.schema !== EXPECTED_SCHEMA) {
fail(
`不支持的声明 schema:${String(root.schema)}(期望 ${EXPECTED_SCHEMA})`,
);
}
const layoutVersion = expectInteger(root.layoutVersion, 'layoutVersion');
const codex = expectObject(root.codex, 'codex');
const targets = expectArray(codex.targets, 'codex.targets').map(
(entry, index) => {
const at = `codex.targets[${index}]`;
const parsed = expectObject(entry, at);
const files = expectStringArray(parsed.files, `${at}.files`);
if (files.length === 0) {
fail(`${at}.files 不能为空`);
}
const executable = expectString(parsed.executable, `${at}.executable`);
if (!files.includes(executable)) {
fail(`${at}.executable 必须属于组件白名单:${executable}`);
}
return {
target: expectString(parsed.target, `${at}.target`),
platform: expectString(parsed.platform, `${at}.platform`),
directory: expectString(parsed.directory, `${at}.directory`),
executable,
files,
};
},
);
const seenTargets = new Set();
for (const entry of targets) {
if (seenTargets.has(entry.target)) {
fail(`codex.targets 重复声明目标 ${entry.target}`);
}
seenTargets.add(entry.target);
}
const noticeSources = expectArray(
codex.noticeSources,
'codex.noticeSources',
).map((entry, index) => {
const at = `codex.noticeSources[${index}]`;
const parsed = expectObject(entry, at);
return {
targets: expectStringArray(parsed.targets, `${at}.targets`),
source: expectString(parsed.source, `${at}.source`),
preserve: expectBoolean(parsed.preserve, `${at}.preserve`),
};
});
for (const entry of targets) {
const covered = noticeSources.filter((notice) =>
notice.targets.includes(entry.target),
);
if (covered.length !== 1) {
fail(
`目标 ${entry.target} 必须且只能有一条第三方声明来源(实际 ${covered.length} 条)`,
);
}
}
const universalGroups = expectArray(
codex.universalGroups,
'codex.universalGroups',
).map((entry, index) => {
const at = `codex.universalGroups[${index}]`;
const parsed = expectObject(entry, at);
const groupTargets = expectStringArray(parsed.targets, `${at}.targets`);
for (const target of groupTargets) {
if (!seenTargets.has(target)) {
fail(`${at}.targets 含未声明目标 ${target}`);
}
}
return {
name: expectString(parsed.name, `${at}.name`),
directory: expectString(parsed.directory, `${at}.directory`),
targets: groupTargets,
};
});
const plugins = expectObject(root.plugins, 'plugins');
const subdirectories = expectArray(
plugins.subdirectories,
'plugins.subdirectories',
).map((entry, index) => {
const at = `plugins.subdirectories[${index}]`;
const parsed = expectObject(entry, at);
return {
path: expectString(parsed.path, `${at}.path`),
targetContains: optionalStringArray(parsed, 'targetContains', at),
targets: optionalStringArray(parsed, 'targets', at),
features: optionalStringArray(parsed, 'features', at),
};
});
const libraryStaging = expectArray(
plugins.libraryStaging,
'plugins.libraryStaging',
).map((entry, index) => {
const at = `plugins.libraryStaging[${index}]`;
const parsed = expectObject(entry, at);
return {
plugin: expectString(parsed.plugin, `${at}.plugin`),
sourceSubdirectory: expectString(
parsed.sourceSubdirectory,
`${at}.sourceSubdirectory`,
),
targets: expectStringArray(parsed.targets, `${at}.targets`),
features: expectStringArray(parsed.features, `${at}.features`),
layout: expectString(parsed.layout, `${at}.layout`),
};
});
return {
layoutVersion,
codex: {
version: expectString(codex.version, 'codex.version'),
cliVersionPrefix: expectString(
codex.cliVersionPrefix,
'codex.cliVersionPrefix',
),
manifestSchema: expectString(
codex.manifestSchema,
'codex.manifestSchema',
),
resourceDirectory: expectString(
codex.resourceDirectory,
'codex.resourceDirectory',
),
manifestFileName: expectString(
codex.manifestFileName,
'codex.manifestFileName',
),
packageMetadataFileName: expectString(
codex.packageMetadataFileName,
'codex.packageMetadataFileName',
),
noticeFileName: expectString(
codex.noticeFileName,
'codex.noticeFileName',
),
sourceRoots: expectStringArray(codex.sourceRoots, 'codex.sourceRoots'),
sourceRelativePaths: expectStringArray(
codex.sourceRelativePaths,
'codex.sourceRelativePaths',
),
noticeSources,
universalGroups,
targets,
},
plugins: {
sourceDirectory: expectString(
plugins.sourceDirectory,
'plugins.sourceDirectory',
),
destinationDirectory: expectString(
plugins.destinationDirectory,
'plugins.destinationDirectory',
),
manifestFileName: expectString(
plugins.manifestFileName,
'plugins.manifestFileName',
),
targetContainsAny: expectStringArray(
plugins.targetContainsAny,
'plugins.targetContainsAny',
),
subdirectories,
libraryStaging,
skipDirectoryNames: expectStringArray(
plugins.skipDirectoryNames,
'plugins.skipDirectoryNames',
),
skipDirectoryNamePrefixes: expectStringArray(
plugins.skipDirectoryNamePrefixes,
'plugins.skipDirectoryNamePrefixes',
),
skipFileNamePrefixes: expectStringArray(
plugins.skipFileNamePrefixes,
'plugins.skipFileNamePrefixes',
),
skipFileNameFragments: expectStringArray(
plugins.skipFileNameFragments,
'plugins.skipFileNameFragments',
),
},
};
}
function renderCodexTarget(entry) {
return renderStruct(
'CodexTarget',
[
['target', rustString(entry.target)],
['platform', rustString(entry.platform)],
['directory', rustString(entry.directory)],
['executable', rustString(entry.executable)],
['files', rustStrings(entry.files)],
],
1,
);
}
function renderNoticeSource(entry) {
return renderStruct(
'NoticeSource',
[
['targets', rustStrings(entry.targets)],
['source', rustString(entry.source)],
['preserve', entry.preserve ? 'true' : 'false'],
],
1,
);
}
function renderUniversalGroup(entry) {
return renderStruct(
'UniversalGroup',
[
['name', rustString(entry.name)],
['directory', rustString(entry.directory)],
['targets', rustStrings(entry.targets)],
],
1,
);
}
function renderSubdirectory(entry) {
return renderStruct(
'Subdirectory',
[
['path', rustString(entry.path)],
['target_contains', rustStrings(entry.targetContains)],
['targets', rustStrings(entry.targets)],
['features', rustStrings(entry.features)],
],
1,
);
}
function renderLibraryStaging(entry) {
return renderStruct(
'LibraryStaging',
[
['plugin', rustString(entry.plugin)],
['source_subdirectory', rustString(entry.sourceSubdirectory)],
['targets', rustStrings(entry.targets)],
['features', rustStrings(entry.features)],
['layout', rustString(entry.layout)],
],
1,
);
}
function renderGenerated(declaration) {
const codex = declaration.codex;
const plugins = declaration.plugins;
const codexStruct = renderStruct('Codex', [
['resource_directory', rustString(codex.resourceDirectory)],
['manifest_file_name', rustString(codex.manifestFileName)],
['package_metadata_file_name', rustString(codex.packageMetadataFileName)],
['notice_file_name', rustString(codex.noticeFileName)],
['source_roots', rustStrings(codex.sourceRoots)],
['source_relative_paths', rustStrings(codex.sourceRelativePaths)],
[
'notice_sources',
`&[\n${codex.noticeSources.map(renderNoticeSource).join(',\n')}\n]`,
],
[
'universal_groups',
`&[\n${codex.universalGroups.map(renderUniversalGroup).join(',\n')}\n]`,
],
['targets', `&[\n${codex.targets.map(renderCodexTarget).join(',\n')}\n]`],
]);
const pluginsStruct = renderStruct('Plugins', [
['source_directory', rustString(plugins.sourceDirectory)],
['destination_directory', rustString(plugins.destinationDirectory)],
['manifest_file_name', rustString(plugins.manifestFileName)],
['target_contains_any', rustStrings(plugins.targetContainsAny)],
[
'subdirectories',
`&[\n${plugins.subdirectories.map(renderSubdirectory).join(',\n')}\n]`,
],
[
'library_staging',
`&[\n${plugins.libraryStaging.map(renderLibraryStaging).join(',\n')}\n]`,
],
['skip_directory_names', rustStrings(plugins.skipDirectoryNames)],
[
'skip_directory_name_prefixes',
rustStrings(plugins.skipDirectoryNamePrefixes),
],
['skip_file_name_prefixes', rustStrings(plugins.skipFileNamePrefixes)],
['skip_file_name_fragments', rustStrings(plugins.skipFileNameFragments)],
]);
return `// @generated by apps/ai-game-creator-shell/scripts/check-package-layout.mjs
// 来源:build_support/package-layout.json。不要手工编辑本文件。
// 修改随包资源布局请编辑声明文件,然后运行
// npm run agc:package-layout:sync(在仓库根目录)
// 门禁会校验两者一致(npm run agc:typecheck 链内含 check-package-layout.mjs)。
pub const DECLARATION_SCHEMA: &str = ${rustString(EXPECTED_SCHEMA)};
pub const LAYOUT_VERSION: u64 = ${declaration.layoutVersion};
pub const CODEX_VERSION: &str = ${rustString(declaration.codex.version)};
pub const CODEX_CLI_VERSION: &str = ${rustString(declaration.codex.cliVersionPrefix + declaration.codex.version)};
pub const CODEX_MANIFEST_SCHEMA: &str = ${rustString(declaration.codex.manifestSchema)};
pub const CODEX: Codex = ${codexStruct};
pub const PLUGINS: Plugins = ${pluginsStruct};
`;
}
function appLockedCodexVersion() {
const appPackage = expectObject(
JSON.parse(readFileSync(path.join(APP_ROOT, 'package.json'), 'utf8')),
'apps/ai-game-creator-shell/package.json',
);
const declared =
appPackage.dependencies?.['@openai/codex'] ??
appPackage.devDependencies?.['@openai/codex'];
if (typeof declared !== 'string' || declared.length === 0) {
fail(
'应用 package.json 未声明 @openai/codex,无法校验声明的 codex.version',
);
}
return declared.replace(/^[\^~]/, '');
}
function main() {
const declaration = parseDeclaration(readFileSync(DECLARATION_PATH, 'utf8'));
const lockedVersion = appLockedCodexVersion();
if (lockedVersion !== declaration.codex.version) {
fail(
`声明 codex.version(${declaration.codex.version})与应用锁定的 @openai/codex(${lockedVersion})不一致;请同步更新 build_support/package-layout.json`,
);
}
const rendered = renderGenerated(declaration);
const write = process.argv.includes('--write');
if (write) {
writeFileSync(GENERATED_PATH, rendered);
console.log(
`随包资源声明已生成:${path.relative(process.cwd(), GENERATED_PATH)}(layoutVersion ${declaration.layoutVersion})`,
);
return;
}
const current = readFileSync(GENERATED_PATH, 'utf8');
if (current !== rendered) {
console.error(
[
`随包资源声明与 Rust 常量不一致:`,
` 声明:${path.relative(process.cwd(), DECLARATION_PATH)}`,
` 生成:${path.relative(process.cwd(), GENERATED_PATH)}`,
'请运行:npm run agc:package-layout:sync',
].join('\n'),
);
process.exit(1);
}
console.log(
`随包资源声明一致(layoutVersion ${declaration.layoutVersion},codex ${declaration.codex.version},目标 ${declaration.codex.targets.length},插件子目录 ${declaration.plugins.subdirectories.length})`,
);
}
try {
main();
} catch (error) {
if (error instanceof DeclarationError) {
console.error(`随包资源声明无效:${error.message}`);
process.exit(1);
}
throw error;
}
@@ -1,8 +1,18 @@
//! 构建与运行共用的平台布局;只允许分发锁定原生包里的明确组件。
//!
//! 布局、组件白名单与版本常量来自唯一声明 `build_support/package-layout.json`
//! (Rust 侧经 `build_support/package-layout.generated.rs` 取得编译期常量,
//! 由 `scripts/check-package-layout.mjs` 生成并在门禁中校验一致)。
//! 本模块只读声明,不写任何随包资源。
pub const VERSION: &str = "0.155.1";
pub const CLI_VERSION: &str = "codex-cli 0.155.1";
pub const SCHEMA: &str = "genarrative-codex-sidecar.v2";
// 共享声明模块:构建脚本、运行期与测试各自只用到其中一部分,未用到的入口不算缺陷。
#[allow(dead_code)]
#[path = "package_layout.rs"]
pub(crate) mod package_layout;
pub const VERSION: &str = package_layout::CODEX_VERSION;
pub const CLI_VERSION: &str = package_layout::CODEX_CLI_VERSION;
pub const SCHEMA: &str = package_layout::CODEX_MANIFEST_SCHEMA;
#[derive(Clone, Copy, Debug)]
pub struct Layout {
@@ -12,46 +22,13 @@ pub struct Layout {
pub files: &'static [&'static str],
}
const WINDOWS_FILES: &[&str] = &[
"bin/codex.exe",
"bin/codex-code-mode-host.exe",
"codex-path/rg.exe",
"codex-resources/codex-command-runner.exe",
"codex-resources/codex-windows-sandbox-setup.exe",
"codex-package.json",
];
const MAC_FILES: &[&str] = &[
"bin/codex",
"bin/codex-code-mode-host",
"codex-path/rg",
"codex-resources/zsh/bin/zsh",
"codex-package.json",
];
pub fn for_target(target: &str) -> Option<Layout> {
match target {
"x86_64-pc-windows-msvc" => Some(Layout {
platform: "win32-x64",
directory: "win-x64",
executable: "bin/codex.exe",
files: WINDOWS_FILES,
}),
"aarch64-apple-darwin" | "x86_64-apple-darwin" => Some(Layout {
platform: if target.starts_with("aarch64") {
"darwin-arm64"
} else {
"darwin-x64"
},
directory: if target.starts_with("aarch64") {
"mac-native/darwin-arm64"
} else {
"mac-native/darwin-x64"
},
executable: "bin/codex",
files: MAC_FILES,
}),
_ => None,
}
package_layout::codex_target(target).map(|declared| Layout {
platform: declared.platform,
directory: declared.directory,
executable: declared.executable,
files: declared.files,
})
}
#[cfg(test)]
@@ -80,4 +57,11 @@ mod tests {
assert!(for_target("aarch64-pc-windows-msvc").is_none());
assert!(for_target("x86_64-unknown-linux-gnu").is_none());
}
#[test]
fn constants_come_from_the_shared_declaration() {
assert_eq!(VERSION, "0.155.1");
assert_eq!(CLI_VERSION, format!("codex-cli {VERSION}"));
assert_eq!(SCHEMA, "genarrative-codex-sidecar.v2");
}
}
@@ -0,0 +1,115 @@
// @generated by apps/ai-game-creator-shell/scripts/check-package-layout.mjs
// 来源:build_support/package-layout.json。不要手工编辑本文件。
// 修改随包资源布局请编辑声明文件,然后运行
// npm run agc:package-layout:sync(在仓库根目录)
// 门禁会校验两者一致(npm run agc:typecheck 链内含 check-package-layout.mjs)。
pub const DECLARATION_SCHEMA: &str = "agc-package-layout.v1";
pub const LAYOUT_VERSION: u64 = 1;
pub const CODEX_VERSION: &str = "0.155.1";
pub const CODEX_CLI_VERSION: &str = "codex-cli 0.155.1";
pub const CODEX_MANIFEST_SCHEMA: &str = "genarrative-codex-sidecar.v2";
pub const CODEX: Codex = Codex {
resource_directory: "resources/codex",
manifest_file_name: "manifest.json",
package_metadata_file_name: "codex-package.json",
notice_file_name: "NOTICE.md",
source_roots: &["app", "repo"],
source_relative_paths: &["node_modules/@openai/codex-<platform>/vendor/<target>", "node_modules/@openai/codex/node_modules/@openai/codex-<platform>/vendor/<target>"],
notice_sources: &[
NoticeSource {
targets: &["aarch64-apple-darwin", "x86_64-apple-darwin"],
source: "resources/codex/【声明】Mac内置Codex组件-2026-09-18.md",
preserve: false,
},
NoticeSource {
targets: &["x86_64-pc-windows-msvc"],
source: "resources/codex/win-x64/NOTICE.md",
preserve: true,
}
],
universal_groups: &[
UniversalGroup {
name: "mac-native",
directory: "mac-native",
targets: &["aarch64-apple-darwin", "x86_64-apple-darwin"],
}
],
targets: &[
CodexTarget {
target: "x86_64-pc-windows-msvc",
platform: "win32-x64",
directory: "win-x64",
executable: "bin/codex.exe",
files: &["bin/codex.exe", "bin/codex-code-mode-host.exe", "codex-path/rg.exe", "codex-resources/codex-command-runner.exe", "codex-resources/codex-windows-sandbox-setup.exe", "codex-package.json"],
},
CodexTarget {
target: "aarch64-apple-darwin",
platform: "darwin-arm64",
directory: "mac-native/darwin-arm64",
executable: "bin/codex",
files: &["bin/codex", "bin/codex-code-mode-host", "codex-path/rg", "codex-resources/zsh/bin/zsh", "codex-package.json"],
},
CodexTarget {
target: "x86_64-apple-darwin",
platform: "darwin-x64",
directory: "mac-native/darwin-x64",
executable: "bin/codex",
files: &["bin/codex", "bin/codex-code-mode-host", "codex-path/rg", "codex-resources/zsh/bin/zsh", "codex-package.json"],
}
],
};
pub const PLUGINS: Plugins = Plugins {
source_directory: "plugins",
destination_directory: "resources/plugins",
manifest_file_name: "plugin.json",
target_contains_any: &["windows", "apple-darwin"],
subdirectories: &[
Subdirectory {
path: "src",
target_contains: &[],
targets: &[],
features: &[],
},
Subdirectory {
path: "panels",
target_contains: &[],
targets: &[],
features: &[],
},
Subdirectory {
path: "skills",
target_contains: &[],
targets: &[],
features: &[],
},
Subdirectory {
path: "native/payload",
target_contains: &["windows"],
targets: &[],
features: &[],
},
Subdirectory {
path: "dotnet/publish/win-x64",
target_contains: &[],
targets: &["x86_64-pc-windows-msvc"],
features: &["unity-editor-execute"],
}
],
library_staging: &[
LibraryStaging {
plugin: "agc-godot-editor",
source_subdirectory: "native/gdextension",
targets: &["x86_64-pc-windows-msvc"],
features: &["godot-editor-execute"],
layout: "godot-bundle",
}
],
skip_directory_names: &["target", "node_modules"],
skip_directory_name_prefixes: &["."],
skip_file_name_prefixes: &["."],
skip_file_name_fragments: &[".test."],
};
@@ -0,0 +1,110 @@
{
"schema": "agc-package-layout.v1",
"layoutVersion": 1,
"description": "AGC 随包资源布局与复制规则的唯一声明。Rust 侧构建期校验与 Node 侧准备步骤共用本文件,任何一侧都不得再写第二份布局或组件白名单。含 <platform>、<target> 占位符的字段由调用方按目标三元展开。修改布局时同步递增 layoutVersion(准备步骤的缓存 key 组成部分)。",
"codex": {
"version": "0.155.1",
"cliVersionPrefix": "codex-cli ",
"manifestSchema": "genarrative-codex-sidecar.v2",
"resourceDirectory": "resources/codex",
"manifestFileName": "manifest.json",
"packageMetadataFileName": "codex-package.json",
"noticeFileName": "NOTICE.md",
"sourceRoots": ["app", "repo"],
"sourceRelativePaths": [
"node_modules/@openai/codex-<platform>/vendor/<target>",
"node_modules/@openai/codex/node_modules/@openai/codex-<platform>/vendor/<target>"
],
"noticeSources": [
{
"targets": ["aarch64-apple-darwin", "x86_64-apple-darwin"],
"source": "resources/codex/【声明】Mac内置Codex组件-2026-09-18.md",
"preserve": false
},
{
"targets": ["x86_64-pc-windows-msvc"],
"source": "resources/codex/win-x64/NOTICE.md",
"preserve": true
}
],
"universalGroups": [
{
"name": "mac-native",
"directory": "mac-native",
"targets": ["aarch64-apple-darwin", "x86_64-apple-darwin"]
}
],
"targets": [
{
"target": "x86_64-pc-windows-msvc",
"platform": "win32-x64",
"directory": "win-x64",
"executable": "bin/codex.exe",
"files": [
"bin/codex.exe",
"bin/codex-code-mode-host.exe",
"codex-path/rg.exe",
"codex-resources/codex-command-runner.exe",
"codex-resources/codex-windows-sandbox-setup.exe",
"codex-package.json"
]
},
{
"target": "aarch64-apple-darwin",
"platform": "darwin-arm64",
"directory": "mac-native/darwin-arm64",
"executable": "bin/codex",
"files": [
"bin/codex",
"bin/codex-code-mode-host",
"codex-path/rg",
"codex-resources/zsh/bin/zsh",
"codex-package.json"
]
},
{
"target": "x86_64-apple-darwin",
"platform": "darwin-x64",
"directory": "mac-native/darwin-x64",
"executable": "bin/codex",
"files": [
"bin/codex",
"bin/codex-code-mode-host",
"codex-path/rg",
"codex-resources/zsh/bin/zsh",
"codex-package.json"
]
}
]
},
"plugins": {
"sourceDirectory": "plugins",
"destinationDirectory": "resources/plugins",
"manifestFileName": "plugin.json",
"targetContainsAny": ["windows", "apple-darwin"],
"subdirectories": [
{ "path": "src" },
{ "path": "panels" },
{ "path": "skills" },
{ "path": "native/payload", "targetContains": ["windows"] },
{
"path": "dotnet/publish/win-x64",
"targets": ["x86_64-pc-windows-msvc"],
"features": ["unity-editor-execute"]
}
],
"libraryStaging": [
{
"plugin": "agc-godot-editor",
"sourceSubdirectory": "native/gdextension",
"targets": ["x86_64-pc-windows-msvc"],
"features": ["godot-editor-execute"],
"layout": "godot-bundle"
}
],
"skipDirectoryNames": ["target", "node_modules"],
"skipDirectoryNamePrefixes": ["."],
"skipFileNamePrefixes": ["."],
"skipFileNameFragments": [".test."]
}
}
File diff suppressed because it is too large Load Diff
@@ -4,6 +4,11 @@
#[path = "../build_support/godot_bundle.rs"]
mod godot_bundle;
// 复用随包资源声明的既有单测(校验通过/拒绝用例),生产运行时只经 codex_bundle 使用布局。
#[cfg(test)]
#[path = "../build_support/package_layout.rs"]
mod package_layout;
use std::collections::BTreeMap;
use std::fs;
use std::fs::{File, OpenOptions};