Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 81a35e0bff | |||
| 080c3ebc4f | |||
| c75fcbf023 | |||
| cf5eac1c63 | |||
| ea81d8ebb5 | |||
| d0a8f254b3 | |||
| a312ee20b5 | |||
| 1a61c0bea7 | |||
| 7cd8b39593 | |||
| 5e7ff450b6 | |||
| 7fc739fad0 | |||
| d2366bc40a | |||
| 7400c706f8 | |||
| 6befa3c33a | |||
| 1746c77cb1 | |||
| 41874ab391 | |||
| aa5221abcf | |||
| 9f5c84ee71 | |||
| 578f8019fc | |||
| 4d3f87cd39 | |||
| d901945c77 | |||
| d155e303bd | |||
| 8e78be766e | |||
| 7052d3e692 | |||
| 1b4cb3c45e | |||
| 15c227cebf | |||
| 21137f676b | |||
| f395a6ed36 | |||
| da7c4dc71c | |||
| fd423770d8 | |||
| 794f3d4cf8 | |||
| e04a8310a0 | |||
| 82957fe1d4 | |||
| 5e16837f3d | |||
| effdaefc63 | |||
| 8abeb333bb | |||
| 4c3c0e9cd4 | |||
| e3d28c50af | |||
| 8e3d9991fa | |||
| 51e35468a5 | |||
| 03a4410272 | |||
| e118d66699 | |||
| 1363b9374c | |||
| 3c457feb4c | |||
| aa5c857060 | |||
| 755bf80858 | |||
| 4c0fdf1806 | |||
| 7f129ddd6e | |||
| 4d79971d6b | |||
| 5550e24f83 | |||
| a4ead1a5d6 | |||
| 5949c2fd58 |
@@ -29,9 +29,10 @@ Prefer `scripts/genarrative_external_api.py` for runnable REST calls. It uses on
|
||||
- Authenticate MCP and business API calls with `Authorization: Bearer <tnr_sk_...>`. Never ask the user to paste a key into chat or place one in repository files.
|
||||
- All eight generation POST routes require `Idempotency-Key` and return HTTP `202`; `202` is durable acceptance, not a media result.
|
||||
- Retry an uncertain submission only with the exact same body and the same idempotency key. A polling timeout is not permission to generate again.
|
||||
- Use stable references such as `objectKey`, project resource ID, or asset ID in generation requests. Use `/assets/read-url` only for temporary preview/download access.
|
||||
- Use stable references such as `objectKey`, project resource ID, or asset ID where each operation permits them. Image edit/redraw is stricter: `sourceReferenceId` accepts only a registered project resource ID or asset ID; upload confirmation alone is not enough. Use `/assets/read-url` only for temporary preview/download access.
|
||||
- Preserve both warning channels after completion. A general `warning` can coexist with `sliceWarning`; do not discard either.
|
||||
- Do not invent missing derivatives. A source-preserved warning means the main source remains usable but requested post-processing failed. A slice warning means the complete transparent sheet is usable but individual slices are absent.
|
||||
- For successful `style="pixelArt"`, treat completed-result and nested resource/asset dimensions as the final logical-grid PNG dimensions. They may differ from `size`, `imageSize`, the provider image, and `canvasCompletion.placeholder`; do not rescale or reject the artifact to match those inputs.
|
||||
- Keep generated artifacts in the canvas and asset library together. Character animation accepts `assetFolderId` and `assetLabel`; its completed result directly returns the final `assetKind="character-animation"` resource and asset with formal sequence fields. Do not create a duplicate first-frame record.
|
||||
|
||||
## Documentation Navigation
|
||||
|
||||
@@ -50,7 +50,7 @@ Every generation row requires a stable `Idempotency-Key` header and returns HTTP
|
||||
| Capability | POST path | Required body fields | Common optional body fields |
|
||||
| --- | --- | --- | --- |
|
||||
| Image generation | `/api/external/v1/editor/images/generations` | `prompt` | `kind`, `style`, `model`, `aspectRatio`, `imageSize`, `size`, `referenceImageSrcs`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
|
||||
| Image edit/redraw | `/api/external/v1/editor/images/edits` | `prompt`, `sourceImageSrc` | `referenceImageSrcs`, `model`, `size`, `projectId`, `assetFolderId`, `assetLabel`, `sourceResourceId`, `targetLayerId`, `canvasCompletion` |
|
||||
| Image edit/redraw | `/api/external/v1/editor/images/edits` | `prompt`, `sourceReferenceId` | `referenceImageSrcs`, `model`, `size`, `projectId`, `assetFolderId`, `assetLabel`, `targetLayerId`, `canvasCompletion` |
|
||||
| Icon spritesheet | `/api/external/v1/editor/icon-spritesheets/generations` | `referenceId`, `iconDescriptions` | `style`, `referenceImageSrcs`, `screenColor`, `model`, `aspectRatio`, `imageSize`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
|
||||
| UI asset extraction | `/api/external/v1/editor/ui-designs/assets/extractions` | `sourceImageSrc`, `aspectRatio`, `imageSize` | `screenColor`, `model`, `referenceImageSrcs`, `projectId`, `assetFolderId`, `spritesheetLabel`, `canvasCompletion` |
|
||||
| Character animation | `/api/external/v1/editor/character-animations/generations` | `sourceLayerId`, `sourceImageSrc`, `sourceWidth`, `sourceHeight`, `promptText`, `resolution`, `ratio`, `frameCount`, `durationSeconds`, `model` | `projectId`, `sourceResourceId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
|
||||
@@ -82,13 +82,13 @@ After confirming a local upload, pass its stable `objectKey` into operations tha
|
||||
| Target capability | Field |
|
||||
| --- | --- |
|
||||
| Image generation | `referenceImageSrcs` |
|
||||
| Image edit/redraw | `sourceImageSrc`; additional references in `referenceImageSrcs` |
|
||||
| Image edit/redraw | `sourceReferenceId` must be a registered project resource ID or asset ID; additional references remain in `referenceImageSrcs` |
|
||||
| Icon spritesheet | Register the primary spec as an `assetKind="icon-spec"` project resource or asset, then pass its returned ID as `referenceId`; additional style references remain in `referenceImageSrcs` |
|
||||
| UI design extraction | `sourceImageSrc`; additional references in `referenceImageSrcs` |
|
||||
| Character animation | `sourceImageSrc` |
|
||||
| Video with image references | `referenceImageSrcs` |
|
||||
|
||||
Use video/audio reference arrays only with models that support them. Do not pass an expiring signed read URL as a generation reference.
|
||||
For image edit/redraw, confirming an upload is not sufficient: create a project resource or asset-library record first, then pass that record's ID as `sourceReferenceId`. The main source never accepts objectKey, URL, Data URL, or Blob URL. Use video/audio reference arrays only with models that support them. Do not pass an expiring signed read URL as a generation reference.
|
||||
|
||||
The icon-spritesheet primary `referenceId` is intentionally stricter than ordinary image references: it accepts only a current-owner project resource ID or asset ID whose authoritative `assetKind` is `icon-spec`. It does not accept an `objectKey`, URL, Data URL, or Blob URL.
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ A minimal `canvasCompletion` is:
|
||||
}
|
||||
```
|
||||
|
||||
`dialogId` is optional. Do not reconstruct canvas state from completion results. Reload the project and asset library when complete authoritative snapshots are needed.
|
||||
`dialogId` is optional. The placeholder supplies canvas placement and completion coordinates; it is not a final media pixel-size constraint. For successful pixel-art snapping, the result layer uses the final logical-grid PNG dimensions even when they differ from the placeholder. Do not reconstruct canvas state from completion results. Reload the project and asset library when complete authoritative snapshots are needed.
|
||||
|
||||
Character animation accepts `assetFolderId` and `assetLabel` and persists the final transparent sequence directly. Its completed compact result includes the authoritative `assetKind="character-animation"` resource and asset with `imageSequenceFrames` and `imageSequenceDurationMs`. Use those records directly and never synthesize a duplicate asset from the first frame.
|
||||
|
||||
@@ -145,7 +145,7 @@ Carry the current art spec in `generationInputs.artSpec` and reflect important c
|
||||
The top-level `style` field is not the art spec's visual-style prose. It appends a short server-side clause to the prompt sent to the provider and enables deterministic post-processing:
|
||||
|
||||
- Omitted, `null`, empty string, or `"none"`: no clause is appended and no post-processing runs, without warning.
|
||||
- `"pixelArt"`: append one short pixel-art line to the end of the prompt sent to the provider, and enable pixel-art snapping, for ordinary image generation, `kind: "character"`, and icon spritesheet generation. The line is appended, not substituted — the rest of your prompt is unchanged. For the exact per-kind wording, read the `style` field description in the OpenAPI document; it is the contract, and this guide deliberately does not copy it.
|
||||
- `"pixelArt"`: append one short pixel-art line to the end of the prompt sent to the provider, and enable pixel-art snapping, for ordinary image generation, `kind: "character"`, and icon spritesheet generation. On successful snapping, each detected grid cell becomes one output pixel and the logical-grid PNG is persisted directly; it is not resized back to `size`, `imageSize`, the provider image, or `canvasCompletion.placeholder`. The line is appended, not substituted — the rest of your prompt is unchanged. For the exact per-kind wording, read the `style` field description in the OpenAPI document; it is the contract, and this guide deliberately does not copy it.
|
||||
- Unknown strings, or `"pixelArt"` on unsupported kinds such as `spec`, `quick-edit`, `ui-design`, or `publication-material`: continue without style processing and return `warning.code: "unsupported-image-style"`.
|
||||
- Non-string JSON values: malformed request, HTTP `400`.
|
||||
|
||||
@@ -169,6 +169,8 @@ client.generate_image(
|
||||
)
|
||||
```
|
||||
|
||||
Image edit/redraw has a stricter main-source identity rule. After upload confirmation, create either a project resource or an asset-library record and pass its `resourceId` or `assetId` as `sourceReferenceId`. Do not pass the uploaded objectKey as the main source; objectKey remains valid only for auxiliary `referenceImageSrcs` where the OpenAPI permits it.
|
||||
|
||||
Icon spritesheet generation has a stricter primary-spec contract. After upload confirmation, create a project resource or asset record with `assetKind: "icon-spec"`, retain its returned `resourceId` or `assetId`, and pass that ID as `referenceId`. The primary spec does not accept the uploaded `objectKey` directly; only additional style references may continue to use stable object keys in `referenceImageSrcs`.
|
||||
|
||||
For character animation from a local-only source, use actual dimensions and a stable synthetic layer ID:
|
||||
@@ -196,7 +198,7 @@ Do not guess dimensions or pass a temporary signed read URL. See `authentication
|
||||
|
||||
The completed `result` may contain stable artifact fields such as:
|
||||
|
||||
- `objectKey`, media type, dimensions, or task ID.
|
||||
- `objectKey`, media type, dimensions, or task ID. For successful `pixelArt`, image `width`/`height`, icon `spritesheetWidth`/`spritesheetHeight`, and nested resource/asset dimensions are the actual final logical-grid PNG dimensions rather than requested, provider, or placeholder dimensions.
|
||||
- Sound-effect `durationSeconds` is the probed MP3 duration and `loop` is the frozen request boolean; neither is inferred from Prompt text.
|
||||
- `resource`, `resourceId`, or equivalent canvas reference.
|
||||
- `asset`, `assetId`, or equivalent library reference.
|
||||
@@ -209,6 +211,10 @@ It deliberately excludes a complete project/canvas/library snapshot, Data URL, B
|
||||
|
||||
Interpret warnings only after the query reaches `status=completed`. The query-level `warning` is display-ready text. Compact `result.warning` and `result.sliceWarning` preserve structured artifact semantics.
|
||||
|
||||
### Delivery-size normalization result before pixel-art snapping
|
||||
|
||||
`result.warning.code: "dimension-restore-fallback"` records only the delivery-size normalization result established before any subsequent `pixelArt` snapping: the provider image could not be safely normalized, so its dimensions were preserved at that processing boundary. It does not describe or constrain the dimensions after `pixelArt`; if snapping succeeds, use the completed result's actual logical-grid dimensions as authoritative.
|
||||
|
||||
### Source-preserved post-processing failure
|
||||
|
||||
When `result.warning.code` is `postprocess-failed-source-preserved`:
|
||||
|
||||
@@ -548,13 +548,16 @@ class GenarrativeExternalClient:
|
||||
idempotency_key=idempotency_key,
|
||||
)
|
||||
|
||||
def edit_image(self, prompt: str, source_image_src: str, **fields: Any) -> Any:
|
||||
def edit_image(self, prompt: str, source_reference_id: str, **fields: Any) -> Any:
|
||||
source_reference_id = source_reference_id.strip()
|
||||
if not source_reference_id:
|
||||
raise GenarrativeApiError("source_reference_id must be a registered resource or asset ID")
|
||||
self._apply_canvas_session_fields(fields, prompt, 1024, 1024)
|
||||
prompt = self._apply_art_spec(fields, prompt)
|
||||
idempotency_key = fields.pop("idempotencyKey", None)
|
||||
return self.submit_and_wait_generation(
|
||||
"/api/external/v1/editor/images/edits",
|
||||
{"prompt": prompt, "sourceImageSrc": source_image_src, **fields},
|
||||
{"prompt": prompt, "sourceReferenceId": source_reference_id, **fields},
|
||||
idempotency_key=idempotency_key,
|
||||
)
|
||||
|
||||
|
||||
@@ -69,23 +69,8 @@ jobs:
|
||||
- name: Install npm dependencies
|
||||
run: bash scripts/ci-npm-ci-with-retry.sh
|
||||
|
||||
- name: Run repository lint gates
|
||||
run: npm run lint
|
||||
|
||||
- name: Build web applications
|
||||
run: npm run build
|
||||
|
||||
- name: Validate content data
|
||||
run: npm run check:content
|
||||
|
||||
- name: Check committed whitespace
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base_ref="${SPACETIME_SCHEMA_BASE_REF:-}"
|
||||
test -n "${base_ref}"
|
||||
git cat-file -e "${base_ref}^{commit}"
|
||||
git diff --check "${base_ref}"...HEAD
|
||||
- name: Run repository checks
|
||||
run: npm run check:repository-ci
|
||||
|
||||
frontend-tests:
|
||||
name: Frontend tests
|
||||
|
||||
@@ -39,6 +39,8 @@ temp*build*/
|
||||
/apps/ai-game-creator-shell/game-creator.config.local.json
|
||||
/apps/mobile-shell/.expo/
|
||||
/apps/mobile-shell/.expo-export-smoke/
|
||||
/apps/preview-deployer-web/dist/
|
||||
/apps/preview-deployer-web/node_modules/
|
||||
/server-rs/.spacetimedb/
|
||||
/server-rs/.data/
|
||||
/public/generated-animations
|
||||
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
npm run format:staged
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
npm run check:pre-push-master -- "$@"
|
||||
@@ -26,6 +26,12 @@
|
||||
- 后续新增 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`;历史文档不要求批量重命名,除非本次任务明确涉及。
|
||||
- 工程修改要同步更新对应 `docs/` 文档;产生长期有效的架构约定、接口变化、排障经验、开发流程或协作规则时,同步更新 `docs/project-memory/shared-memory/`。
|
||||
- 默认保持系统简洁:优先复用、修改、扩展现有系统、页面和公共组件,不新建平行系统或平行页面。
|
||||
- 对已明确退役且不存在现役调用方、公开契约、持久化数据、活跃实例或迁移要求的对象,坚持“四不写”:
|
||||
1. 不写历史兼容代码。
|
||||
2. 不写用于维持退役行为的防御性兼容测试。
|
||||
3. 不写仅说明其曾存在或已删除的墓碑注释。
|
||||
4. 不写仅记录其已删除的墓碑文档;直接将权威文档更新为当前状态。
|
||||
- 公开 API、持久化数据、SpacetimeDB schema、跨版本重放、活跃实例和正式迁移不适用“四不写”;必要兼容应最小化、白名单化并配套契约或迁移测试,迁移完成后同步删除兼容实现与对应测试。
|
||||
- UI 面板中不要默认写功能说明、规则描述或开发解释文案;移动端优先,同时保证网页端可正常显示和操作。
|
||||
- 点击按钮弹出独立面板的设计,不要实现成在当前面板下面追加内容。
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"agentMode": "codex_app_server",
|
||||
"llm": {
|
||||
"apiKey": "",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
@@ -15,9 +16,5 @@
|
||||
"retryBackoffMs": 500
|
||||
},
|
||||
"agentLlm": {},
|
||||
"editorApi": {
|
||||
"baseUrl": "http://127.0.0.1:8082",
|
||||
"apiKey": ""
|
||||
},
|
||||
"mcpServers": {}
|
||||
}
|
||||
|
||||
+11
@@ -10,7 +10,9 @@
|
||||
"dependencies": {
|
||||
"@lexical/react": "^0.47.0",
|
||||
"@lexical/utils": "^0.47.0",
|
||||
"@tauri-apps/api": "^2.11.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "2.3.2",
|
||||
"@tauri-apps/plugin-http": "^2.5.9",
|
||||
"@tauri-apps/plugin-opener": "~2",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"lexical": "^0.47.0",
|
||||
@@ -2208,6 +2210,15 @@
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-http": {
|
||||
"version": "2.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-http/-/plugin-http-2.5.9.tgz",
|
||||
"integrity": "sha512-lCiY0+vs4HvIUSvZrBs8TC3TiCB0MOPRmiUjTq4prW7SlcJE2jdLeT6KBsJrT9Tlplufl7W1pY6SFAO3gCWxDA==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-opener": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.4.tgz",
|
||||
|
||||
@@ -36,7 +36,9 @@
|
||||
"dependencies": {
|
||||
"@lexical/react": "^0.47.0",
|
||||
"@lexical/utils": "^0.47.0",
|
||||
"@tauri-apps/api": "^2.11.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "2.3.2",
|
||||
"@tauri-apps/plugin-http": "^2.5.9",
|
||||
"@tauri-apps/plugin-opener": "~2",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"lexical": "^0.47.0",
|
||||
|
||||
@@ -253,12 +253,24 @@ export function defaultRuntimeConfigDirCandidates({
|
||||
),
|
||||
);
|
||||
} else {
|
||||
const configuredRoot = environment.XDG_CONFIG_HOME;
|
||||
const posixAbsoluteConfiguredRoot =
|
||||
configuredRoot && path.posix.isAbsolute(configuredRoot);
|
||||
const hostAbsoluteConfiguredRoot =
|
||||
configuredRoot &&
|
||||
!posixAbsoluteConfiguredRoot &&
|
||||
path.isAbsolute(configuredRoot);
|
||||
const configRoot =
|
||||
environment.XDG_CONFIG_HOME &&
|
||||
path.posix.isAbsolute(environment.XDG_CONFIG_HOME)
|
||||
? environment.XDG_CONFIG_HOME
|
||||
configuredRoot &&
|
||||
(posixAbsoluteConfiguredRoot || hostAbsoluteConfiguredRoot)
|
||||
? configuredRoot
|
||||
: path.posix.join(homeDirectory, '.config');
|
||||
pushUnique(candidates, path.posix.join(configRoot, appIdentifier));
|
||||
pushUnique(
|
||||
candidates,
|
||||
hostAbsoluteConfiguredRoot
|
||||
? path.join(configRoot, appIdentifier)
|
||||
: path.posix.join(configRoot, appIdentifier),
|
||||
);
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
@@ -114,9 +114,22 @@ const rustSharedContractSource = fs.readFileSync(
|
||||
'utf8',
|
||||
);
|
||||
const allowedUncalledTauriCommands = [
|
||||
'archive_failed_local_project_resource_edit',
|
||||
'chat_with_game_creator_agent',
|
||||
'commit_local_project_asset',
|
||||
'confirm_local_project_asset_canvas_generation_service_identity',
|
||||
'create_local_project_asset_canvas_draft',
|
||||
'discard_local_project_asset_canvas_draft',
|
||||
'generate_local_project_asset_canvas_image',
|
||||
'open_game_creator_launcher_window',
|
||||
'open_game_creator_workspace_window',
|
||||
'read_local_project_asset_canvas_draft',
|
||||
'read_local_project_asset_canvas_media',
|
||||
'recover_local_project_asset_canvas_transactions',
|
||||
'recover_local_project_asset_canvas_generations',
|
||||
'stage_local_project_asset_canvas_image',
|
||||
'store_local_project_asset_canvas_media',
|
||||
'update_local_project_asset_canvas_draft',
|
||||
];
|
||||
const sourceExtensions = new Set([
|
||||
'.json',
|
||||
@@ -651,10 +664,12 @@ async function runConfigWizardRegressionChecks() {
|
||||
apiKind: 'openai_responses',
|
||||
},
|
||||
);
|
||||
assert.equal(updatedPrimary.agentMode, 'provider');
|
||||
await writeGameCreatorWizardConfig(overlayState, updatedPrimary);
|
||||
const reloadedState =
|
||||
await readGameCreatorWizardConfigState(overlayConfigDir);
|
||||
assert.equal(reloadedState.effectiveConfig.llm.apiKey, 'fixture-new-key');
|
||||
assert.equal(reloadedState.effectiveConfig.agentMode, 'provider');
|
||||
assert.equal(reloadedState.effectiveConfig.llm.model, 'new-model');
|
||||
assert.equal(
|
||||
reloadedState.effectiveConfig.llm.baseUrl,
|
||||
@@ -1160,6 +1175,12 @@ if (defaultAppConfig.llm?.apiKey !== '') {
|
||||
throw new Error('AI game creator shell default llm.apiKey must stay empty');
|
||||
}
|
||||
|
||||
if (defaultAppConfig.agentMode !== 'codex_app_server') {
|
||||
throw new Error(
|
||||
'AI game creator shell default agentMode must be codex_app_server',
|
||||
);
|
||||
}
|
||||
|
||||
const allowedLlmReasoningEfforts = new Set([
|
||||
'default',
|
||||
'low',
|
||||
@@ -1186,9 +1207,9 @@ for (const [agentId, agentConfig] of Object.entries(
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultAppConfig.editorApi?.apiKey !== '') {
|
||||
if (defaultAppConfig.editorApi !== undefined) {
|
||||
throw new Error(
|
||||
'AI game creator shell default editorApi.apiKey must stay empty',
|
||||
'AI game creator shell ordinary default config must not contain editorApi',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1216,10 +1237,10 @@ if (
|
||||
clientWindow.width !== 1280 ||
|
||||
clientWindow.height !== 800 ||
|
||||
clientWindow.minWidth !== 1280 ||
|
||||
clientWindow.minHeight !== 800
|
||||
clientWindow.minHeight !== 720
|
||||
) {
|
||||
throw new Error(
|
||||
'AI game creator shell client window must keep the landscape workbench size',
|
||||
'AI game creator shell client window must default to 1280x800 and stay at least 1280x720',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1451,6 +1472,22 @@ if (
|
||||
);
|
||||
}
|
||||
|
||||
const gameChatReleaseWindows = gameChatReleaseTauriConfig.app?.windows ?? [];
|
||||
const gameChatReleaseClientWindow = gameChatReleaseWindows[0];
|
||||
if (
|
||||
gameChatReleaseWindows.length !== 1 ||
|
||||
gameChatReleaseClientWindow?.label !== 'client' ||
|
||||
gameChatReleaseClientWindow?.url !== 'index.html' ||
|
||||
gameChatReleaseClientWindow?.width !== 1280 ||
|
||||
gameChatReleaseClientWindow?.height !== 800 ||
|
||||
gameChatReleaseClientWindow?.minWidth !== 1280 ||
|
||||
gameChatReleaseClientWindow?.minHeight !== 720
|
||||
) {
|
||||
throw new Error(
|
||||
'AI game creator game-chat client window must default to 1280x800 and stay at least 1280x720',
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
tauriConfig.version !== '0.1.0' ||
|
||||
packageConfig.version !== '0.1.0' ||
|
||||
@@ -1530,7 +1567,6 @@ for (const snippet of [
|
||||
'$verified = $targetItem.GetAccessControl()',
|
||||
'$rules.Count -ne 1',
|
||||
'[System.Security.AccessControl.FileSystemRights]::FullControl',
|
||||
"runChildCapture('powershell.exe'",
|
||||
"'-NoProfile'",
|
||||
"'-Command'",
|
||||
'windowsPrivateAclScript',
|
||||
@@ -1543,6 +1579,11 @@ for (const snippet of [
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!/runChildCapture\(\s*['"]powershell\.exe['"]/u.test(configWizardSource)) {
|
||||
throw new Error(
|
||||
'AI game creator config wizard guardrail drifted: runChildCapture(powershell.exe)',
|
||||
);
|
||||
}
|
||||
if (/\bGet-Acl\b/u.test(configWizardSource)) {
|
||||
throw new Error(
|
||||
'AI game creator config wizard must not rely on Get-Acl module auto-loading',
|
||||
@@ -1569,7 +1610,8 @@ for (const snippet of [
|
||||
'const GAME_CREATOR_LOCAL_CONFIG_FILE_NAME: &str = "game-creator.config.local.json"',
|
||||
'const DEFAULT_GAME_CREATOR_APP_CONFIG_JSON: &str = include_str!("../../game-creator.config.json")',
|
||||
'fn configure_game_creator_runtime_config_dir(',
|
||||
'app.path().app_config_dir()?',
|
||||
'game_creator_runtime_config_dir()',
|
||||
'.unwrap_or_else(|| app.path().app_config_dir())?',
|
||||
'fn load_game_creator_app_config()',
|
||||
'fn read_game_creator_app_config()',
|
||||
'fn write_game_creator_app_config(',
|
||||
@@ -1663,7 +1705,8 @@ for (const snippet of [
|
||||
"'write_game_creator_app_config'",
|
||||
'aria-label="运行时配置"',
|
||||
'LLM API Key',
|
||||
'画板 API Key',
|
||||
'External Editor Base URL',
|
||||
'External Editor API Key',
|
||||
'runtime_config.save',
|
||||
"'/run:运行自检,启动本地 HTTP 预览并载入客户端运行视图'",
|
||||
"'activate_local_game_preview'",
|
||||
|
||||
@@ -211,6 +211,7 @@ export function buildGameCreatorWizardConfig(existingConfig, llmInput) {
|
||||
}
|
||||
return {
|
||||
...source,
|
||||
agentMode: 'provider',
|
||||
llm: {
|
||||
...previousLlm,
|
||||
apiKey,
|
||||
@@ -290,21 +291,25 @@ export async function secureWindowsGameCreatorPathForCurrentUser(
|
||||
targetPath,
|
||||
{ isDirectory },
|
||||
) {
|
||||
const result = await runChildCapture('powershell.exe', [
|
||||
'-NoLogo',
|
||||
'-NoProfile',
|
||||
'-NonInteractive',
|
||||
'-ExecutionPolicy',
|
||||
'Bypass',
|
||||
'-Command',
|
||||
windowsPrivateAclScript,
|
||||
], {
|
||||
env: {
|
||||
...process.env,
|
||||
GENARRATIVE_AGC_PRIVATE_PATH: targetPath,
|
||||
GENARRATIVE_AGC_PRIVATE_IS_DIRECTORY: String(isDirectory),
|
||||
const result = await runChildCapture(
|
||||
'powershell.exe',
|
||||
[
|
||||
'-NoLogo',
|
||||
'-NoProfile',
|
||||
'-NonInteractive',
|
||||
'-ExecutionPolicy',
|
||||
'Bypass',
|
||||
'-Command',
|
||||
windowsPrivateAclScript,
|
||||
],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
GENARRATIVE_AGC_PRIVATE_PATH: targetPath,
|
||||
GENARRATIVE_AGC_PRIVATE_IS_DIRECTORY: String(isDirectory),
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
if (result.code !== 0 || result.signal) {
|
||||
const detail = result.stderr.trim() || result.stdout.trim();
|
||||
throw new Error(
|
||||
|
||||
@@ -46,6 +46,8 @@ export function buildProcessSessionFixtureSource({
|
||||
' if (!echoed && line === challenge) {',
|
||||
' echoed = true;',
|
||||
" console.log(echoPrefix + ' ' + challenge);",
|
||||
" } else if (line === challenge + ':stop') {",
|
||||
' stop();',
|
||||
' }',
|
||||
' }',
|
||||
'});',
|
||||
|
||||
@@ -4,8 +4,9 @@ import fs from 'node:fs/promises';
|
||||
import http from 'node:http';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const appRoot = path.resolve(new URL('..', import.meta.url).pathname);
|
||||
const appRoot = fileURLToPath(new URL('..', import.meta.url));
|
||||
const localConfigPath = path.join(appRoot, 'game-creator.config.local.json');
|
||||
const projectRoot = path.join(
|
||||
os.tmpdir(),
|
||||
@@ -884,7 +885,28 @@ function readBrowserDom(url) {
|
||||
}
|
||||
|
||||
function resolveChromeBin() {
|
||||
const windowsRoot = path.parse(os.homedir()).root;
|
||||
for (const candidate of [
|
||||
path.join(
|
||||
windowsRoot,
|
||||
'Program Files/Google/Chrome/Application/chrome.exe',
|
||||
),
|
||||
path.join(
|
||||
windowsRoot,
|
||||
'Program Files (x86)/Google/Chrome/Application/chrome.exe',
|
||||
),
|
||||
path.join(
|
||||
os.homedir(),
|
||||
'AppData/Local/Google/Chrome/Application/chrome.exe',
|
||||
),
|
||||
path.join(
|
||||
windowsRoot,
|
||||
'Program Files/Microsoft/Edge/Application/msedge.exe',
|
||||
),
|
||||
path.join(
|
||||
windowsRoot,
|
||||
'Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
|
||||
),
|
||||
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
||||
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
||||
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
||||
|
||||
@@ -45,12 +45,16 @@ function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) {
|
||||
if (separatorIndex < 0) {
|
||||
return ['dev', ...args, '--config', configOverride];
|
||||
}
|
||||
const separatedArguments = args.slice(separatorIndex);
|
||||
if (separatedArguments[1] !== '--') {
|
||||
separatedArguments.unshift('--');
|
||||
}
|
||||
return [
|
||||
'dev',
|
||||
...args.slice(0, separatorIndex),
|
||||
'--config',
|
||||
configOverride,
|
||||
...args.slice(separatorIndex),
|
||||
...separatedArguments,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
+157
@@ -702,10 +702,29 @@ version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie_store"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206"
|
||||
dependencies = [
|
||||
"cookie",
|
||||
"document-features",
|
||||
"idna",
|
||||
"log",
|
||||
"publicsuffix",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
@@ -889,6 +908,12 @@ version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
||||
|
||||
[[package]]
|
||||
name = "data-url"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
|
||||
|
||||
[[package]]
|
||||
name = "dbus"
|
||||
version = "0.9.11"
|
||||
@@ -1018,6 +1043,15 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
|
||||
dependencies = [
|
||||
"litrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dom_query"
|
||||
version = "0.27.0"
|
||||
@@ -1131,6 +1165,15 @@ version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.1"
|
||||
@@ -1591,6 +1634,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chromiumoxide",
|
||||
"futures",
|
||||
"getrandom 0.3.4",
|
||||
"http",
|
||||
"image",
|
||||
"jsonschema",
|
||||
@@ -1616,6 +1660,7 @@ dependencies = [
|
||||
"tauri-build",
|
||||
"tauri-plugin-clipboard-manager",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-http",
|
||||
"tauri-plugin-opener",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
@@ -1835,6 +1880,25 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap 2.14.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
@@ -1955,6 +2019,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
@@ -1979,6 +2044,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2171,10 +2237,23 @@ checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"image-webp",
|
||||
"moxcms",
|
||||
"num-traits",
|
||||
"png 0.18.1",
|
||||
"tiff",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
|
||||
dependencies = [
|
||||
"byteorder-lite",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2488,6 +2567,12 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
@@ -2547,6 +2632,16 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
@@ -3734,6 +3829,22 @@ dependencies = [
|
||||
"windows 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psl-types"
|
||||
version = "2.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
|
||||
|
||||
[[package]]
|
||||
name = "publicsuffix"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf"
|
||||
dependencies = [
|
||||
"idna",
|
||||
"psl-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.30"
|
||||
@@ -3987,8 +4098,12 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"cookie",
|
||||
"cookie_store",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
@@ -3998,6 +4113,8 @@ dependencies = [
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
@@ -4021,6 +4138,7 @@ dependencies = [
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams 0.4.2",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5103,6 +5221,30 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-http"
|
||||
version = "2.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5bd512048e1985b7ec78f96d99083e2ddaf7e0d906b2b63c44ce5bb8b894067"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cookie_store",
|
||||
"data-url",
|
||||
"http",
|
||||
"regex",
|
||||
"reqwest 0.12.28",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-fs",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"url",
|
||||
"urlpattern",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-opener"
|
||||
version = "2.5.4"
|
||||
@@ -5755,6 +5897,12 @@ dependencies = [
|
||||
"unic-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-general-category"
|
||||
version = "1.1.0"
|
||||
@@ -6176,6 +6324,15 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com"
|
||||
version = "0.38.2"
|
||||
|
||||
@@ -19,8 +19,9 @@ agent-runtime-core = { path = "../../../server-rs/crates/agent-runtime-core" }
|
||||
base64 = "0.22"
|
||||
chromiumoxide = "0.9.1"
|
||||
futures = "0.3"
|
||||
getrandom = "0.3"
|
||||
http = "1"
|
||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp"] }
|
||||
jsonschema = { version = "0.49.3", default-features = false }
|
||||
oxc_allocator = "0.143.0"
|
||||
oxc_ast = "0.143.0"
|
||||
@@ -37,10 +38,11 @@ similar = "2.7"
|
||||
platform-llm = { path = "../../../server-rs/crates/platform-llm" }
|
||||
platform-agent = { path = "../../../server-rs/crates/platform-agent" }
|
||||
portable-pty = "0.9"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "native-tls"] }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "native-tls"] }
|
||||
shared-contracts = { path = "../../../server-rs/crates/shared-contracts", default-features = false }
|
||||
tauri = { version = "2.11.2", features = [] }
|
||||
tauri-plugin-dialog = "2.7.1"
|
||||
tauri-plugin-http = { version = "2.5.9", default-features = false, features = ["charset", "cookies", "http2", "rustls-tls"] }
|
||||
tauri-plugin-opener = "2"
|
||||
tempfile = "3"
|
||||
tokio = { version = "1", features = ["io-util", "macros", "process", "rt-multi-thread", "signal", "sync", "time"] }
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#[path = "build_support/frontend_dist_guard.rs"]
|
||||
mod frontend_dist_guard;
|
||||
#[path = "build_support/runtime_prompt_bundle.rs"]
|
||||
mod runtime_prompt_bundle;
|
||||
|
||||
@@ -64,5 +66,14 @@ fn main() {
|
||||
.join("agent_runtime_prompt_bundle.rs");
|
||||
fs::write(&output_path, compiled.rust_source)
|
||||
.unwrap_or_else(|error| panic!("写入 Prompt Bundle 生成代码失败:{error}"));
|
||||
if !tauri_build::is_dev() {
|
||||
let frontend_dist = manifest_dir
|
||||
.parent()
|
||||
.expect("AI 游戏创作 Tauri manifest 必须位于应用目录下")
|
||||
.join("dist");
|
||||
println!("cargo:rerun-if-changed={}", frontend_dist.display());
|
||||
frontend_dist_guard::validate_frontend_dist(&frontend_dist)
|
||||
.unwrap_or_else(|error| panic!("生产 frontendDist 检查失败:{error}"));
|
||||
}
|
||||
tauri_build::build()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
const FORBIDDEN_TEST_PROJECT_PATH: &[u8] = b"/tmp/genarrative-ai-game-draft";
|
||||
|
||||
pub fn validate_frontend_dist(dist_root: &Path) -> Result<(), String> {
|
||||
let root_metadata = fs::symlink_metadata(dist_root)
|
||||
.map_err(|error| format!("读取生产 frontendDist 失败:.: {error}"))?;
|
||||
if metadata_is_link(&root_metadata) {
|
||||
return Err("生产 frontendDist 不能是符号链接:.".to_string());
|
||||
}
|
||||
if !root_metadata.is_dir() {
|
||||
return Err("生产 frontendDist 必须是目录:.".to_string());
|
||||
}
|
||||
|
||||
validate_frontend_dist_directory(dist_root, dist_root)
|
||||
}
|
||||
|
||||
fn validate_frontend_dist_directory(dist_root: &Path, directory: &Path) -> Result<(), String> {
|
||||
let mut entries = fs::read_dir(directory)
|
||||
.map_err(|error| {
|
||||
format!(
|
||||
"读取生产 frontendDist 目录失败:{}: {error}",
|
||||
relative_dist_path(dist_root, directory)
|
||||
)
|
||||
})?
|
||||
.map(|entry| {
|
||||
entry.map(|entry| entry.path()).map_err(|error| {
|
||||
format!(
|
||||
"读取生产 frontendDist 目录项失败:{}: {error}",
|
||||
relative_dist_path(dist_root, directory)
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<PathBuf>, String>>()?;
|
||||
entries.sort();
|
||||
|
||||
for path in entries {
|
||||
let relative_path = relative_dist_path(dist_root, &path);
|
||||
let metadata = fs::symlink_metadata(&path)
|
||||
.map_err(|error| format!("读取生产 frontendDist 资源失败:{relative_path}: {error}"))?;
|
||||
if metadata_is_link(&metadata) {
|
||||
return Err(format!(
|
||||
"生产 frontendDist 不能包含符号链接:{relative_path}"
|
||||
));
|
||||
}
|
||||
if metadata.is_dir() {
|
||||
validate_frontend_dist_directory(dist_root, &path)?;
|
||||
continue;
|
||||
}
|
||||
if !metadata.is_file() {
|
||||
return Err(format!(
|
||||
"生产 frontendDist 只能包含普通文件和目录:{relative_path}"
|
||||
));
|
||||
}
|
||||
|
||||
let bytes = fs::read(&path)
|
||||
.map_err(|error| format!("读取生产 frontendDist 文件失败:{relative_path}: {error}"))?;
|
||||
if contains_bytes(&bytes, FORBIDDEN_TEST_PROJECT_PATH) {
|
||||
return Err(format!(
|
||||
"生产 frontendDist 包含禁止的测试默认路径 /tmp/genarrative-ai-game-draft:{relative_path}"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn contains_bytes(haystack: &[u8], needle: &[u8]) -> bool {
|
||||
!needle.is_empty()
|
||||
&& haystack
|
||||
.windows(needle.len())
|
||||
.any(|window| window == needle)
|
||||
}
|
||||
|
||||
fn relative_dist_path(dist_root: &Path, path: &Path) -> String {
|
||||
path.strip_prefix(dist_root)
|
||||
.ok()
|
||||
.filter(|relative| !relative.as_os_str().is_empty())
|
||||
.map(|relative| relative.display().to_string())
|
||||
.unwrap_or_else(|| ".".to_string())
|
||||
}
|
||||
|
||||
fn metadata_is_link(metadata: &fs::Metadata) -> bool {
|
||||
if metadata.file_type().is_symlink() {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
|
||||
const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0400;
|
||||
if metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
@@ -9,6 +9,10 @@
|
||||
"core:image:allow-rgba",
|
||||
"core:image:allow-size",
|
||||
"core:resources:allow-close",
|
||||
{
|
||||
"identifier": "http:default",
|
||||
"allow": [{ "url": "https://dev.genarrative.world/api/*" }]
|
||||
},
|
||||
"opener:default"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -16,4 +16,4 @@ git.inspect 会返回 commitSnapshotFingerprint;只有当前非零 revision
|
||||
|
||||
用户输入请求协议:user.input_request 使用 {"questions":[{"id":"唯一 snake_case","header":"最多 12 字符","question":"单句问题","options":[{"label":"短选项","description":"一条影响说明"},{"label":"另一选项","description":"一条影响说明"}]}]},一次 1-3 题、每题 2-3 个选项且始终允许自由输入。它必须是本轮唯一函数调用,不得同批调用 update_agent_plan、其他动作函数或 respond_to_user。只有 Project Supervisor 或没有父委派身份的静态 Agent 开发试聊可直接调用;委派专业 Agent 和动态隔离 child 必须把澄清需要回传父 Agent。
|
||||
|
||||
静态委派协议:新 agent.delegate 必须提交 1-8 条 acceptanceCriteria、0-16 个精确项目内非私有 expectedArtifacts,以及 nullable repairOfDelegationId/runId。专业 Agent 收到的 task 会携带完整合同。Supervisor 认领回执后必须区分 evidence-ready 与 needs-repair;前者仍需语义验收,后者不能作为成功。
|
||||
静态委派协议:新 agent.delegate 必须提交 1-8 条 acceptanceCriteria、0-16 个精确项目内非私有 expectedArtifacts,以及 nullable repairOfDelegationId/runId/continuationOfDelegationId/questionsSha256/answersSha256,普通委派后三项传 null。专业 Agent 收到的 task 会携带完整合同。Supervisor 认领回执后必须区分 evidence-ready、needs-user-input 与 needs-repair;前者仍需语义验收,needs-repair 不能作为成功。专业 Agent 若缺少会实质改变结果的用户事实,不能调用 user.input_request,必须以最终回复首行 `AGC_NEEDS_USER_INPUT_V1`,下一行短 JSON `{"questions":[...]}` 返回 1-3 个结构化问题;Runtime 会把它作为内部回执交给 Supervisor。Supervisor 对每个原 delivery 逐一用现有 user.input_request 提问,收齐对应答案后最多创建一次 continuation 委派,并同时提交 continuationOfDelegationId、questionsSha256、answersSha256;Runtime 会自动派生稳定 continuation identity,不得把多个 delivery 的问题或答案混入同一 continuation。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "genarrative.agent-runtime",
|
||||
"version": "2026-08-07.2",
|
||||
"version": "2026-08-11.1",
|
||||
"sections": {
|
||||
"common": "common.md",
|
||||
"isolatedTemplateCatalogIntro": "isolated-template-catalog-intro.md",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user