Compare commits
198 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bcff8f1957 | |||
| dfcd67bd05 | |||
| 8d4d7ecc95 | |||
| 0a6d550d73 | |||
| 2412700e66 | |||
| fb558baa8d | |||
| badb886790 | |||
| 0a11507266 | |||
| a27798be77 | |||
| 0723df0e6d | |||
| bb16c41938 | |||
| 3c00bcd2e0 | |||
| c188accafb | |||
| a2fc6054e3 | |||
| 5736ad62b4 | |||
| 2d82ac55e4 | |||
| dea6576f89 | |||
| 02b4b6955d | |||
| a43c84fd75 | |||
| 9a326c940a | |||
| 0edefc67ec | |||
| c1342910c5 | |||
| ae78e3bd5b | |||
| 84ec79a8bb | |||
| 92832e56c2 | |||
| 18ae260069 | |||
| 6a6d1f85d6 | |||
| f30a55069a | |||
| d58e5cbebd | |||
| 9e70f7a07c | |||
| dc3e92433f | |||
| cef79f629a | |||
| 6cb13b3884 | |||
| a9575f0f04 | |||
| ae174ecb06 | |||
| 4024ea53ed | |||
| 5393ef8720 | |||
| 7ce7c11ca4 | |||
| 9820203597 | |||
| 78675326c7 | |||
| b91350f3f2 | |||
| 5379e038f6 | |||
| 57c825089b | |||
| 0073fbd188 | |||
| 299cfdcd5e | |||
| 52fbdb29f7 | |||
| 83f07fc58d | |||
| 1bbb6b7e1b | |||
| e536882479 | |||
| 6b1dfc77d9 | |||
| 6d1937e6ff | |||
| 3451c17142 | |||
| 6db06b6c97 | |||
| bae124a054 | |||
| 1fb72ba26c | |||
| 1664bc6bc8 | |||
| d1562ab65f | |||
| 676bd524ee | |||
| 8f4cce288c | |||
| 8a42b23ea4 | |||
| 38e195060b | |||
| 1303df1ee7 | |||
| 5c0e84c78a | |||
| 777f567cb4 | |||
| 3016b6a3af | |||
| 179c4c36c5 | |||
| d5f0f6de93 | |||
| fb47002bfb | |||
| 498b905bd8 | |||
| e564db43f1 | |||
| b8e73766fe | |||
| 72f268e088 | |||
| fc0dcb0782 | |||
| 44de26d204 | |||
| 82a486e72b | |||
| 4974844090 | |||
| 1f2906217b | |||
| 48b852ab04 | |||
| 840f219e18 | |||
| c4d7a76a0a | |||
| 8ee4ff5b4f | |||
| cdf41012fb | |||
| 9a8564bc1d | |||
| d793b0e4c7 | |||
| 1d23f51433 | |||
| 7d904974f2 | |||
| bd972662c7 | |||
| 87c0e167c4 | |||
| e99f3209f1 | |||
| 97de29a58f | |||
| a321243945 | |||
| 2a19ee1e27 | |||
| cad861a13d | |||
| 69f850ff82 | |||
| 7733202bb3 | |||
| 92085dfdd5 | |||
| 122835839a | |||
| 5424424bad | |||
| 5c316fc680 | |||
| e088106541 | |||
| 0b44253c9d | |||
| 392741215c | |||
| 130dc8ef9a | |||
| 21b05f9446 | |||
| 44ac7f1252 | |||
| 6725ee1b8c | |||
| 323051be29 | |||
| 6506e95fa1 | |||
| 7b67d8e9b7 | |||
| cb7bcbc67e | |||
| 21209910dd | |||
| e2fbef79e1 | |||
| 2281458ba6 | |||
| e1e4f487f5 | |||
| 4a9f4657e9 | |||
| b64c3c05ef | |||
| 9d581bbe8c | |||
| f8bcf7cf54 | |||
| 9d1f4c1975 | |||
| c3b3b47c9a | |||
| d208c44c86 | |||
| db9f452cae | |||
| 9734f78474 | |||
| cd80c085ff | |||
| 5eee62cf39 | |||
| 6fd731f56c | |||
| 29498a727d | |||
| c72d295a84 | |||
| 3bbde84f87 | |||
| 706fa3ca8c | |||
| baa1f32f8e | |||
| b2b517d5b9 | |||
| 2266d7fe89 | |||
| 942d2d36ad | |||
| 2b8ced9195 | |||
| fa02c366a9 | |||
| 969afbaf94 | |||
| 291724a984 | |||
| ea3b3d423d | |||
| b79f72391b | |||
| 55ff47c6c4 | |||
| 0b3475ef98 | |||
| 3fdbe81bfe | |||
| 59b92123b9 | |||
| c3e8be719d | |||
| a786fa7562 | |||
| 9671944e0f | |||
| ee474b8ec8 | |||
| 5b4599f147 | |||
| fe08a2643c | |||
| e6f0e6248e | |||
| aec9b61121 | |||
| eba8f25995 | |||
| 3550f046e4 | |||
| 477eb1a003 | |||
| 3a363c98ff | |||
| c932f5e8fb | |||
| 17bf54f211 | |||
| 2a0a4238d1 | |||
| de98219a94 | |||
| aeb5894b9d | |||
| 3c9039d08b | |||
| 69426ee61c | |||
| 93b36cb97c | |||
| eff046a27a | |||
| a47262b551 | |||
| 0804e29daf | |||
| 0d0d4c0915 | |||
| 3b69b48860 | |||
| d4a4d0ba3a | |||
| baaa587f2f | |||
| ce1afde307 | |||
| 262dad5563 | |||
| 1f530bc806 | |||
| 059826b06d | |||
| e9f50248b9 | |||
| 07eb59fb6d | |||
| 755f58ddfb | |||
| f76d338e69 | |||
| d825d81810 | |||
| ac12e929f2 | |||
| 0dc1eed38e | |||
| 3fbdf210be | |||
| 825b0ccf6c | |||
| f5035af504 | |||
| 3485bad2d3 | |||
| b563c82c7b | |||
| cc86d29b83 | |||
| b4a4d68c55 | |||
| 59a69f063d | |||
| 78307de9a1 | |||
| 8d269d785d | |||
| 96b06cc39b | |||
| dafcfbe6b7 | |||
| a9b462d43f | |||
| 8daf85df6c | |||
| d0fedd1d99 | |||
| 5fd26a11ee |
@@ -17,7 +17,7 @@ Prefer `scripts/genarrative_external_api.py` for runnable REST calls. It uses on
|
|||||||
2. Before the first generation in a new conversation, obtain a canvas name unless an existing `projectId` and `assetFolderId` were supplied. Create or reuse a project and a same-name asset-library folder. Retain `canvasName`, `projectId`, `assetFolderId`, and the current art spec.
|
2. Before the first generation in a new conversation, obtain a canvas name unless an existing `projectId` and `assetFolderId` were supplied. Create or reuse a project and a same-name asset-library folder. Retain `canvasName`, `projectId`, `assetFolderId`, and the current art spec.
|
||||||
3. Normalize art requests into a reusable spec. Ask only for missing values that block the selected operation. Reuse the spec until the user changes its style, subject family, palette, format, or constraints.
|
3. Normalize art requests into a reusable spec. Ask only for missing values that block the selected operation. Reuse the spec until the user changes its style, subject family, palette, format, or constraints.
|
||||||
4. Infer the operation from the user's intent. Do not ask the user to select an API unless two operations would produce materially different artifacts.
|
4. Infer the operation from the user's intent. Do not ask the user to select an API unless two operations would produce materially different artifacts.
|
||||||
5. If a reference exists only as a local file, upload and confirm it first. Pass the stable returned `objectKey` to generation; never substitute a temporary signed URL.
|
5. If a reference exists only as a local file, upload and confirm it first. Pass the stable returned `objectKey` to operations that accept object references; never substitute a temporary signed URL. For an icon-spritesheet primary spec, additionally create a project resource or asset record with `assetKind="icon-spec"`, then pass the returned resource or asset ID as `referenceId`.
|
||||||
6. For generation endpoints that support the fields, include `projectId`, `assetFolderId`, an asset label, and `canvasCompletion` so the result enters both the canvas and its same-name library folder.
|
6. For generation endpoints that support the fields, include `projectId`, `assetFolderId`, an asset label, and `canvasCompletion` so the result enters both the canvas and its same-name library folder.
|
||||||
7. Treat every generation POST as asynchronous. Send one stable `Idempotency-Key` per logical request, retain the returned `operationId`, and poll the returned `statusUrl` or `GET /api/external/v1/generations/{operationId}` according to `pollAfterMs`.
|
7. Treat every generation POST as asynchronous. Send one stable `Idempotency-Key` per logical request, retain the returned `operationId`, and poll the returned `statusUrl` or `GET /api/external/v1/generations/{operationId}` according to `pollAfterMs`.
|
||||||
8. Consume `result` only after `status=completed`. On `failed`, surface the safe error. On a client timeout or lost response, retain the operation/key; do not create a replacement request.
|
8. Consume `result` only after `status=completed`. On `failed`, surface the safe error. On a client timeout or lost response, retain the operation/key; do not create a replacement request.
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ Every generation row requires a stable `Idempotency-Key` header and returns HTTP
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| Image generation | `/api/external/v1/editor/images/generations` | `prompt` | `kind`, `style`, `model`, `aspectRatio`, `imageSize`, `size`, `referenceImageSrcs`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
|
| 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`, `sourceImageSrc` | `referenceImageSrcs`, `model`, `size`, `projectId`, `assetFolderId`, `assetLabel`, `sourceResourceId`, `targetLayerId`, `canvasCompletion` |
|
||||||
| Icon spritesheet | `/api/external/v1/editor/icon-spritesheets/generations` | `referenceImageSrc`, `iconDescriptions` | `style`, `referenceImageSrcs`, `screenColor`, `model`, `aspectRatio`, `imageSize`, `projectId`, `assetFolderId`, `assetLabel`, `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` |
|
| 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` |
|
| Character animation | `/api/external/v1/editor/character-animations/generations` | `sourceLayerId`, `sourceImageSrc`, `sourceWidth`, `sourceHeight`, `promptText`, `resolution`, `ratio`, `frameCount`, `durationSeconds`, `model` | `projectId`, `sourceResourceId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
|
||||||
| Video generation | `/api/external/v1/editor/videos/generations` | `prompt`, `model`, `aspectRatio`, `durationSeconds`, `resolution`, `mode`, `sound` | `referenceImageSrcs`, `referenceVideoSrcs`, `referenceAudioSrcs`, `webSearchEnabled`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
|
| Video generation | `/api/external/v1/editor/videos/generations` | `prompt`, `model`, `aspectRatio`, `durationSeconds`, `resolution`, `mode`, `sound` | `referenceImageSrcs`, `referenceVideoSrcs`, `referenceAudioSrcs`, `webSearchEnabled`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
|
||||||
| Sound effect | `/api/external/v1/editor/audios/sound-effects/generations` | `prompt`, `duration` | `model`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
|
| Sound effect | `/api/external/v1/editor/audios/sound-effects/generations` | `prompt` | `model`, `duration`, `loop`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
|
||||||
| Background music | `/api/external/v1/editor/audios/background-music/generations` | `gptDescriptionPrompt`, `makeInstrumental` | `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
|
| Background music | `/api/external/v1/editor/audios/background-music/generations` | `gptDescriptionPrompt`, `makeInstrumental` | `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
|
||||||
|
|
||||||
Poll all eight through:
|
Poll all eight through:
|
||||||
@@ -77,24 +77,27 @@ Supply the `operationId` returned by submission. Poll no faster than `pollAfterM
|
|||||||
|
|
||||||
## Reference Field Mapping
|
## Reference Field Mapping
|
||||||
|
|
||||||
After confirming a local upload, pass its stable `objectKey` into:
|
After confirming a local upload, pass its stable `objectKey` into operations that accept object references:
|
||||||
|
|
||||||
| Target capability | Field |
|
| Target capability | Field |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Image generation | `referenceImageSrcs` |
|
| Image generation | `referenceImageSrcs` |
|
||||||
| Image edit/redraw | `sourceImageSrc`; additional references in `referenceImageSrcs` |
|
| Image edit/redraw | `sourceImageSrc`; additional references in `referenceImageSrcs` |
|
||||||
| Icon spritesheet | `referenceImageSrc`; additional style references 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` |
|
| UI design extraction | `sourceImageSrc`; additional references in `referenceImageSrcs` |
|
||||||
| Character animation | `sourceImageSrc` |
|
| Character animation | `sourceImageSrc` |
|
||||||
| Video with image references | `referenceImageSrcs` |
|
| 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.
|
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.
|
||||||
|
|
||||||
## Common Values
|
## Common Values
|
||||||
|
|
||||||
Use OpenAPI as the final authority; these common values are a routing aid:
|
Use OpenAPI as the final authority; these common values are a routing aid:
|
||||||
|
|
||||||
- Image `kind`: `spec`, `character`, `quick-edit`, `ui-design`, `publication-material`; ordinary image generation may omit it.
|
- Image `kind`: `spec`, `character`, `quick-edit`, `ui-design`, `publication-material`; ordinary image generation may omit it.
|
||||||
|
- External v1 currently has no structured game-scene generation operation. Do not send `kind: "scene"` or `assetKind: "scene"` through generic image generation; the server rejects both before queueing.
|
||||||
- Image `model`: `gpt-image-2`, `gemini-3.1-flash-image-preview`, `nanobanana2`, `nano-banana`.
|
- Image `model`: `gpt-image-2`, `gemini-3.1-flash-image-preview`, `nanobanana2`, `nano-banana`.
|
||||||
- Image `aspectRatio`: `1:1`, `2:3`, `3:2`, `9:16`, `16:9`.
|
- Image `aspectRatio`: `1:1`, `2:3`, `3:2`, `9:16`, `16:9`.
|
||||||
- Image `imageSize`: `0.5K`, `1K`, `2K`.
|
- Image `imageSize`: `0.5K`, `1K`, `2K`.
|
||||||
@@ -102,6 +105,7 @@ Use OpenAPI as the final authority; these common values are a routing aid:
|
|||||||
- Video `aspectRatio`: `16:9`, `9:16`, `1:1`, `4:3`, `3:4`, `21:9`.
|
- Video `aspectRatio`: `16:9`, `9:16`, `1:1`, `4:3`, `3:4`, `21:9`.
|
||||||
- Video `resolution`: `480p`, `720p`, `1080p`; `mode`: `std`; `sound`: `on` or `off`.
|
- Video `resolution`: `480p`, `720p`, `1080p`; `mode`: `std`; `sound`: `on` or `off`.
|
||||||
- Character animation uses `model: "seedance2.0-fast"`; `resolution`: `480p` or `720p`; `frameCount`: `32`, `40`, or `48`; `durationSeconds`: `4`, `5`, or `6`; `ratio`: `same`, `1:1`, `4:3`, `16:9`, `9:16`, or `3:4`.
|
- Character animation uses `model: "seedance2.0-fast"`; `resolution`: `480p` or `720p`; `frameCount`: `32`, `40`, or `48`; `durationSeconds`: `4`, `5`, or `6`; `ratio`: `same`, `1:1`, `4:3`, `16:9`, `9:16`, or `3:4`.
|
||||||
|
- Sound effect uses canonical model `eleven_text_to_sound_v2`; omit `duration` or send `null` for automatic duration, otherwise send a finite `0.5-30` number. `loop` defaults to `false` and remains independent from Prompt text.
|
||||||
- UI extraction uses `aspectRatio: "1:1"`; use `imageSize: "1K"` for normal/small extraction and `2K` for dense designs.
|
- UI extraction uses `aspectRatio: "1:1"`; use `imageSize: "1K"` for normal/small extraction and `2K` for dense designs.
|
||||||
|
|
||||||
Do not hard-code this list as a replacement client schema. In particular, the top-level image `style` field is intentionally extensible; see `requests-and-outputs.md` for its fallback behavior.
|
Do not hard-code this list as a replacement client schema. In particular, the top-level image `style` field is intentionally extensible; see `requests-and-outputs.md` for its fallback behavior.
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ Keep the existing autonomous-build task graph. Do not add a parallel task system
|
|||||||
|
|
||||||
1. `art-director` generates `assets/art-spec.png` with image generation, `kind: "spec"`, then registers it as `assetKind: "icon-spec"`. This image is the authoritative visual spec; `generationInputs.artSpec` is supporting structured context.
|
1. `art-director` generates `assets/art-spec.png` with image generation, `kind: "spec"`, then registers it as `assetKind: "icon-spec"`. This image is the authoritative visual spec; `generationInputs.artSpec` is supporting structured context.
|
||||||
2. `design-foundation` generates `assets/ui-prototype.png` with `kind: "ui-design"`, using the registered art-spec resource ID in `referenceImageSrcs`.
|
2. `design-foundation` generates `assets/ui-prototype.png` with `kind: "ui-design"`, using the registered art-spec resource ID in `referenceImageSrcs`.
|
||||||
3. `art-asset-plan` generates transparent `assets/art-spritesheet.png` through icon spritesheet generation, using the same registered art-spec resource ID as `referenceImageSrc` plus concrete `iconDescriptions`.
|
3. `art-asset-plan` generates transparent `assets/art-spritesheet.png` through icon spritesheet generation, using the same registered art-spec resource ID as `referenceId` plus concrete `iconDescriptions`.
|
||||||
|
|
||||||
For a playable Canvas game, do not stop at generation. Make `code-prototype` depend on `art-asset-plan` and consume the persisted `iconImageSrcs` slices for core players, blocks or targets, scene obstacles, and feedback. For the four-category game-chat contract, require exactly four slices before registering the local runtime sheet; both fewer and extra components fail closed. Treat `art-spec.png` as reference-only. A full-sheet `<img>`, CSS background, path-only mention, guessed equal-grid crop, or code-drawn replacement for core entities is not runtime asset use. If slicing produces `sliceWarning`, keep the complete transparent sheet as a valid editor artifact, but fail the playable game asset gate until real slice files or verified atlas coordinates exist; never invent coordinates or replace the icon-spritesheet route with ordinary image generation.
|
For a playable Canvas game, do not stop at generation. Make `code-prototype` depend on `art-asset-plan` and consume the persisted `iconImageSrcs` slices for core players, blocks or targets, scene obstacles, and feedback. For the four-category game-chat contract, require exactly four slices before registering the local runtime sheet; both fewer and extra components fail closed. Treat `art-spec.png` as reference-only. A full-sheet `<img>`, CSS background, path-only mention, guessed equal-grid crop, or code-drawn replacement for core entities is not runtime asset use. If slicing produces `sliceWarning`, keep the complete transparent sheet as a valid editor artifact, but fail the playable game asset gate until real slice files or verified atlas coordinates exist; never invent coordinates or replace the icon-spritesheet route with ordinary image generation.
|
||||||
|
|
||||||
|
|||||||
@@ -104,6 +104,8 @@ For the lower-level asset/resource creation endpoints, `generationInputs` is rep
|
|||||||
|
|
||||||
## Art Spec and Image Request
|
## Art Spec and Image Request
|
||||||
|
|
||||||
|
Generic External v1 image generation does not expose the main-site structured game-scene contract. `kind: "scene"` and `assetKind: "scene"` are both invalid and return HTTP `400` before any generation job is queued. Do not replace the structured scene fields and server-owned prompt assembly with a generic image prompt.
|
||||||
|
|
||||||
Carry the current art spec in `generationInputs.artSpec` and reflect important constraints in the prompt:
|
Carry the current art spec in `generationInputs.artSpec` and reflect important constraints in the prompt:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -167,6 +169,8 @@ client.generate_image(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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:
|
For character animation from a local-only source, use actual dimensions and a stable synthetic layer ID:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -193,6 +197,7 @@ Do not guess dimensions or pass a temporary signed read URL. See `authentication
|
|||||||
The completed `result` may contain stable artifact fields such as:
|
The completed `result` may contain stable artifact fields such as:
|
||||||
|
|
||||||
- `objectKey`, media type, dimensions, or task ID.
|
- `objectKey`, media type, dimensions, or task ID.
|
||||||
|
- 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.
|
- `resource`, `resourceId`, or equivalent canvas reference.
|
||||||
- `asset`, `assetId`, or equivalent library reference.
|
- `asset`, `assetId`, or equivalent library reference.
|
||||||
- `spritesheetResource`, `spritesheetAsset`, and stable spritesheet metadata.
|
- `spritesheetResource`, `spritesheetAsset`, and stable spritesheet metadata.
|
||||||
|
|||||||
@@ -560,10 +560,13 @@ class GenarrativeExternalClient:
|
|||||||
|
|
||||||
def generate_icon_spritesheet(
|
def generate_icon_spritesheet(
|
||||||
self,
|
self,
|
||||||
reference_image_src: str,
|
reference_id: str,
|
||||||
icon_descriptions: list[str],
|
icon_descriptions: list[str],
|
||||||
**fields: Any,
|
**fields: Any,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
|
reference_id = normalize_optional_text(reference_id)
|
||||||
|
if not reference_id:
|
||||||
|
raise GenarrativeApiError("reference_id must be a registered icon-spec resource or asset ID")
|
||||||
descriptions = [item.strip() for item in icon_descriptions if item.strip()]
|
descriptions = [item.strip() for item in icon_descriptions if item.strip()]
|
||||||
if not descriptions:
|
if not descriptions:
|
||||||
raise GenarrativeApiError("icon_descriptions must contain at least one non-empty item")
|
raise GenarrativeApiError("icon_descriptions must contain at least one non-empty item")
|
||||||
@@ -574,9 +577,9 @@ class GenarrativeExternalClient:
|
|||||||
return self.submit_and_wait_generation(
|
return self.submit_and_wait_generation(
|
||||||
"/api/external/v1/editor/icon-spritesheets/generations",
|
"/api/external/v1/editor/icon-spritesheets/generations",
|
||||||
{
|
{
|
||||||
"referenceImageSrc": reference_image_src,
|
|
||||||
"iconDescriptions": descriptions,
|
|
||||||
**fields,
|
**fields,
|
||||||
|
"referenceId": reference_id,
|
||||||
|
"iconDescriptions": descriptions,
|
||||||
},
|
},
|
||||||
idempotency_key=idempotency_key,
|
idempotency_key=idempotency_key,
|
||||||
)
|
)
|
||||||
@@ -649,13 +652,19 @@ class GenarrativeExternalClient:
|
|||||||
idempotency_key=idempotency_key,
|
idempotency_key=idempotency_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_sound_effect(self, prompt: str, duration: int, **fields: Any) -> Any:
|
def generate_sound_effect(
|
||||||
|
self,
|
||||||
|
prompt: str,
|
||||||
|
duration: float | None = None,
|
||||||
|
loop: bool = False,
|
||||||
|
**fields: Any,
|
||||||
|
) -> Any:
|
||||||
self._apply_canvas_session_fields(fields, prompt, 360, 120)
|
self._apply_canvas_session_fields(fields, prompt, 360, 120)
|
||||||
prompt = self._apply_art_spec(fields, prompt)
|
prompt = self._apply_art_spec(fields, prompt)
|
||||||
idempotency_key = fields.pop("idempotencyKey", None)
|
idempotency_key = fields.pop("idempotencyKey", None)
|
||||||
return self.submit_and_wait_generation(
|
return self.submit_and_wait_generation(
|
||||||
"/api/external/v1/editor/audios/sound-effects/generations",
|
"/api/external/v1/editor/audios/sound-effects/generations",
|
||||||
{"prompt": prompt, "duration": duration, **fields},
|
{"prompt": prompt, "duration": duration, "loop": loop, **fields},
|
||||||
idempotency_key=idempotency_key,
|
idempotency_key=idempotency_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -766,9 +775,11 @@ def _self_test() -> None:
|
|||||||
["蛇头向上", "蛇身直线", "转角", "尾部", "四类食物"],
|
["蛇头向上", "蛇身直线", "转角", "尾部", "四类食物"],
|
||||||
canvasSession=session,
|
canvasSession=session,
|
||||||
assetLabel="贪吃蛇透明图集",
|
assetLabel="贪吃蛇透明图集",
|
||||||
|
referenceId="must-not-override-explicit-reference",
|
||||||
|
iconDescriptions=["不得覆盖显式图标描述"],
|
||||||
)
|
)
|
||||||
assert calls[0]["path"] == "/api/external/v1/editor/icon-spritesheets/generations"
|
assert calls[0]["path"] == "/api/external/v1/editor/icon-spritesheets/generations"
|
||||||
assert calls[0]["body"]["referenceImageSrc"] == "editor-resource-spec"
|
assert calls[0]["body"]["referenceId"] == "editor-resource-spec"
|
||||||
assert calls[0]["body"]["screenColor"] == "auto"
|
assert calls[0]["body"]["screenColor"] == "auto"
|
||||||
assert calls[0]["body"]["iconDescriptions"][0] == "蛇头向上"
|
assert calls[0]["body"]["iconDescriptions"][0] == "蛇头向上"
|
||||||
assert calls[1]["path"] == "/api/external/v1/generations/task-operation-demo"
|
assert calls[1]["path"] == "/api/external/v1/generations/task-operation-demo"
|
||||||
|
|||||||
@@ -130,6 +130,11 @@ VECTOR_ENGINE_BASE_URL="https://api.vectorengine.cn"
|
|||||||
VECTOR_ENGINE_API_KEY=""
|
VECTOR_ENGINE_API_KEY=""
|
||||||
VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS="1000000"
|
VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS="1000000"
|
||||||
|
|
||||||
|
# ElevenLabs editor sound-effect generation is server-side only.
|
||||||
|
ELEVENLABS_BASE_URL="https://api.elevenlabs.io"
|
||||||
|
ELEVENLABS_API_KEY=""
|
||||||
|
ELEVENLABS_REQUEST_TIMEOUT_MS="180000"
|
||||||
|
|
||||||
# 阿里云 OSS 配置。
|
# 阿里云 OSS 配置。
|
||||||
# Rust `server-rs` 的 `api-server` 会优先从 `.env` / `.env.local` 读取这些变量,
|
# Rust `server-rs` 的 `api-server` 会优先从 `.env` / `.env.local` 读取这些变量,
|
||||||
# 用于签发浏览器 PostObject 直传票据,并保持 `/generated-*` 旧路径习惯。
|
# 用于签发浏览器 PostObject 直传票据,并保持 `/generated-*` 旧路径习惯。
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
files: [
|
files: [
|
||||||
'apps/admin-web/src/pages/*.tsx',
|
'apps/admin-web/src/pages/*.tsx',
|
||||||
|
'src/components/platform-entry/PlatformActiveMobileWelcomeDialog.tsx',
|
||||||
'src/components/platform-entry/PlatformMobileHomeWelcomeDialog.tsx',
|
'src/components/platform-entry/PlatformMobileHomeWelcomeDialog.tsx',
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
@@ -72,6 +73,7 @@ module.exports = {
|
|||||||
'src/components/image-editor/**/*.{ts,tsx}',
|
'src/components/image-editor/**/*.{ts,tsx}',
|
||||||
'src/components/project/**/*.{ts,tsx}',
|
'src/components/project/**/*.{ts,tsx}',
|
||||||
'src/components/platform-entry/PlatformActiveProfileView*.tsx',
|
'src/components/platform-entry/PlatformActiveProfileView*.tsx',
|
||||||
|
'src/components/platform-entry/PlatformActiveMobileWelcomeDialog*.tsx',
|
||||||
'src/components/platform-entry/PlatformEntryActiveFlowShell*.tsx',
|
'src/components/platform-entry/PlatformEntryActiveFlowShell*.tsx',
|
||||||
'src/components/platform-entry/PlatformEntryFlowShell.tsx',
|
'src/components/platform-entry/PlatformEntryFlowShell.tsx',
|
||||||
'src/components/platform-entry/PlatformProfileApiKeysModal.tsx',
|
'src/components/platform-entry/PlatformProfileApiKeysModal.tsx',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
name: genarrative-dev-stack-port-routing
|
name: genarrative-dev-stack-port-routing
|
||||||
short_description: 修改 Genarrative 本地 dev 启动端口、代理目标、端口冲突处理时使用。
|
short_description: 修改 Genarrative 本地 dev 启动端口、代理目标、端口冲突处理时使用。
|
||||||
description: 在 Genarrative 中修改 npm run dev / dev:spacetime / dev:api-server / dev:bgfilter-worker / dev:web / dev:admin-web 的本地启动端口、端口可用性探测、端口漂移、SpacetimeDB publish server、Rust 进程环境变量、Vite 代理目标和后台 admin-web 启动串联时使用。
|
description: 在 Genarrative 中修改 npm run dev / dev:spacetime / dev:api-server / dev:bgfilter-worker / dev:web / dev:admin-web 的本地启动端口、端口可用性探测、端口漂移、SpacetimeDB publish server、Rust 进程环境变量、Vite 代理目标和后台 admin-web 启动串联时使用。
|
||||||
version: 1.1.0
|
version: 1.2.0
|
||||||
author: Hermes Agent
|
author: Hermes Agent
|
||||||
license: MIT
|
license: MIT
|
||||||
metadata:
|
metadata:
|
||||||
@@ -17,7 +17,7 @@ metadata:
|
|||||||
|
|
||||||
## 适用场景
|
## 适用场景
|
||||||
|
|
||||||
- 修改 `scripts/dev.mjs`、`scripts/dev-utils.mjs`、`scripts/dev-stack-port-utils.mjs`。
|
- 修改 `scripts/dev.mjs`、`scripts/dev-utils.mjs`、`scripts/dev-stack-port-utils.mjs` 或 AI 游戏创作客户端 dev 启动器。
|
||||||
- 处理 `3000`、`3101`、`3102`、`8082` 等端口被占用导致本地开发栈启动失败。
|
- 处理 `3000`、`3101`、`3102`、`8082` 等端口被占用导致本地开发栈启动失败。
|
||||||
- 排查 Vite 代理仍指向旧 api-server 端口、前端打开了旧 dev server、后台代理错配。
|
- 排查 Vite 代理仍指向旧 api-server 端口、前端打开了旧 dev server、后台代理错配。
|
||||||
- 调整 SpacetimeDB standalone、publish、Rust `api-server`、主站 Vite、后台 Vite 的启动顺序。
|
- 调整 SpacetimeDB standalone、publish、Rust `api-server`、主站 Vite、后台 Vite 的启动顺序。
|
||||||
@@ -32,10 +32,11 @@ metadata:
|
|||||||
3. SpacetimeDB standalone:`3101`,健康检查为 `http://127.0.0.1:<spacetime-port>/v1/ping`。
|
3. SpacetimeDB standalone:`3101`,健康检查为 `http://127.0.0.1:<spacetime-port>/v1/ping`。
|
||||||
4. 后台 Vite:`3102`,后台地址为 `http://127.0.0.1:<admin-web-port>/admin/`。
|
4. 后台 Vite:`3102`,后台地址为 `http://127.0.0.1:<admin-web-port>/admin/`。
|
||||||
5. 独立 BgFilter worker:`8083`,就绪检查为 `http://127.0.0.1:<bgfilter-worker-port>/readyz`。
|
5. 独立 BgFilter worker:`8083`,就绪检查为 `http://127.0.0.1:<bgfilter-worker-port>/readyz`。
|
||||||
|
6. AI 游戏创作 Vite:非 Linux 兼容首选 `3080`;Linux 使用当前用户端口段的 `start + 5`。
|
||||||
|
|
||||||
端口不可用时,脚本会从优先端口开始向后寻找可用端口。后续流程必须以解析后的实际端口为准,不能继续使用默认端口。
|
端口不可用时,脚本会从优先端口开始向后寻找可用端口。后续流程必须以解析后的实际端口为准,不能继续使用默认端口。
|
||||||
|
|
||||||
Linux 多用户并发开发时,`GENARRATIVE_DEV_PORT_RANGE` 或 `--port-range` 会先向系统级注册表 `/var/tmp/genarrative-dev-port-ranges/registry.json` 申请一个端口段,再把该段映射为 `web = start`、`api = start + 1`、`spacetime = start + 2`、`adminWeb = start + 3`、`bgfilterWorker = start + 4`。注册表锁文件是 `/var/tmp/genarrative-dev-port-ranges/registry.lock`,可通过 `GENARRATIVE_DEV_PORT_RANGE_REGISTRY_DIR` 覆盖目录。自动分配从 `10000-10099` 起,每次占用 100 个端口块,后续块按 `10100-10199`、`10200-10299` 递增;当前口径是“一个用户固定占用一个段,后续启动继续复用这段并在段内漂移”;该注册表只在 Linux 上生效;Windows 继续沿用原有统一端口探测和漂移逻辑,不读系统级注册表。
|
Linux 多用户并发开发时,`GENARRATIVE_DEV_PORT_RANGE` 或 `--port-range` 会先向系统级注册表 `/var/tmp/genarrative-dev-port-ranges/registry.json` 申请一个端口段,再把该段映射为 `web = start`、`api = start + 1`、`spacetime = start + 2`、`adminWeb = start + 3`、`bgfilterWorker = start + 4`、`agcVite = start + 5`。注册表锁文件是 `/var/tmp/genarrative-dev-port-ranges/registry.lock`,可通过 `GENARRATIVE_DEV_PORT_RANGE_REGISTRY_DIR` 覆盖目录。自动分配从 `10000-10099` 起,每次占用 100 个端口块,后续块按 `10100-10199`、`10200-10299` 递增;当前口径是“一个用户固定占用一个段,后续启动继续复用这段并在段内漂移”;该注册表只在 Linux 上生效;Windows 继续沿用原有统一端口探测和漂移逻辑,不读系统级注册表。
|
||||||
|
|
||||||
## 实现入口
|
## 实现入口
|
||||||
|
|
||||||
@@ -51,9 +52,12 @@ Linux 多用户并发开发时,`GENARRATIVE_DEV_PORT_RANGE` 或 `--port-range`
|
|||||||
- `scripts/dev.mjs`
|
- `scripts/dev.mjs`
|
||||||
- 解析 CLI 参数后统一计算 client host、端口、`SPACETIME_SERVER`、`RUST_SERVER_TARGET`。
|
- 解析 CLI 参数后统一计算 client host、端口、`SPACETIME_SERVER`、`RUST_SERVER_TARGET`。
|
||||||
- 完整栈按 SpacetimeDB、publish、BgFilter worker readiness、api-server readiness、主站 Vite、后台 Vite 顺序启动。
|
- 完整栈按 SpacetimeDB、publish、BgFilter worker readiness、api-server readiness、主站 Vite、后台 Vite 顺序启动。
|
||||||
- Linux 下会先申请系统级端口段并把它映射成五个 dev 端口;自动分配从 `10000-10099` 起,Windows 则把第五个服务纳入原有统一参数解析与端口漂移逻辑。
|
- Linux 下会先申请系统级端口段并映射成六个预留槽位;主 dev 栈使用前五个,AGC Vite 使用 `start + 5`,自动分配从 `10000-10099` 起。Windows 的主 dev 栈和 AGC 则各自沿用统一端口探测与漂移逻辑。
|
||||||
- 完整栈和 `dev:api-server` 把两个 Rust 进程作为同一重启单元,先全部停止,再先启动 BgFilter worker、后启动 api-server;不要为同一份 Rust 源码创建两个并发 `cargo` watcher。
|
- 完整栈和 `dev:api-server` 把两个 Rust 进程作为同一重启单元,先全部停止,再先启动 BgFilter worker、后启动 api-server;不要为同一份 Rust 源码创建两个并发 `cargo` watcher。
|
||||||
- 单模块命令复用同一套参数和 env 解析。
|
- 单模块命令复用同一套参数和 env 解析。
|
||||||
|
- `apps/ai-game-creator-shell/scripts/dev-port.mjs`
|
||||||
|
- 复用系统级用户端口段,解析 AGC Vite 的 `start + 5` 首选槽位。
|
||||||
|
- 把最终端口通过 `GENARRATIVE_AGC_VITE_PORT` 同步给 `beforeDevCommand` 和配套后端端口解析器,通过 Tauri CLI `--config` 同步 `build.devUrl`,并通过 Vite CLI `--port` 同步 `strictPort` 监听。
|
||||||
|
|
||||||
## 必须保持的传递链路
|
## 必须保持的传递链路
|
||||||
|
|
||||||
@@ -69,6 +73,7 @@ Linux 多用户并发开发时,`GENARRATIVE_DEV_PORT_RANGE` 或 `--port-range`
|
|||||||
8. 后台 Vite:`ADMIN_API_TARGET`、`GENARRATIVE_API_TARGET`、`GENARRATIVE_API_PORT`、`--port=${ADMIN_WEB_PORT}`。
|
8. 后台 Vite:`ADMIN_API_TARGET`、`GENARRATIVE_API_TARGET`、`GENARRATIVE_API_PORT`、`--port=${ADMIN_WEB_PORT}`。
|
||||||
9. 控制台日志:`[dev:ports]` 和 `[dev] web/admin web/api-server/bgfilter-worker/spacetime` 必须显示最终实际地址。
|
9. 控制台日志:`[dev:ports]` 和 `[dev] web/admin web/api-server/bgfilter-worker/spacetime` 必须显示最终实际地址。
|
||||||
10. Linux 端口段注册:`[dev] port-range:` 与 `[dev] port-range-registry:` 只在 Linux 输出,Windows 不应依赖系统级注册表。
|
10. Linux 端口段注册:`[dev] port-range:` 与 `[dev] port-range-registry:` 只在 Linux 输出,Windows 不应依赖系统级注册表。
|
||||||
|
11. AI 游戏创作客户端:外层启动器解析最终 AGC Vite 端口后,通过 Tauri CLI `--config` 覆盖 `build.devUrl`,把同一 `GENARRATIVE_AGC_VITE_PORT` 传给 `beforeDevCommand` 与配套后端端口解析器,并用 Vite CLI `--port` 启动严格监听;后端端口漂移必须跳过该预留端口。
|
||||||
|
|
||||||
如果只改了其中一段,通常会出现:浏览器打开的前端可用,但 `/api/*` 代理到旧端口;后台页面可用但后台 API 失败;SpacetimeDB 启动在新端口但 publish 仍发往旧端口。
|
如果只改了其中一段,通常会出现:浏览器打开的前端可用,但 `/api/*` 代理到旧端口;后台页面可用但后台 API 失败;SpacetimeDB 启动在新端口但 publish 仍发往旧端口。
|
||||||
|
|
||||||
@@ -128,6 +133,7 @@ node scripts/dev-stack-port-utils.mjs resolve-dev-stack spacetime:127.0.0.1:0 ap
|
|||||||
- [ ] `npm run dev` 的 SpacetimeDB、publish、api-server、主站 Vite、后台 Vite 都使用实际端口。
|
- [ ] `npm run dev` 的 SpacetimeDB、publish、api-server、主站 Vite、后台 Vite 都使用实际端口。
|
||||||
- [ ] BgFilter worker 在 api-server 前 ready,父子共享实际 base URL / Token,Rust watch 只触发一次组合重启。
|
- [ ] BgFilter worker 在 api-server 前 ready,父子共享实际 base URL / Token,Rust watch 只触发一次组合重启。
|
||||||
- [ ] `npm run dev:web` 在主站端口不可用时能切换到可用端口。
|
- [ ] `npm run dev:web` 在主站端口不可用时能切换到可用端口。
|
||||||
|
- [ ] `npm run agc` / `npm run agc:game-chat` 在 Linux 使用用户段 `start + 5`,Tauri、Vite、marker 和预检使用同一最终端口。
|
||||||
- [ ] 文档同步更新 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
- [ ] 文档同步更新 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
- [ ] 长期踩坑同步更新 `docs/project-memory/shared-memory/pitfalls.md`。
|
- [ ] 长期踩坑同步更新 `docs/project-memory/shared-memory/pitfalls.md`。
|
||||||
- [ ] 修改中文文件后运行 `npm run check:encoding`。
|
- [ ] 修改中文文件后运行 `npm run check:encoding`。
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/start-tauri-dev.mjs",
|
"dev": "node scripts/start-tauri-dev.mjs",
|
||||||
"dev:app-run": "node scripts/start-tauri-dev.mjs --app-run",
|
|
||||||
"game-chat": "node scripts/start-tauri-dev.mjs --game-chat",
|
"game-chat": "node scripts/start-tauri-dev.mjs --game-chat",
|
||||||
"dev-server": "node scripts/start-dev-server.mjs",
|
"dev-server": "node scripts/start-dev-server.mjs",
|
||||||
"dev-stack": "node scripts/start-dev-stack.mjs",
|
"dev-stack": "node scripts/start-dev-stack.mjs",
|
||||||
|
|||||||
@@ -27,12 +27,6 @@ const tauriConfig = JSON.parse(
|
|||||||
'utf8',
|
'utf8',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const appRunTauriConfig = JSON.parse(
|
|
||||||
fs.readFileSync(
|
|
||||||
new URL('../src-tauri/tauri.app-run-dev.conf.json', import.meta.url),
|
|
||||||
'utf8',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const gameChatReleaseTauriConfig = JSON.parse(
|
const gameChatReleaseTauriConfig = JSON.parse(
|
||||||
fs.readFileSync(
|
fs.readFileSync(
|
||||||
new URL('../src-tauri/tauri.game-chat-release.conf.json', import.meta.url),
|
new URL('../src-tauri/tauri.game-chat-release.conf.json', import.meta.url),
|
||||||
@@ -68,6 +62,14 @@ const viteConfigSource = fs.readFileSync(
|
|||||||
new URL('../vite.config.ts', import.meta.url),
|
new URL('../vite.config.ts', import.meta.url),
|
||||||
'utf8',
|
'utf8',
|
||||||
);
|
);
|
||||||
|
const devPortSource = fs.readFileSync(
|
||||||
|
new URL('../scripts/dev-port.mjs', import.meta.url),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
const startTauriDevSource = fs.readFileSync(
|
||||||
|
new URL('../scripts/start-tauri-dev.mjs', import.meta.url),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
const appSource = [
|
const appSource = [
|
||||||
readSourceTree(new URL('../src/', import.meta.url), '.ts'),
|
readSourceTree(new URL('../src/', import.meta.url), '.ts'),
|
||||||
readSourceTree(new URL('../src/', import.meta.url), '.tsx'),
|
readSourceTree(new URL('../src/', import.meta.url), '.tsx'),
|
||||||
@@ -1223,7 +1225,7 @@ if (
|
|||||||
|
|
||||||
if (tauriConfig.build?.devUrl !== 'http://127.0.0.1:3080/') {
|
if (tauriConfig.build?.devUrl !== 'http://127.0.0.1:3080/') {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'AI game creator shell Tauri devUrl must stay on the fixed Vite dev port',
|
'AI game creator shell Tauri config must retain the non-launcher fallback devUrl',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1233,12 +1235,41 @@ if (!viteConfigSource.includes("host: '127.0.0.1'")) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!viteConfigSource.includes('port: 3080')) {
|
if (
|
||||||
|
!viteConfigSource.includes('port: 3080') ||
|
||||||
|
!viteConfigSource.includes('port: server.config.server.port')
|
||||||
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'AI game creator shell Vite dev port must match Tauri devUrl',
|
'AI game creator shell Vite config must retain its fallback and report the actual CLI-selected port',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const snippet of [
|
||||||
|
'mapDevPortsToPortRange',
|
||||||
|
'agcVitePort',
|
||||||
|
'resolveAgcDevEndpoint',
|
||||||
|
'GENARRATIVE_AGC_VITE_PORT',
|
||||||
|
]) {
|
||||||
|
if (!devPortSource.includes(snippet)) {
|
||||||
|
throw new Error(
|
||||||
|
`AI game creator shell dev port resolver drifted: ${snippet}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const snippet of [
|
||||||
|
'resolveAgcDevEndpoint',
|
||||||
|
'withAgcDevEndpointEnv',
|
||||||
|
"'--config'",
|
||||||
|
'configOverride',
|
||||||
|
]) {
|
||||||
|
if (!startTauriDevSource.includes(snippet)) {
|
||||||
|
throw new Error(
|
||||||
|
`AI game creator shell Tauri dev port injection drifted: ${snippet}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!viteConfigSource.includes('strictPort: true')) {
|
if (!viteConfigSource.includes('strictPort: true')) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'AI game creator shell Vite dev server must not drift away from Tauri devUrl',
|
'AI game creator shell Vite dev server must not drift away from Tauri devUrl',
|
||||||
@@ -1259,7 +1290,7 @@ if (
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'AI game creator shell beforeDevCommand must reuse or start the fixed Vite dev server',
|
'AI game creator shell beforeDevCommand must start the selected Vite dev server',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1269,33 +1300,6 @@ if (packageConfig.scripts?.dev !== 'node scripts/start-tauri-dev.mjs') {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
packageConfig.scripts?.['dev:app-run'] !==
|
|
||||||
'node scripts/start-tauri-dev.mjs --app-run' ||
|
|
||||||
rootPackageConfig.scripts?.['agc:app-run'] !==
|
|
||||||
'npm --prefix apps/ai-game-creator-shell run dev:app-run'
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
'AI game creator app-run dev profile must use the managed Tauri dev launcher',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const appRunWindow = appRunTauriConfig.app?.windows?.[0];
|
|
||||||
if (
|
|
||||||
appRunTauriConfig.productName !==
|
|
||||||
'Genarrative AI Game Creator App Run' ||
|
|
||||||
appRunTauriConfig.identifier !==
|
|
||||||
'world.genarrative.ai-game-creator.app-run' ||
|
|
||||||
appRunTauriConfig.identifier === tauriConfig.identifier ||
|
|
||||||
appRunTauriConfig.build?.devUrl !== 'http://127.0.0.1:3081/' ||
|
|
||||||
appRunWindow?.label !== 'client' ||
|
|
||||||
appRunWindow?.title !== 'AI 游戏创作 · App Run'
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
'AI game creator app-run profile must keep an independent identity, title, and Vite port',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
packageConfig.scripts?.['game-chat'] !==
|
packageConfig.scripts?.['game-chat'] !==
|
||||||
'node scripts/start-tauri-dev.mjs --game-chat'
|
'node scripts/start-tauri-dev.mjs --game-chat'
|
||||||
@@ -1496,12 +1500,15 @@ if (
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const snippet of [
|
for (const snippet of [
|
||||||
'const port = 3080',
|
'resolveAgcDevEndpoint',
|
||||||
|
'withAgcDevEndpointEnv',
|
||||||
"response.body.includes('<title>AI 游戏创作</title>')",
|
"response.body.includes('<title>AI 游戏创作</title>')",
|
||||||
'function isPortListening()',
|
'function isPortListening()',
|
||||||
'reuse existing Vite dev server',
|
'cannot be safely reused',
|
||||||
'non-HTTP or unrecognized server',
|
'non-HTTP or unrecognized server',
|
||||||
"'--config', 'vite.config.ts'",
|
"'--config'",
|
||||||
|
"'vite.config.ts'",
|
||||||
|
"'--port'",
|
||||||
]) {
|
]) {
|
||||||
if (!devServerSource.includes(snippet)) {
|
if (!devServerSource.includes(snippet)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import {
|
||||||
|
findAvailablePort,
|
||||||
|
formatPortDecision,
|
||||||
|
mapDevPortsToPortRange,
|
||||||
|
normalizePort,
|
||||||
|
reserveLinuxDevPortRange,
|
||||||
|
} from '../../../scripts/dev-stack-port-utils.mjs';
|
||||||
|
|
||||||
|
const agcDevHost = '127.0.0.1';
|
||||||
|
const legacyAgcDevPort = 3080;
|
||||||
|
const agcVitePortEnvKey = 'GENARRATIVE_AGC_VITE_PORT';
|
||||||
|
|
||||||
|
function readConfiguredAgcDevPort(env = process.env) {
|
||||||
|
const rawPort = String(env[agcVitePortEnvKey] ?? '').trim();
|
||||||
|
if (!rawPort) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = normalizePort(rawPort, -1);
|
||||||
|
if (port < 1024) {
|
||||||
|
throw new Error(`${agcVitePortEnvKey} 必须是 1024-65535 的有效端口`);
|
||||||
|
}
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAgcDevEndpoint(port, portRange = null) {
|
||||||
|
const url = `http://${agcDevHost}:${port}/`;
|
||||||
|
return {
|
||||||
|
host: agcDevHost,
|
||||||
|
port,
|
||||||
|
url,
|
||||||
|
markerUrl: `${url}__agc_dev_server.json`,
|
||||||
|
portRange,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readAgcDevEndpoint(env = process.env) {
|
||||||
|
return createAgcDevEndpoint(
|
||||||
|
readConfiguredAgcDevPort(env) ?? legacyAgcDevPort,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveAgcDevEndpoint({
|
||||||
|
env = process.env,
|
||||||
|
platform = process.platform,
|
||||||
|
strictConfigured = false,
|
||||||
|
reservePortRange = reserveLinuxDevPortRange,
|
||||||
|
findPort = findAvailablePort,
|
||||||
|
} = {}) {
|
||||||
|
const configuredPort = readConfiguredAgcDevPort(env);
|
||||||
|
let portRange = null;
|
||||||
|
let preferredPort = configuredPort ?? legacyAgcDevPort;
|
||||||
|
|
||||||
|
if (platform === 'linux') {
|
||||||
|
const allocation = await reservePortRange({ env });
|
||||||
|
if (!allocation?.range) {
|
||||||
|
throw new Error('无法取得当前 Linux 用户的 dev 端口段');
|
||||||
|
}
|
||||||
|
portRange = allocation.range;
|
||||||
|
const mappedAgcVitePort = mapDevPortsToPortRange(portRange)?.agcVitePort;
|
||||||
|
if (!Number.isInteger(mappedAgcVitePort)) {
|
||||||
|
throw new Error(
|
||||||
|
`当前 Linux dev 端口段 ${portRange.label} 缺少 AGC Vite 槽位;请先迁移为至少 6 个端口且不与其它用户重叠的端口段`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (configuredPort != null && configuredPort < mappedAgcVitePort) {
|
||||||
|
throw new Error(
|
||||||
|
`${agcVitePortEnvKey} ${configuredPort} 占用了 Linux dev 端口段 ${portRange.label} 的前五个服务槽位`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
preferredPort = configuredPort ?? mappedAgcVitePort;
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = await findPort({
|
||||||
|
host: agcDevHost,
|
||||||
|
preferredPort,
|
||||||
|
portRange,
|
||||||
|
strict: strictConfigured && configuredPort != null,
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
formatPortDecision({
|
||||||
|
name: 'ai-game-creator-shell',
|
||||||
|
host: agcDevHost,
|
||||||
|
preferredPort,
|
||||||
|
resolvedPort: port,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (portRange) {
|
||||||
|
console.log(`[ai-game-creator-shell] dev port-range: ${portRange.label}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return createAgcDevEndpoint(port, portRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
function withAgcDevEndpointEnv(endpoint, env = process.env) {
|
||||||
|
return {
|
||||||
|
...env,
|
||||||
|
[agcVitePortEnvKey]: String(endpoint.port),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
agcDevHost,
|
||||||
|
agcVitePortEnvKey,
|
||||||
|
createAgcDevEndpoint,
|
||||||
|
legacyAgcDevPort,
|
||||||
|
readAgcDevEndpoint,
|
||||||
|
readConfiguredAgcDevPort,
|
||||||
|
resolveAgcDevEndpoint,
|
||||||
|
withAgcDevEndpointEnv,
|
||||||
|
};
|
||||||
@@ -3,10 +3,11 @@ import http from 'node:http';
|
|||||||
import net from 'node:net';
|
import net from 'node:net';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
import { resolveAgcDevEndpoint, withAgcDevEndpointEnv } from './dev-port.mjs';
|
||||||
|
|
||||||
const appRoot = fileURLToPath(new URL('..', import.meta.url));
|
const appRoot = fileURLToPath(new URL('..', import.meta.url));
|
||||||
const host = '127.0.0.1';
|
const endpoint = await resolveAgcDevEndpoint();
|
||||||
const port = 3080;
|
const { host, port, url: devUrl } = endpoint;
|
||||||
const devUrl = `http://${host}:${port}/`;
|
|
||||||
|
|
||||||
function readExistingServer() {
|
function readExistingServer() {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@@ -69,8 +70,10 @@ function isPortListening() {
|
|||||||
const existing = await readExistingServer();
|
const existing = await readExistingServer();
|
||||||
if (existing) {
|
if (existing) {
|
||||||
if (isAiGameCreatorServer(existing)) {
|
if (isAiGameCreatorServer(existing)) {
|
||||||
console.log(`[ai-game-creator-shell] reuse existing Vite dev server ${devUrl}`);
|
console.error(
|
||||||
process.exit(0);
|
`[ai-game-creator-shell] ${devUrl} is already running but cannot be safely reused. Stop that process before starting the dev server.`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
console.error(
|
console.error(
|
||||||
`[ai-game-creator-shell] ${devUrl} is already in use by another server. Stop that process before starting Tauri dev.`,
|
`[ai-game-creator-shell] ${devUrl} is already in use by another server. Stop that process before starting Tauri dev.`,
|
||||||
@@ -88,9 +91,20 @@ if (await isPortListening()) {
|
|||||||
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||||
const child = spawn(
|
const child = spawn(
|
||||||
npm,
|
npm,
|
||||||
['--prefix', '../..', 'exec', 'vite', '--', '--config', 'vite.config.ts'],
|
[
|
||||||
|
'--prefix',
|
||||||
|
'../..',
|
||||||
|
'exec',
|
||||||
|
'vite',
|
||||||
|
'--',
|
||||||
|
'--config',
|
||||||
|
'vite.config.ts',
|
||||||
|
'--port',
|
||||||
|
String(endpoint.port),
|
||||||
|
],
|
||||||
{
|
{
|
||||||
cwd: appRoot,
|
cwd: appRoot,
|
||||||
|
env: withAgcDevEndpointEnv(endpoint),
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
// Node 18.20+/20+/24 on Windows rejects spawning .cmd (npm.cmd) without a shell (EINVAL).
|
// Node 18.20+/20+/24 on Windows rejects spawning .cmd (npm.cmd) without a shell (EINVAL).
|
||||||
shell: true,
|
shell: true,
|
||||||
|
|||||||
@@ -5,58 +5,23 @@ import net from 'node:net';
|
|||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
import {
|
||||||
|
agcVitePortEnvKey,
|
||||||
|
readAgcDevEndpoint,
|
||||||
|
resolveAgcDevEndpoint,
|
||||||
|
withAgcDevEndpointEnv,
|
||||||
|
} from './dev-port.mjs';
|
||||||
|
|
||||||
const appRoot = fileURLToPath(new URL('..', import.meta.url));
|
const appRoot = fileURLToPath(new URL('..', import.meta.url));
|
||||||
const repoRoot = resolve(appRoot, '../..');
|
const repoRoot = resolve(appRoot, '../..');
|
||||||
const devStackStatePath = resolve(repoRoot, '.app/dev-stack.json');
|
const devStackStatePath = resolve(repoRoot, '.app/dev-stack.json');
|
||||||
const devProfileName = process.env.GENARRATIVE_AGC_DEV_PROFILE || 'default';
|
|
||||||
|
|
||||||
function resolveDevStackProfile(name = 'default') {
|
|
||||||
switch (name) {
|
|
||||||
case 'default':
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
viteHost: '127.0.0.1',
|
|
||||||
vitePort: 3080,
|
|
||||||
apiPort: 8082,
|
|
||||||
bgfilterWorkerPort: 8083,
|
|
||||||
spacetimePort: 3101,
|
|
||||||
backendDatabase: 'genarrative-game-creator-dev',
|
|
||||||
backendSpacetimeDataDir: resolve(
|
|
||||||
repoRoot,
|
|
||||||
'server-rs/.spacetimedb/ai-game-creator/data',
|
|
||||||
),
|
|
||||||
};
|
|
||||||
case 'app-run':
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
viteHost: '127.0.0.1',
|
|
||||||
vitePort: 3081,
|
|
||||||
apiPort: 8084,
|
|
||||||
bgfilterWorkerPort: 8085,
|
|
||||||
spacetimePort: 3103,
|
|
||||||
backendDatabase: 'genarrative-game-creator-app-run-dev',
|
|
||||||
backendSpacetimeDataDir: resolve(
|
|
||||||
repoRoot,
|
|
||||||
'server-rs/.spacetimedb/ai-game-creator-app-run/data',
|
|
||||||
),
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
throw new Error(`未知 AI 游戏创作开发 profile: ${name}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const devProfile = resolveDevStackProfile(devProfileName);
|
|
||||||
const {
|
|
||||||
viteHost,
|
|
||||||
vitePort,
|
|
||||||
apiPort,
|
|
||||||
spacetimePort,
|
|
||||||
backendDatabase,
|
|
||||||
backendSpacetimeDataDir,
|
|
||||||
} = devProfile;
|
|
||||||
const viteUrl = `http://${viteHost}:${vitePort}/`;
|
|
||||||
const defaultApiTarget =
|
const defaultApiTarget =
|
||||||
process.env.RUST_SERVER_TARGET || `http://127.0.0.1:${apiPort}`;
|
process.env.RUST_SERVER_TARGET || 'http://127.0.0.1:8082';
|
||||||
|
const backendDatabase = 'genarrative-game-creator-dev';
|
||||||
|
const backendSpacetimeDataDir = resolve(
|
||||||
|
repoRoot,
|
||||||
|
'server-rs/.spacetimedb/ai-game-creator/data',
|
||||||
|
);
|
||||||
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||||
const childLifecycles = new WeakMap();
|
const childLifecycles = new WeakMap();
|
||||||
|
|
||||||
@@ -110,7 +75,6 @@ function resolveBackendTargetsFromState(
|
|||||||
expectedDatabase = backendDatabase,
|
expectedDatabase = backendDatabase,
|
||||||
expectedSpacetimeDataDir = backendSpacetimeDataDir,
|
expectedSpacetimeDataDir = backendSpacetimeDataDir,
|
||||||
fallbackApiTarget = defaultApiTarget,
|
fallbackApiTarget = defaultApiTarget,
|
||||||
fallbackSpacetimeTarget = `http://127.0.0.1:${spacetimePort}`,
|
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
const apiServer = state?.services?.['api-server'];
|
const apiServer = state?.services?.['api-server'];
|
||||||
@@ -139,7 +103,7 @@ function resolveBackendTargetsFromState(
|
|||||||
? spacetime.url
|
? spacetime.url
|
||||||
: requireAgcBackend
|
: requireAgcBackend
|
||||||
? ''
|
? ''
|
||||||
: fallbackSpacetimeTarget;
|
: 'http://127.0.0.1:3101';
|
||||||
return {
|
return {
|
||||||
apiUrl,
|
apiUrl,
|
||||||
spacetimeUrl,
|
spacetimeUrl,
|
||||||
@@ -170,24 +134,13 @@ async function isBackendReady() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveViteProfileUrls(profile = devProfile) {
|
async function readExistingViteServer(endpoint = readAgcDevEndpoint()) {
|
||||||
const profileViteUrl = `http://${profile.viteHost}:${profile.vitePort}/`;
|
return httpGetText(endpoint.url);
|
||||||
return {
|
|
||||||
viteUrl: profileViteUrl,
|
|
||||||
viteMarkerUrl: `${profileViteUrl}__agc_dev_server.json`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readExistingViteServer(profile = devProfile) {
|
function isVitePortListening(endpoint = readAgcDevEndpoint()) {
|
||||||
return httpGetText(resolveViteProfileUrls(profile).viteUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isVitePortListening(profile = devProfile) {
|
|
||||||
return new Promise((resolveRequest) => {
|
return new Promise((resolveRequest) => {
|
||||||
const socket = net.connect({
|
const socket = net.connect({ host: endpoint.host, port: endpoint.port });
|
||||||
host: profile.viteHost,
|
|
||||||
port: profile.vitePort,
|
|
||||||
});
|
|
||||||
socket.once('connect', () => {
|
socket.once('connect', () => {
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
resolveRequest(true);
|
resolveRequest(true);
|
||||||
@@ -210,11 +163,8 @@ function isAiGameCreatorServer(response) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readExistingViteMarker(profile = devProfile) {
|
async function readExistingViteMarker(endpoint = readAgcDevEndpoint()) {
|
||||||
const response = await httpGetText(
|
const response = await httpGetText(endpoint.markerUrl, 2000);
|
||||||
resolveViteProfileUrls(profile).viteMarkerUrl,
|
|
||||||
2000,
|
|
||||||
);
|
|
||||||
if (!response || response.statusCode !== 200) {
|
if (!response || response.statusCode !== 200) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -226,17 +176,16 @@ async function readExistingViteMarker(profile = devProfile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function preflightExistingVite({
|
async function preflightExistingVite({
|
||||||
profile = devProfile,
|
endpoint = readAgcDevEndpoint(),
|
||||||
readServer = () => readExistingViteServer(profile),
|
readServer = readExistingViteServer,
|
||||||
portListening = () => isVitePortListening(profile),
|
portListening = isVitePortListening,
|
||||||
readMarker = () => readExistingViteMarker(profile),
|
readMarker = readExistingViteMarker,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const { viteUrl: profileViteUrl } = resolveViteProfileUrls(profile);
|
const existing = await readServer(endpoint);
|
||||||
const existing = await readServer();
|
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
if (await portListening()) {
|
if (await portListening(endpoint)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${profileViteUrl} is already in use by a non-HTTP or unrecognized server. Stop it before starting Tauri dev.`,
|
`${endpoint.url} is already in use by a non-HTTP or unrecognized server. Stop it before starting Tauri dev.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return { status: 'available', apiTarget: '' };
|
return { status: 'available', apiTarget: '' };
|
||||||
@@ -244,11 +193,11 @@ async function preflightExistingVite({
|
|||||||
|
|
||||||
if (!isAiGameCreatorServer(existing)) {
|
if (!isAiGameCreatorServer(existing)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${profileViteUrl} is already in use by another server. Stop it before starting Tauri dev.`,
|
`${endpoint.url} is already in use by another server. Stop it before starting Tauri dev.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const marker = await readMarker();
|
const marker = await readMarker(endpoint);
|
||||||
const markerApiTarget =
|
const markerApiTarget =
|
||||||
marker?.schemaVersion === 1 &&
|
marker?.schemaVersion === 1 &&
|
||||||
marker?.app === 'ai-game-creator-shell' &&
|
marker?.app === 'ai-game-creator-shell' &&
|
||||||
@@ -257,7 +206,7 @@ async function preflightExistingVite({
|
|||||||
: '';
|
: '';
|
||||||
const actualTarget = markerApiTarget || 'unknown';
|
const actualTarget = markerApiTarget || 'unknown';
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${profileViteUrl} is already running with API target ${actualTarget}. Its owning worktree cannot be proven, so it will not be reused. Stop that Vite dev server before starting Tauri dev.`,
|
`${endpoint.url} is already running with API target ${actualTarget}. Its owning worktree cannot be proven, so it will not be reused. Stop that Vite dev server before starting Tauri dev.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,50 +511,27 @@ async function waitForBackendReady(backendChild, timeoutMs = 600_000) {
|
|||||||
throw new Error('等待配套后端和数据库启动超时');
|
throw new Error('等待配套后端和数据库启动超时');
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildBackendStartArguments(profile = devProfile) {
|
|
||||||
return [
|
|
||||||
'--prefix',
|
|
||||||
'../..',
|
|
||||||
'run',
|
|
||||||
'agc:backend',
|
|
||||||
'--',
|
|
||||||
'--database',
|
|
||||||
profile.backendDatabase,
|
|
||||||
'--spacetime-data-dir',
|
|
||||||
profile.backendSpacetimeDataDir,
|
|
||||||
'--api-port',
|
|
||||||
String(profile.apiPort),
|
|
||||||
'--bgfilter-worker-port',
|
|
||||||
String(profile.bgfilterWorkerPort),
|
|
||||||
'--spacetime-port',
|
|
||||||
String(profile.spacetimePort),
|
|
||||||
'--no-interactive',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildViteStartArguments(profile = devProfile) {
|
|
||||||
return [
|
|
||||||
'--prefix',
|
|
||||||
'../..',
|
|
||||||
'exec',
|
|
||||||
'vite',
|
|
||||||
'--',
|
|
||||||
'--config',
|
|
||||||
'vite.config.ts',
|
|
||||||
'--host',
|
|
||||||
profile.viteHost,
|
|
||||||
'--port',
|
|
||||||
String(profile.vitePort),
|
|
||||||
'--strictPort',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function ensureBackend({
|
async function ensureBackend({
|
||||||
onBackendChild = () => {},
|
onBackendChild = () => {},
|
||||||
checkBackendReady = isBackendReady,
|
checkBackendReady = isBackendReady,
|
||||||
resolveTargets = readBackendTargets,
|
resolveTargets = readBackendTargets,
|
||||||
spawnBackend = () =>
|
spawnBackend = () =>
|
||||||
spawnChild(npm, buildBackendStartArguments(), { cwd: appRoot }),
|
spawnChild(
|
||||||
|
npm,
|
||||||
|
[
|
||||||
|
'--prefix',
|
||||||
|
'../..',
|
||||||
|
'run',
|
||||||
|
'agc:backend',
|
||||||
|
'--',
|
||||||
|
'--database',
|
||||||
|
backendDatabase,
|
||||||
|
'--spacetime-data-dir',
|
||||||
|
backendSpacetimeDataDir,
|
||||||
|
'--no-interactive',
|
||||||
|
],
|
||||||
|
{ cwd: appRoot },
|
||||||
|
),
|
||||||
waitUntilReady = waitForBackendReady,
|
waitUntilReady = waitForBackendReady,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
if (await checkBackendReady()) {
|
if (await checkBackendReady()) {
|
||||||
@@ -627,7 +553,7 @@ async function ensureBackend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startVite(apiTarget) {
|
async function startVite(apiTarget, endpoint = readAgcDevEndpoint()) {
|
||||||
const { apiUrl } = readBackendTargets();
|
const { apiUrl } = readBackendTargets();
|
||||||
if (apiUrl !== apiTarget) {
|
if (apiUrl !== apiTarget) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -635,19 +561,33 @@ async function startVite(apiTarget) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const existing = await readExistingViteServer();
|
const existing = await readExistingViteServer(endpoint);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
if (isAiGameCreatorServer(existing)) {
|
if (isAiGameCreatorServer(existing)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${viteUrl} is already running and cannot be safely reused. Stop it before starting Tauri dev.`,
|
`${endpoint.url} is already running and cannot be safely reused. Stop it before starting Tauri dev.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${viteUrl} is already in use by another server. Stop it before starting Tauri dev.`,
|
`${endpoint.url} is already in use by another server. Stop it before starting Tauri dev.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return spawnChild(npm, buildViteStartArguments(), { cwd: appRoot });
|
return spawnChild(
|
||||||
|
npm,
|
||||||
|
[
|
||||||
|
'--prefix',
|
||||||
|
'../..',
|
||||||
|
'exec',
|
||||||
|
'vite',
|
||||||
|
'--',
|
||||||
|
'--config',
|
||||||
|
'vite.config.ts',
|
||||||
|
'--port',
|
||||||
|
String(endpoint.port),
|
||||||
|
],
|
||||||
|
{ cwd: appRoot, env: withAgcDevEndpointEnv(endpoint) },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
@@ -667,10 +607,9 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(
|
const endpoint = await resolveAgcDevEndpoint({ strictConfigured: true });
|
||||||
`[ai-game-creator-shell] dev profile ${devProfile.name}: vite=${viteUrl} api=${apiPort} spacetime=${spacetimePort}`,
|
process.env[agcVitePortEnvKey] = String(endpoint.port);
|
||||||
);
|
await preflightExistingVite({ endpoint });
|
||||||
await preflightExistingVite();
|
|
||||||
const backend = await ensureBackend({
|
const backend = await ensureBackend({
|
||||||
onBackendChild(child) {
|
onBackendChild(child) {
|
||||||
backendChild = child;
|
backendChild = child;
|
||||||
@@ -684,7 +623,7 @@ async function main() {
|
|||||||
throw new Error(`启动期收到 ${shutdownSignal},已停止配套后端`);
|
throw new Error(`启动期收到 ${shutdownSignal},已停止配套后端`);
|
||||||
}
|
}
|
||||||
|
|
||||||
viteChild = await startVite(backend.targets.apiUrl);
|
viteChild = await startVite(backend.targets.apiUrl, endpoint);
|
||||||
if (shutdownSignal) {
|
if (shutdownSignal) {
|
||||||
stopChild(viteChild, shutdownSignal);
|
stopChild(viteChild, shutdownSignal);
|
||||||
throw new Error(`启动期收到 ${shutdownSignal},已停止前端服务`);
|
throw new Error(`启动期收到 ${shutdownSignal},已停止前端服务`);
|
||||||
@@ -727,8 +666,6 @@ function isDirectModuleExecution() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
buildBackendStartArguments,
|
|
||||||
buildViteStartArguments,
|
|
||||||
ensureBackend,
|
ensureBackend,
|
||||||
formatChildFailure,
|
formatChildFailure,
|
||||||
isDirectModuleExecution,
|
isDirectModuleExecution,
|
||||||
@@ -737,7 +674,6 @@ export {
|
|||||||
readChildFailure,
|
readChildFailure,
|
||||||
readLinuxProcessGroupAlive,
|
readLinuxProcessGroupAlive,
|
||||||
resolveBackendTargetsFromState,
|
resolveBackendTargetsFromState,
|
||||||
resolveDevStackProfile,
|
|
||||||
runWindowsTaskkill,
|
runWindowsTaskkill,
|
||||||
spawnChild,
|
spawnChild,
|
||||||
stopChild,
|
stopChild,
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
import {
|
||||||
|
readAgcDevEndpoint,
|
||||||
|
resolveAgcDevEndpoint,
|
||||||
|
withAgcDevEndpointEnv,
|
||||||
|
} from './dev-port.mjs';
|
||||||
import {
|
import {
|
||||||
preflightExistingVite,
|
preflightExistingVite,
|
||||||
resolveDevStackProfile,
|
|
||||||
spawnChild,
|
spawnChild,
|
||||||
stopChild,
|
stopChild,
|
||||||
terminateChildTree,
|
terminateChildTree,
|
||||||
@@ -16,43 +20,44 @@ const tauriCliPath = resolve(repoRoot, 'node_modules/@tauri-apps/cli/tauri.js');
|
|||||||
|
|
||||||
function parseLauncherArguments(argv) {
|
function parseLauncherArguments(argv) {
|
||||||
const args = [...argv];
|
const args = [...argv];
|
||||||
const appRun = args[0] === '--app-run';
|
|
||||||
if (appRun) {
|
|
||||||
args.shift();
|
|
||||||
}
|
|
||||||
const gameChat = args[0] === '--game-chat';
|
const gameChat = args[0] === '--game-chat';
|
||||||
if (gameChat) {
|
if (gameChat) {
|
||||||
args.shift();
|
args.shift();
|
||||||
}
|
}
|
||||||
if (appRun && gameChat) {
|
return { gameChat, args };
|
||||||
throw new Error('app-run profile 不能与 game-chat 入口同时使用');
|
|
||||||
}
|
|
||||||
return { appRun, gameChat, args };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTauriArguments(argv) {
|
function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) {
|
||||||
const { appRun, gameChat, args } = parseLauncherArguments(argv);
|
const { gameChat, args } = parseLauncherArguments(argv);
|
||||||
if (appRun) {
|
const configOverride = JSON.stringify({ build: { devUrl } });
|
||||||
|
if (gameChat) {
|
||||||
return [
|
return [
|
||||||
'dev',
|
'dev',
|
||||||
'--config',
|
'--config',
|
||||||
'src-tauri/tauri.app-run-dev.conf.json',
|
configOverride,
|
||||||
|
'--',
|
||||||
|
'--',
|
||||||
|
'--game-chat',
|
||||||
...args,
|
...args,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (gameChat) {
|
const separatorIndex = args.indexOf('--');
|
||||||
return ['dev', '--', '--', '--game-chat', ...args];
|
if (separatorIndex < 0) {
|
||||||
|
return ['dev', ...args, '--config', configOverride];
|
||||||
}
|
}
|
||||||
return ['dev', ...args];
|
return [
|
||||||
|
'dev',
|
||||||
|
...args.slice(0, separatorIndex),
|
||||||
|
'--config',
|
||||||
|
configOverride,
|
||||||
|
...args.slice(separatorIndex),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function spawnTauriCli(argv, { profileName = 'default' } = {}) {
|
function spawnTauriCli(argv, { env = process.env } = {}) {
|
||||||
return spawnChild(process.execPath, [tauriCliPath, ...argv], {
|
return spawnChild(process.execPath, [tauriCliPath, ...argv], {
|
||||||
cwd: appRoot,
|
cwd: appRoot,
|
||||||
env: {
|
env,
|
||||||
...process.env,
|
|
||||||
GENARRATIVE_AGC_DEV_PROFILE: profileName,
|
|
||||||
},
|
|
||||||
shell: false,
|
shell: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -60,18 +65,20 @@ function spawnTauriCli(argv, { profileName = 'default' } = {}) {
|
|||||||
async function runTauriDev(
|
async function runTauriDev(
|
||||||
argv = process.argv.slice(2),
|
argv = process.argv.slice(2),
|
||||||
{
|
{
|
||||||
|
resolveDevEndpoint = resolveAgcDevEndpoint,
|
||||||
preflight = preflightExistingVite,
|
preflight = preflightExistingVite,
|
||||||
spawnCli = spawnTauriCli,
|
spawnCli = spawnTauriCli,
|
||||||
waitForCli = waitForChildTermination,
|
waitForCli = waitForChildTermination,
|
||||||
terminateTree = terminateChildTree,
|
terminateTree = terminateChildTree,
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
const { appRun } = parseLauncherArguments(argv);
|
const endpoint = await resolveDevEndpoint();
|
||||||
const profile = resolveDevStackProfile(appRun ? 'app-run' : 'default');
|
await preflight({ endpoint });
|
||||||
await preflight({ profile });
|
|
||||||
|
|
||||||
const tauriArguments = buildTauriArguments(argv);
|
const tauriArguments = buildTauriArguments(argv, endpoint.url);
|
||||||
const child = spawnCli(tauriArguments, { profileName: profile.name });
|
const child = spawnCli(tauriArguments, {
|
||||||
|
env: withAgcDevEndpointEnv(endpoint),
|
||||||
|
});
|
||||||
let resolveShutdown;
|
let resolveShutdown;
|
||||||
let shutdownSignal = '';
|
let shutdownSignal = '';
|
||||||
let repeatedSignal = false;
|
let repeatedSignal = false;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"id": "genarrative.agent-runtime",
|
"id": "genarrative.agent-runtime",
|
||||||
"version": "2026-08-06.1",
|
"version": "2026-08-07.2",
|
||||||
"sections": {
|
"sections": {
|
||||||
"common": "common.md",
|
"common": "common.md",
|
||||||
"isolatedTemplateCatalogIntro": "isolated-template-catalog-intro.md",
|
"isolatedTemplateCatalogIntro": "isolated-template-catalog-intro.md",
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
"platformDefault": "platform/default.md",
|
"platformDefault": "platform/default.md",
|
||||||
"platformLinux": "platform/linux.md",
|
"platformLinux": "platform/linux.md",
|
||||||
"codePrototypeGameChat": "roles/code-prototype-game-chat.md",
|
"codePrototypeGameChat": "roles/code-prototype-game-chat.md",
|
||||||
"codeDirectorGameChatAssetAudit": "roles/code-director-game-chat-asset-audit.md",
|
|
||||||
"projectSupervisorGameChatRouting": "supervisor/game-chat-routing.md",
|
"projectSupervisorGameChatRouting": "supervisor/game-chat-routing.md",
|
||||||
"providerIsolatedToolContract": "provider/isolated-tool-contract.md",
|
"providerIsolatedToolContract": "provider/isolated-tool-contract.md",
|
||||||
"providerAutonomousRunProfile": "provider/autonomous-run-profile.md",
|
"providerAutonomousRunProfile": "provider/autonomous-run-profile.md",
|
||||||
@@ -67,11 +66,6 @@
|
|||||||
"rootSourceKind": "supervisorGameChat",
|
"rootSourceKind": "supervisorGameChat",
|
||||||
"sections": ["projectSupervisorGameChatRouting"]
|
"sections": ["projectSupervisorGameChatRouting"]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"agentId": "code-director",
|
|
||||||
"rootSourceKind": "supervisorGameChat",
|
|
||||||
"sections": ["codeDirectorGameChatAssetAudit"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"agentId": "code-prototype",
|
"agentId": "code-prototype",
|
||||||
"rootSourceKind": "supervisorGameChat",
|
"rootSourceKind": "supervisorGameChat",
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
本次修复的原生工具目录只保留 agent.delegate。必须在同一响应一次性建立完整首批合同,且只允许以下三个非 repair 委派,各出现一次:design-director 与 code-director 的 task 或 acceptanceCriteria 必须显式声明只读且不得修改项目,expectedArtifacts 必须为 [];art-director 必须是非只读规范图生成任务,expectedArtifacts 必须包含 assets/art-spec.png。三者都必须提供非空 task、1-8 条 acceptanceCriteria,并设置 repairOfDelegationId=null、runId=null。不得委派 code-prototype、quality-review、design-foundation、art-asset-plan 或其它底层 Agent,不得调用 agent.spawn_isolated,不得更新计划、读取、搜索、查询状态、修改项目或返回最终回复。不要解释,不要 markdown,不要代码围栏。
|
本片段只适用于普通 GUI / CLI 的完整 `autonomous-game-build` manifest DAG;不得用于持久 source 为 `project-supervisor-game-chat` 的单主 route,后者不得用此修复建立固定首批委派。普通 DAG 的本次修复原生工具目录只保留 agent.delegate。必须在同一响应一次性建立完整首批合同,且只允许以下三个非 repair 委派,各出现一次:design-director 与 code-director 的 task 或 acceptanceCriteria 必须显式声明只读且不得修改项目,expectedArtifacts 必须为 [];art-director 必须是非只读规范图生成任务,expectedArtifacts 必须包含 assets/art-spec.png。三者都必须提供非空 task、1-8 条 acceptanceCriteria,并设置 repairOfDelegationId=null、runId=null。不得委派 code-prototype、quality-review、design-foundation、art-asset-plan 或其它底层 Agent,不得调用 agent.spawn_isolated,不得更新计划、读取、搜索、查询状态、修改项目或返回最终回复。不要解释,不要 markdown,不要代码围栏。
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
autonomous-game-build 的正式 manifest 任务图是唯一首轮专业执行链。不得在 manifest 之前另行创建 code-prototype、quality-review、art-director、design-foundation 或 art-asset-plan 的首批 agent.delegate;这些角色会由 Runtime 按 manifest 依赖顺序调度。没有待认领的显式返工合同时也不得额外委派。请直接推进/观察 manifest,Runtime 会在你尝试收束时调度 ready task,并在任务图完成前阻止最终交付。
|
本片段只适用于普通 GUI / CLI 的完整 `autonomous-game-build` manifest DAG,不适用于持久 source 为 `project-supervisor-game-chat` 的单主 route。普通 DAG 中,正式 manifest 任务图是唯一首轮专业执行链:不得在 manifest 之前另行创建 code-prototype、quality-review、art-director、design-foundation 或 art-asset-plan 的首批 agent.delegate;这些角色会由 Runtime 按 manifest 依赖顺序调度。没有待认领的显式返工合同时也不得额外委派。game-chat 则由其专用路由提示决定:Supervisor 持久化意图后只启动 code-prototype,只有该主 Agent 的 asset.list 审计证实真实缺口时才可委派受限美术 child。请直接推进/观察适用于当前 root source 的任务路径;Runtime 会在你尝试收束时调度 ready task,并在任务图完成前阻止最终交付。
|
||||||
|
|||||||
-1
@@ -1 +0,0 @@
|
|||||||
game-chat 的 code-director 是程序侧资产审计节点。第一步必须调用 `asset.list` 核对当前正式资产、Canvas 登记和可复用状态;不得生成图片、修改游戏或只凭用户措辞猜测缺口。取得 asset.list observation 后,本轮必须调用 `agent.route_manifest` 提交结构化结果:Supervisor 选择 audit-existing-first 且没有缺口时使用 `strategy=use-existing-art, missingAssetSlots=[]`;存在缺口时使用 `strategy=generate-missing-art`,missingAssetSlots 只能精确列出 `art-spec`、`core-spritesheet` 中实际缺失项;Supervisor 选择 regenerate-art 时使用 `strategy=regenerate-art, missingAssetSlots=["art-spec","core-spritesheet"]`。Runtime 会复核权威资产状态,拒绝遗漏、虚构、过期或重复缺口。路由成功后再交付只读审计结论;美术补齐后由同一根 Run 恢复 code-prototype。
|
|
||||||
+4
-2
@@ -1,3 +1,5 @@
|
|||||||
game-chat 使用素材完整快车道。必须先保护既有游戏语义和已完成产物:如果 observation 尚未包含 game/index.html 的当前正文与摘要,本响应第一步必须调用 file.read(path=game/index.html),不得猜测入口仍是初始化占位。只有文件缺失或正文与初始化页面一致、且当前 run 尚未写入项目时,才允许用一次 file.write 生成完整、自包含、可运行的 game/index.html;检测到非占位入口、已有可玩实现、当前 revision 已验证,或当前 run 已发生修改时,禁止整文件 file.write,必须保留原玩法,优先执行静态检查/试玩,确需修复时只能依据已读取的精确原文做最小 file.patch。不得把“继续”、continue 或其它纯续跑词当作游戏主题;缺少可继承的具体目标时必须失败关闭,不得另造新玩法。
|
game-chat 使用单主素材审计快车道。你是唯一的 `code-prototype` 主 Agent;必须先保护既有游戏语义和已完成产物。若本轮尚未取得成功的 `asset.list` observation,第一步必须调用 `asset.list` 核对当前正式资产、Canvas 登记和可复用状态;不得仅凭用户措辞、关键词或旧摘要判断已有素材是否可用。审计后必须用 `agent.route_manifest` 持久化审计结论:完整覆盖使用 `use-existing-art, missingAssetSlots=[]`,真实缺口使用 `generate-missing-art` 且只列出实际缺失槽位;不得把 Supervisor 的用户 intent 改写成重生成路线。审计证明核心规范图、透明 spritesheet、切片清单或四类切片存在真实缺口时,才可一次委派对应的 `art-director` 或 `art-asset-plan` 补齐;任务必须精确说明缺失项和验收产物,child 只能写 `assets/**`,不得修改 `game/**`、删除/补丁程序文件或替你接入游戏。一次最多保留一个活跃美术委派,收到并认领其回执后继续同一 Run;不得在美术已齐或仅凭“UI 没有用素材”等描述时生成、委派或扣费。Supervisor 持久化的整体重做意图也只是上下文,不能改变这条审计门或授权整套美术强制重生成。
|
||||||
|
|
||||||
HTML 必须满足固定试玩合同,包含真实 Canvas 游戏循环、键盘与触控输入、开始、主要操作、重开、胜负状态和移动端布局。assets/art-spec.png 只能作为视觉规范与派生参考,不得在运行时加载、铺作背景或裁切实体。必须等待已登记且透明有效的 assets/art-spritesheet.png 与 assets/art-spritesheet-slices/manifest.json,从切片清单按 player、blocks-and-targets、obstacles-and-scene、feedback-effects 四种 usage 加载四个不同的独立透明素材,并在活动 Canvas 中通过 drawImage 绘制对应核心玩家、方块/目标、障碍/场景和反馈;不得猜测整张图集是等分网格,不得把整张图集作为 img、CSS background 或完整 drawImage 展示,也不得以纯代码几何替代核心实体。图集、四类切片或其可见使用任一缺失时不得交付。首次实现或最小修复后不要继续扩写功能;Runtime 会自动执行静态自检并在通过后立即试玩。
|
在成功审计并取得可用素材、或已认领必要美术回执后,才读取游戏入口:如果 observation 尚未包含 game/index.html 的当前正文与摘要,本响应下一步必须调用 file.read(path=game/index.html),不得猜测入口仍是初始化占位。只有文件缺失或正文与初始化页面一致、且当前 run 尚未写入项目时,才允许用一次 file.write 生成完整、自包含、可运行的 game/index.html;检测到非占位入口、已有可玩实现、当前 revision 已验证,或当前 run 已发生修改时,禁止整文件 file.write,必须保留原玩法,优先执行静态检查/试玩,确需修复时只能依据已读取的精确原文做最小 file.patch。不得把“继续”、continue 或其它纯续跑词当作游戏主题;缺少可继承的具体目标时必须失败关闭,不得另造新玩法。
|
||||||
|
|
||||||
|
HTML 必须满足固定试玩合同,包含真实 Canvas 游戏循环、键盘与触控输入、开始、主要操作、重开、胜负状态和移动端布局。assets/art-spec.png 只能作为视觉规范与派生参考,不得在运行时加载、铺作背景或裁切实体。必须等待已登记且透明有效的 assets/art-spritesheet.png 与 assets/art-spritesheet-slices/manifest.json,从切片清单按 player、blocks-and-targets、obstacles-and-scene、feedback-effects 四种 usage 加载四个不同的独立透明素材,并在活动 Canvas 中通过 drawImage 绘制对应核心玩家、方块/目标、障碍/场景和反馈;不得猜测整张图集是等分网格,不得把整张图集作为 img、CSS background 或完整 drawImage 展示,也不得以纯代码几何替代核心实体。图集、四类切片或其可见使用任一缺失时不得交付。完成最后一处项目写入后,由你在同一 Run 执行 `game.static_smoke`,再执行 desktop 与 mobile `preview.validate`;不得把接入、检查或试玩交回固定验证节点。首次实现或最小修复后不要继续扩写功能。
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
game-chat 的固定关键词和资产探测只作为 `advisoryOnly=true` 的补充上下文,不能直接选择、重置或跳过 manifest 节点。当前根 Run 尚无工作流决策时,你必须先理解用户目标,并把本轮唯一动作设为 `agent.route_manifest`:通常选择 `strategy=audit-existing-first, missingAssetSlots=[]`,让程序侧先核对现有游戏真正需要且已经可用的正式美术;只有用户明确要求整体重做或替换成全新美术时才选择 `strategy=regenerate-art, missingAssetSlots=[]`。不得根据关键词自行声称已有资产完整,也不得在该结构化决策前委派或调度美术 Agent。
|
game-chat 的固定关键词和资产探测只作为 `advisoryOnly=true` 的补充上下文,不能直接选择、重置或跳过 manifest 节点。当前根 Run 尚无工作流决策时,你必须先自行理解用户真正要做的事,并把本轮唯一动作设为 `agent.route_manifest`:`strategy=audit-existing-first, missingAssetSlots=[]` 是固定执行安全策略;`intentSummary` 必须由你概括用户意图,例如“把已有美术资源接入当前游戏”或“按用户要求刷新整体视觉方向”,不得照抄固定信号代替理解。这个动作只记录意图,不代表生成许可,也不能把整体重做解释成整套美术的强制重生成。不得根据关键词自行声称已有资产完整,也不得在该结构化决策前委派或调度美术 Agent。
|
||||||
|
|
||||||
程序侧审计会在同一根 Run 中提交覆盖合同:现有正式资源覆盖完整时直接开放 code-prototype 接入;存在明确缺口时只开放缺口对应的美术 owner,补齐后再恢复 code-prototype。Runtime 负责校验路径、Canvas 登记、合同指纹和缺口一致性,但不替你解释用户意图。
|
这一步只持久化用户意图和单主路径,不审计资产、不代替 `code-prototype` 判断缺口,也不得创建 `design-director`、`code-director`、`art-director`、`art-asset-plan`、试玩或其它固定首波节点。持久路由后 Runtime 只启动同一根 Run 的 `code-prototype`。它先以 `asset.list` 取得权威资产、Canvas 登记和可复用状态;只有该审计证明 `art-spec` 或核心 spritesheet 确实缺失,才可由该主 Agent 向对应美术角色发起一次受限的 durable 委派。美术 child 仅可写 `assets/**`,回执由同一 `code-prototype` 认领后恢复其原 Run 接入、静态检查和桌面/移动试玩。完整覆盖时不得生成、委派或扣费;整体重做意图同样必须经过这次审计,不能绕过资产复用或授权整套美术重生成。Runtime 负责校验根/父子身份、路径、Canvas 登记、合同指纹、缺口一致性和写入范围,但不替你解释用户意图或生成美术。
|
||||||
|
|||||||
@@ -1240,13 +1240,13 @@ pub(in crate::agent) async fn request_platform_art_asset_with_runtime_options_at
|
|||||||
.then(|| canonical_art_spec_reference_at(root, &canvas_context.project_id))
|
.then(|| canonical_art_spec_reference_at(root, &canvas_context.project_id))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let (endpoint, request_body) = if is_canonical_art_spritesheet {
|
let (endpoint, request_body) = if is_canonical_art_spritesheet {
|
||||||
let reference_image_src = canonical_reference
|
let reference_id = canonical_reference
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.ok_or_else(|| "透明美术图集缺少规范图引用".to_string())?;
|
.ok_or_else(|| "透明美术图集缺少规范图引用".to_string())?;
|
||||||
(
|
(
|
||||||
"/api/external/v1/editor/icon-spritesheets/generations",
|
"/api/external/v1/editor/icon-spritesheets/generations",
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"referenceImageSrc": reference_image_src,
|
"referenceId": reference_id,
|
||||||
"iconDescriptions": canonical_art_spritesheet_icon_descriptions(&generation_prompt),
|
"iconDescriptions": canonical_art_spritesheet_icon_descriptions(&generation_prompt),
|
||||||
"screenColor": "auto",
|
"screenColor": "auto",
|
||||||
"aspectRatio": options.aspect_ratio,
|
"aspectRatio": options.aspect_ratio,
|
||||||
@@ -5710,22 +5710,10 @@ mod canvas_generation_tests {
|
|||||||
stream
|
stream
|
||||||
.set_read_timeout(Some(Duration::from_secs(10)))
|
.set_read_timeout(Some(Duration::from_secs(10)))
|
||||||
.expect("set request read timeout");
|
.expect("set request read timeout");
|
||||||
let deadline = std::time::Instant::now() + Duration::from_secs(10);
|
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
let mut buffer = [0_u8; 4096];
|
let mut buffer = [0_u8; 4096];
|
||||||
loop {
|
loop {
|
||||||
let read = match stream.read(&mut buffer) {
|
let read = stream.read(&mut buffer).expect("read request bytes");
|
||||||
Ok(read) => read,
|
|
||||||
Err(error)
|
|
||||||
if matches!(
|
|
||||||
error.kind(),
|
|
||||||
std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
|
|
||||||
) && std::time::Instant::now() < deadline =>
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(error) => panic!("read request bytes: {error}"),
|
|
||||||
};
|
|
||||||
if read == 0 {
|
if read == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-4
@@ -344,11 +344,10 @@ pub(super) fn platform_art_generation_runtime_request_snapshot(
|
|||||||
(generation_kind, reference_resource_ids)
|
(generation_kind, reference_resource_ids)
|
||||||
}
|
}
|
||||||
"/api/external/v1/editor/icon-spritesheets/generations" => {
|
"/api/external/v1/editor/icon-spritesheets/generations" => {
|
||||||
let reference_resource_id = json_string_field(&request_body, "referenceImageSrc")
|
let reference_resource_id = json_string_field(&request_body, "referenceId")
|
||||||
|
.or_else(|| json_string_field(&request_body, "referenceImageSrc"))
|
||||||
.filter(|value| !value.trim().is_empty())
|
.filter(|value| !value.trim().is_empty())
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| "External Editor 图集生成账本请求缺少 referenceId".to_string())?;
|
||||||
"External Editor 图集生成账本请求缺少 referenceImageSrc".to_string()
|
|
||||||
})?;
|
|
||||||
("icon-spritesheet".to_string(), vec![reference_resource_id])
|
("icon-spritesheet".to_string(), vec![reference_resource_id])
|
||||||
}
|
}
|
||||||
_ => return Err("External Editor 生成账本 endpoint 不受支持".to_string()),
|
_ => return Err("External Editor 生成账本 endpoint 不受支持".to_string()),
|
||||||
@@ -868,6 +867,35 @@ mod external_generation_state_tests {
|
|||||||
)
|
)
|
||||||
.expect("prepare generation ledger");
|
.expect("prepare generation ledger");
|
||||||
assert!(created);
|
assert!(created);
|
||||||
|
let mut spritesheet_state = prepared.clone();
|
||||||
|
spritesheet_state.endpoint =
|
||||||
|
"/api/external/v1/editor/icon-spritesheets/generations".to_string();
|
||||||
|
spritesheet_state.request_body_json = serde_json::json!({
|
||||||
|
"referenceId": "resource-icon-spec",
|
||||||
|
"iconDescriptions": ["玩家主体"],
|
||||||
|
"projectId": "canvas-project",
|
||||||
|
"assetFolderId": "asset-folder"
|
||||||
|
})
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(
|
||||||
|
platform_art_generation_runtime_request_snapshot(&spritesheet_state)
|
||||||
|
.expect("parse current icon spritesheet ledger")
|
||||||
|
.reference_resource_ids,
|
||||||
|
vec!["resource-icon-spec"]
|
||||||
|
);
|
||||||
|
spritesheet_state.request_body_json = serde_json::json!({
|
||||||
|
"referenceImageSrc": "legacy-resource-icon-spec",
|
||||||
|
"iconDescriptions": ["玩家主体"],
|
||||||
|
"projectId": "canvas-project",
|
||||||
|
"assetFolderId": "asset-folder"
|
||||||
|
})
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(
|
||||||
|
platform_art_generation_runtime_request_snapshot(&spritesheet_state)
|
||||||
|
.expect("parse already persisted legacy icon spritesheet ledger")
|
||||||
|
.reference_resource_ids,
|
||||||
|
vec!["legacy-resource-icon-spec"]
|
||||||
|
);
|
||||||
let mut invalid_key = prepared.clone();
|
let mut invalid_key = prepared.clone();
|
||||||
invalid_key.idempotency_key = "invalid key".to_string();
|
invalid_key.idempotency_key = "invalid key".to_string();
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
@@ -519,7 +519,7 @@ fn game_creator_art_asset_plan_tool_plan_prompt(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
format!(
|
format!(
|
||||||
"{prompt}\n\n你负责首版美术素材实际生成。资产清单和美术计划只是中间结果;最终必须调用 canvas.asset_generate 生成并登记 assets/art-spritesheet.png,固定使用 1:1、1K、assetKind=art-spritesheet、assetLabel=游戏首版核心美术素材、replaceExisting=false,并写入可解析的 assets/manifest.art.json。调用前必须用 asset.list 确认 assets/art-spec.png 已登记为当前项目的 icon-spec 画布资源,并依据当前任务、game/game_design.md 与 manifest 逐项说明真实需要的玩家主体及朝向/状态、目标或收集物、障碍/场景元素和反馈特效,由 Runtime 形成 iconDescriptions;不得假设为塔防或加入合同中不存在的单位、敌人、波次、卡牌。Runtime 固定以规范图的权威 resourceId 作为 referenceImageSrc,调用 POST /api/external/v1/editor/icon-spritesheets/generations,并用 screenColor=auto 完成透明后处理;不得把 UI 原型、Data URL、Blob URL、本地路径或结构化 JSON 冒充规范图引用,不得回退普通生图或 UI extraction。缺少规范图时必须等待 art-director 依赖并如实阻塞。成功后回读 observation 与 asset.list,核对服务端返回的透明 spritesheet、真实 alpha、warning 和 sliceWarning。warning.code=postprocess-failed-source-preserved 时没有透明图集,不得登记、验收或自动重试;仅 sliceWarning 时可保留完整透明图集,但不得声称独立切片已生成。已有有效同路径资产时不得重复生成或扣费;只有带 repairOfDelegationId 的唯一返工轮可 replaceExisting=true 原位替换。不得运行 game.static_smoke 或 preview.validate,也不得编辑 game/index.html。图片生成未配置、待确认、失败或透明证据不足时不得提交最终回复。"
|
"{prompt}\n\n你负责首版美术素材实际生成。资产清单和美术计划只是中间结果;最终必须调用 canvas.asset_generate 生成并登记 assets/art-spritesheet.png,固定使用 1:1、1K、assetKind=art-spritesheet、assetLabel=游戏首版核心美术素材、replaceExisting=false,并写入可解析的 assets/manifest.art.json。调用前必须用 asset.list 确认 assets/art-spec.png 已登记为当前项目的 icon-spec 画布资源,并依据当前任务、game/game_design.md 与 manifest 逐项说明真实需要的玩家主体及朝向/状态、目标或收集物、障碍/场景元素和反馈特效,由 Runtime 形成 iconDescriptions;不得假设为塔防或加入合同中不存在的单位、敌人、波次、卡牌。Runtime 固定以规范图的权威 resourceId 作为 referenceId,调用 POST /api/external/v1/editor/icon-spritesheets/generations,并用 screenColor=auto 完成透明后处理;不得把 UI 原型、Data URL、Blob URL、本地路径或结构化 JSON 冒充规范图引用,不得回退普通生图或 UI extraction。缺少规范图时必须等待 art-director 依赖并如实阻塞。成功后回读 observation 与 asset.list,核对服务端返回的透明 spritesheet、真实 alpha、warning 和 sliceWarning。warning.code=postprocess-failed-source-preserved 时没有透明图集,不得登记、验收或自动重试;仅 sliceWarning 时可保留完整透明图集,但不得声称独立切片已生成。已有有效同路径资产时不得重复生成或扣费;只有带 repairOfDelegationId 的唯一返工轮可 replaceExisting=true 原位替换。不得运行 game.static_smoke 或 preview.validate,也不得编辑 game/index.html。图片生成未配置、待确认、失败或透明证据不足时不得提交最终回复。"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,7 +685,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn runtime_prompt_bundle_manifest_covers_all_embedded_sections() {
|
fn runtime_prompt_bundle_manifest_covers_all_embedded_sections() {
|
||||||
assert_eq!(RUNTIME_PROMPT_BUNDLE_ID, "genarrative.agent-runtime");
|
assert_eq!(RUNTIME_PROMPT_BUNDLE_ID, "genarrative.agent-runtime");
|
||||||
assert_eq!(RUNTIME_PROMPT_BUNDLE_VERSION, "2026-08-06.1");
|
assert_eq!(RUNTIME_PROMPT_BUNDLE_VERSION, "2026-08-07.2");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
RUNTIME_PROMPT_RUNTIME_COMPOSITION,
|
RUNTIME_PROMPT_RUNTIME_COMPOSITION,
|
||||||
&[
|
&[
|
||||||
@@ -707,7 +707,6 @@ mod tests {
|
|||||||
"isolatedAgentContract",
|
"isolatedAgentContract",
|
||||||
"platformDefault",
|
"platformDefault",
|
||||||
"platformLinux",
|
"platformLinux",
|
||||||
"codeDirectorGameChatAssetAudit",
|
|
||||||
"codePrototypeGameChat",
|
"codePrototypeGameChat",
|
||||||
"projectSupervisorGameChatRouting",
|
"projectSupervisorGameChatRouting",
|
||||||
"providerIsolatedToolContract",
|
"providerIsolatedToolContract",
|
||||||
@@ -740,24 +739,25 @@ mod tests {
|
|||||||
Some(AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE),
|
Some(AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE),
|
||||||
);
|
);
|
||||||
assert!(supervisor.contains("固定关键词和资产探测只作为"));
|
assert!(supervisor.contains("固定关键词和资产探测只作为"));
|
||||||
assert!(supervisor.contains("必须先理解用户目标"));
|
assert!(supervisor.contains("必须先自行理解用户真正要做的事"));
|
||||||
|
assert!(supervisor.contains("intentSummary"));
|
||||||
assert!(supervisor.contains("agent.route_manifest"));
|
assert!(supervisor.contains("agent.route_manifest"));
|
||||||
assert!(!supervisor.contains("第一步必须调用 `asset.list`"));
|
assert!(!supervisor.contains("第一步必须调用 `asset.list`"));
|
||||||
|
|
||||||
let code_director = game_creator_agent_runtime_role_overlay_prompt(
|
let code_prototype = game_creator_agent_runtime_role_overlay_prompt(
|
||||||
"code-director",
|
"code-prototype",
|
||||||
Some(AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE),
|
Some(AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE),
|
||||||
);
|
);
|
||||||
assert!(code_director.contains("第一步必须调用 `asset.list`"));
|
assert!(code_prototype.contains("第一步必须调用 `asset.list`"));
|
||||||
assert!(code_director.contains("missingAssetSlots"));
|
assert!(code_prototype.contains("不得仅凭用户措辞"));
|
||||||
assert!(code_director.contains("只凭用户措辞猜测缺口"));
|
assert!(code_prototype.contains("child 只能写 `assets/**`"));
|
||||||
|
|
||||||
assert!(game_creator_agent_runtime_role_overlay_prompt(
|
assert!(game_creator_agent_runtime_role_overlay_prompt(
|
||||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.is_empty());
|
.is_empty());
|
||||||
assert!(game_creator_agent_runtime_role_overlay_prompt("code-director", None).is_empty());
|
assert!(game_creator_agent_runtime_role_overlay_prompt("code-prototype", None).is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1224,7 +1224,7 @@ mod tests {
|
|||||||
assert!(with_canvas.contains("由 Runtime 形成 iconDescriptions"));
|
assert!(with_canvas.contains("由 Runtime 形成 iconDescriptions"));
|
||||||
assert!(with_canvas.contains("玩家主体及朝向/状态"));
|
assert!(with_canvas.contains("玩家主体及朝向/状态"));
|
||||||
assert!(with_canvas.contains("不得假设为塔防"));
|
assert!(with_canvas.contains("不得假设为塔防"));
|
||||||
assert!(with_canvas.contains("权威 resourceId 作为 referenceImageSrc"));
|
assert!(with_canvas.contains("权威 resourceId 作为 referenceId"));
|
||||||
assert!(with_canvas.contains("POST /api/external/v1/editor/icon-spritesheets/generations"));
|
assert!(with_canvas.contains("POST /api/external/v1/editor/icon-spritesheets/generations"));
|
||||||
assert!(with_canvas.contains("不得回退普通生图或 UI extraction"));
|
assert!(with_canvas.contains("不得回退普通生图或 UI extraction"));
|
||||||
assert!(with_canvas.contains("screenColor=auto"));
|
assert!(with_canvas.contains("screenColor=auto"));
|
||||||
|
|||||||
@@ -61,6 +61,15 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
|
|||||||
if let Some(blocker) = supervisor_orchestrator_mutation_block_at(root, agent_id, run_id, tool) {
|
if let Some(blocker) = supervisor_orchestrator_mutation_block_at(root, agent_id, run_id, tool) {
|
||||||
return blocker;
|
return blocker;
|
||||||
}
|
}
|
||||||
|
if let Some(blocker) = game_chat_delegated_art_agent_input_mutation_block(
|
||||||
|
root,
|
||||||
|
agent_id,
|
||||||
|
run_id,
|
||||||
|
tool,
|
||||||
|
&action.input,
|
||||||
|
) {
|
||||||
|
return blocker;
|
||||||
|
}
|
||||||
if let Some(blocker) =
|
if let Some(blocker) =
|
||||||
supervisor_orchestrator_mcp_mutation_block_at(root, agent_id, run_id, action).await
|
supervisor_orchestrator_mcp_mutation_block_at(root, agent_id, run_id, action).await
|
||||||
{
|
{
|
||||||
|
|||||||
+49
-2
@@ -629,6 +629,11 @@ pub(crate) fn validate_agent_runtime_autonomous_plan_liveness(
|
|||||||
.actions
|
.actions
|
||||||
.iter()
|
.iter()
|
||||||
.any(|action| action.tool.trim() == "agent.delegate");
|
.any(|action| action.tool.trim() == "agent.delegate");
|
||||||
|
let has_code_asset_route = agent_id == "code-prototype"
|
||||||
|
&& plan
|
||||||
|
.actions
|
||||||
|
.iter()
|
||||||
|
.any(|action| action.tool.trim() == "agent.route_manifest");
|
||||||
if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
|
if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
|
||||||
if let Some(failed_playtest_revision) = verification_gate.failed_playtest_revision {
|
if let Some(failed_playtest_revision) = verification_gate.failed_playtest_revision {
|
||||||
if project_revision < failed_playtest_revision {
|
if project_revision < failed_playtest_revision {
|
||||||
@@ -765,6 +770,7 @@ pub(crate) fn validate_agent_runtime_autonomous_plan_liveness(
|
|||||||
|| mutation_revision.is_some()
|
|| mutation_revision.is_some()
|
||||||
|| !plan.response.trim().is_empty()
|
|| !plan.response.trim().is_empty()
|
||||||
|| has_mutation
|
|| has_mutation
|
||||||
|
|| has_code_asset_route
|
||||||
{
|
{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -813,9 +819,10 @@ pub(in crate::agent) fn validate_agent_runtime_autonomous_read_only_delivery_pla
|
|||||||
| "command.output_read"
|
| "command.output_read"
|
||||||
| "command.poll"
|
| "command.poll"
|
||||||
| "image.inspect" => false,
|
| "image.inspect" => false,
|
||||||
"preview.validate" => agent_id != "preview-playtest",
|
"agent.route_manifest" => agent_id != "code-prototype",
|
||||||
|
"preview.validate" => agent_id != "preview-playtest" && agent_id != "code-prototype",
|
||||||
"command.run_limited" => {
|
"command.run_limited" => {
|
||||||
agent_id != "preview-readiness"
|
agent_id != "preview-readiness" && agent_id != "code-prototype"
|
||||||
|| action
|
|| action
|
||||||
.input
|
.input
|
||||||
.get("commandId")
|
.get("commandId")
|
||||||
@@ -1771,6 +1778,14 @@ mod tests {
|
|||||||
serde_json::json!({"commandId": "game.static_smoke"}),
|
serde_json::json!({"commandId": "game.static_smoke"}),
|
||||||
);
|
);
|
||||||
let preview = plan_for("preview.validate", serde_json::json!({}));
|
let preview = plan_for("preview.validate", serde_json::json!({}));
|
||||||
|
let route_manifest = plan_for(
|
||||||
|
"agent.route_manifest",
|
||||||
|
serde_json::json!({
|
||||||
|
"strategy": "use-existing-art",
|
||||||
|
"intentSummary": null,
|
||||||
|
"missingAssetSlots": [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
assert!(validate_agent_runtime_autonomous_read_only_delivery_plan(
|
assert!(validate_agent_runtime_autonomous_read_only_delivery_plan(
|
||||||
"preview-readiness",
|
"preview-readiness",
|
||||||
@@ -1784,6 +1799,38 @@ mod tests {
|
|||||||
&preview,
|
&preview,
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
assert!(validate_agent_runtime_autonomous_read_only_delivery_plan(
|
||||||
|
"code-prototype",
|
||||||
|
true,
|
||||||
|
&route_manifest,
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
let verification_gate = AgentRuntimeVerificationGate {
|
||||||
|
schema_version: "test".to_string(),
|
||||||
|
project_id: "test".to_string(),
|
||||||
|
agent_id: "code-prototype".to_string(),
|
||||||
|
run_id: "test".to_string(),
|
||||||
|
requires_verification: false,
|
||||||
|
mutation_revision: None,
|
||||||
|
verified_revision: None,
|
||||||
|
last_mutation_tool: None,
|
||||||
|
last_verification_tool: None,
|
||||||
|
last_verification_status: None,
|
||||||
|
static_smoke_verified_revision: None,
|
||||||
|
failed_playtest_revision: None,
|
||||||
|
updated_at: 0,
|
||||||
|
};
|
||||||
|
assert!(validate_agent_runtime_autonomous_plan_liveness(
|
||||||
|
"code-prototype",
|
||||||
|
AGENT_RUNTIME_AUTONOMOUS_PRE_MUTATION_LOOP_LIMIT + 1,
|
||||||
|
0,
|
||||||
|
&verification_gate,
|
||||||
|
&[],
|
||||||
|
&route_manifest,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
for (agent_id, plan) in [
|
for (agent_id, plan) in [
|
||||||
("quality-review", &smoke),
|
("quality-review", &smoke),
|
||||||
("quality-review", &preview),
|
("quality-review", &preview),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user