Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a7abfd059 | |||
| 1c5ff8f852 | |||
| ce04d8cef4 | |||
| 504e26da43 | |||
| fc14190f58 | |||
| 5e4ff54a9d | |||
| 1f5b7b250a | |||
| a5fd25f10a | |||
| 5db407c3dc | |||
| de9e8e0c7f | |||
| c958e1955b | |||
| 1b579acf47 | |||
| ff84fa7b04 | |||
| 1b56c0a586 | |||
| efdb364a52 | |||
| ebc5dfe7db | |||
| cd9faae9b2 | |||
| d85622069d | |||
| 036395c6c9 | |||
| d1f53fda4d | |||
| 9169ae0dad | |||
| a68a9621a7 | |||
| 9d374a57b1 | |||
| d9431b51da | |||
| 7f9dce5f79 | |||
| 9beb16f203 | |||
| 9b6ed8a4cf | |||
| 16deb0ad61 | |||
| 13f56e644d | |||
| e08b031761 | |||
| 70ad794b28 | |||
| ca761e093c | |||
| a17367112f | |||
| d7a0c3cffd | |||
| fa64819b6e | |||
| f748d36d21 | |||
| ecaf1e8cec | |||
| 79b153e3fa | |||
| 362edcc49d | |||
| 1e434e0cb5 | |||
| cec971c438 | |||
| fca3df964c | |||
| 70157673b6 | |||
| ac8a51ca79 | |||
| 492e9e63af | |||
| 57fae7037a | |||
| 1b010fb1f8 | |||
| 2bcfa10647 | |||
| e777236817 | |||
| d11c74212d | |||
| 82b2f853e7 | |||
| 31f83a751a | |||
| a1cafde7e9 | |||
| 825ea76b7b | |||
| 0594a90bdd | |||
| 910862fd06 | |||
| aee862532c | |||
| 42be8ea060 | |||
| 737a2266b9 |
@@ -32,7 +32,7 @@ Prefer `scripts/genarrative_external_api.py` for runnable REST calls. It uses on
|
|||||||
- Use stable references such as `objectKey`, project resource ID, or asset ID where each operation permits them. Image edit/redraw is stricter: `sourceReferenceId` accepts only a registered project resource ID or asset ID; upload confirmation alone is not enough. Use `/assets/read-url` only for temporary preview/download access.
|
- Use stable references such as `objectKey`, project resource ID, or asset ID where each operation permits them. Image edit/redraw is stricter: `sourceReferenceId` accepts only a registered project resource ID or asset ID; upload confirmation alone is not enough. Use `/assets/read-url` only for temporary preview/download access.
|
||||||
- Preserve both warning channels after completion. A general `warning` can coexist with `sliceWarning`; do not discard either.
|
- Preserve both warning channels after completion. A general `warning` can coexist with `sliceWarning`; do not discard either.
|
||||||
- Do not invent missing derivatives. A source-preserved warning means the main source remains usable but requested post-processing failed. A slice warning means the complete transparent sheet is usable but individual slices are absent.
|
- Do not invent missing derivatives. A source-preserved warning means the main source remains usable but requested post-processing failed. A slice warning means the complete transparent sheet is usable but individual slices are absent.
|
||||||
- Icon spritesheet generation accepts `sliceMode="connected-components"` (default alpha-connectivity detection) or `sliceMode="grid"`. Grid mode requires `gridX` and `gridY` (1-32); use `sliceCount` only to constrain connected-component output.
|
- Icon spritesheet generation requires an explicit `sliceMode` and has no default. Use `sliceMode="grid"` with the `gridX` and `gridY` the requirement actually names (1-32 each) only for equal grid cells or fixed slots; use `sliceMode="connected-components"` for free-form sheets or an open number of subjects, and constrain the count with `sliceCount` instead of inventing grid dimensions. `connected-components` must not carry `gridX`/`gridY`; an omitted, contradictory, or misapplied declaration returns 400 before billing.
|
||||||
- For successful `style="pixelArt"`, treat completed-result and nested resource/asset dimensions as the final logical-grid PNG dimensions. They may differ from `size`, `imageSize`, the provider image, and `canvasCompletion.placeholder`; do not rescale or reject the artifact to match those inputs.
|
- For successful `style="pixelArt"`, treat completed-result and nested resource/asset dimensions as the final logical-grid PNG dimensions. They may differ from `size`, `imageSize`, the provider image, and `canvasCompletion.placeholder`; do not rescale or reject the artifact to match those inputs.
|
||||||
- Keep generated artifacts in the canvas and asset library together. Character animation accepts `assetFolderId` and `assetLabel`; its completed result directly returns the final `assetKind="character-animation"` resource and asset with formal sequence fields. Do not create a duplicate first-frame record.
|
- Keep generated artifacts in the canvas and asset library together. Character animation accepts `assetFolderId` and `assetLabel`; its completed result directly returns the final `assetKind="character-animation"` resource and asset with formal sequence fields. Do not create a duplicate first-frame record.
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ For image edit/redraw, confirming an upload is not sufficient: create a project
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
`sliceMode` controls atlas splitting. Use `"connected-components"` (default) to detect independent opaque regions by alpha connectivity, or `"grid"` with positive `gridX` and `gridY` values (maximum 32 each). `sliceCount` optionally constrains the connected-component result.
|
`sliceMode` is required and has no default, so every request must state it. Use `"connected-components"` to detect independent opaque regions by alpha connectivity, or `"grid"` with positive `gridX` and `gridY` values (maximum 32 each) only when the requirement names equal grid cells or fixed slots; the dimensions must come from that requirement. `connected-components` must not carry `gridX`/`gridY`, and `sliceCount` constrains the connected-component result instead of expressing a grid. Omitting `sliceMode`, or contradicting the declared mode with grid dimensions, returns 400 before pricing, enqueueing, or any provider call.
|
||||||
|
|
||||||
## Common Values
|
## Common Values
|
||||||
|
|
||||||
|
|||||||
@@ -79,9 +79,9 @@ 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 `referenceId` plus concrete `iconDescriptions`. For a fixed four-category game contract it may send `sliceMode: "grid"`; for free-form assets use `sliceMode: "connected-components"` (the default).
|
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`. `sliceMode` is required and has no default: send `sliceMode: "grid"` with `gridX`/`gridY` only when the requirement itself fixes the slots or names the column/row count, and otherwise send `sliceMode: "connected-components"` (with `sliceCount` when a subject count must be constrained); never invent a grid to express "kinds of assets", and never send `gridX`/`gridY` with `connected-components`.
|
||||||
|
|
||||||
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. When using the fixed four-category contract, require response `sliceMode: "grid"` and 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. When the requirement fixes grid slots, require the response `sliceMode` to match the declared `grid` request and exactly `gridX × gridY` slices before registering the local runtime sheet; a connected-components request is instead judged by its own `sliceCount` or by the requirement, and 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.
|
||||||
|
|
||||||
Never use `assets/ui-prototype.png` as the spritesheet visual-spec reference. UI extraction is outside this canonical DAG.
|
Never use `assets/ui-prototype.png` as the spritesheet visual-spec reference. UI extraction is outside this canonical DAG.
|
||||||
|
|
||||||
|
|||||||
@@ -617,6 +617,7 @@ class GenarrativeExternalClient:
|
|||||||
self,
|
self,
|
||||||
reference_id: str,
|
reference_id: str,
|
||||||
icon_descriptions: list[str],
|
icon_descriptions: list[str],
|
||||||
|
slice_mode: str,
|
||||||
**fields: Any,
|
**fields: Any,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
reference_id = normalize_optional_text(reference_id)
|
reference_id = normalize_optional_text(reference_id)
|
||||||
@@ -625,6 +626,20 @@ class GenarrativeExternalClient:
|
|||||||
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")
|
||||||
|
slice_mode = normalize_optional_text(slice_mode)
|
||||||
|
if slice_mode not in ("connected-components", "grid"):
|
||||||
|
raise GenarrativeApiError(
|
||||||
|
"slice_mode must be declared explicitly as 'connected-components' or 'grid'; the API has no default"
|
||||||
|
)
|
||||||
|
grid_x = fields.get("gridX")
|
||||||
|
grid_y = fields.get("gridY")
|
||||||
|
if slice_mode == "grid":
|
||||||
|
if grid_x is None or grid_y is None:
|
||||||
|
raise GenarrativeApiError("slice_mode='grid' requires both gridX and gridY")
|
||||||
|
elif grid_x is not None or grid_y is not None:
|
||||||
|
raise GenarrativeApiError(
|
||||||
|
"slice_mode='connected-components' must not carry gridX/gridY"
|
||||||
|
)
|
||||||
label = fields.get("assetLabel", "图标图集")
|
label = fields.get("assetLabel", "图标图集")
|
||||||
self._apply_canvas_session_fields(fields, label, 1024, 1024)
|
self._apply_canvas_session_fields(fields, label, 1024, 1024)
|
||||||
fields.setdefault("screenColor", "auto")
|
fields.setdefault("screenColor", "auto")
|
||||||
@@ -635,6 +650,7 @@ class GenarrativeExternalClient:
|
|||||||
**fields,
|
**fields,
|
||||||
"referenceId": reference_id,
|
"referenceId": reference_id,
|
||||||
"iconDescriptions": descriptions,
|
"iconDescriptions": descriptions,
|
||||||
|
"sliceMode": slice_mode,
|
||||||
},
|
},
|
||||||
idempotency_key=idempotency_key,
|
idempotency_key=idempotency_key,
|
||||||
)
|
)
|
||||||
@@ -879,9 +895,9 @@ def _self_test() -> None:
|
|||||||
client.generate_icon_spritesheet(
|
client.generate_icon_spritesheet(
|
||||||
"editor-resource-spec",
|
"editor-resource-spec",
|
||||||
["蛇头向上", "蛇身直线", "转角", "尾部", "四类食物"],
|
["蛇头向上", "蛇身直线", "转角", "尾部", "四类食物"],
|
||||||
|
"connected-components",
|
||||||
canvasSession=session,
|
canvasSession=session,
|
||||||
assetLabel="贪吃蛇透明图集",
|
assetLabel="贪吃蛇透明图集",
|
||||||
sliceMode="connected-components",
|
|
||||||
referenceId="must-not-override-explicit-reference",
|
referenceId="must-not-override-explicit-reference",
|
||||||
iconDescriptions=["不得覆盖显式图标描述"],
|
iconDescriptions=["不得覆盖显式图标描述"],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -158,6 +158,15 @@ ALIYUN_OSS_POST_EXPIRE_SECONDS="600"
|
|||||||
ALIYUN_OSS_POST_MAX_SIZE_BYTES="20971520"
|
ALIYUN_OSS_POST_MAX_SIZE_BYTES="20971520"
|
||||||
ALIYUN_OSS_SUCCESS_ACTION_STATUS="200"
|
ALIYUN_OSS_SUCCESS_ACTION_STATUS="200"
|
||||||
|
|
||||||
|
# AGC 项目定时快照上传目标。对象只落在服务端私有前缀
|
||||||
|
# `agc/project-snapshots/v1/{user}/{project}/` 下,客户端直传票据不覆盖该前缀。
|
||||||
|
# bucket 与凭据可以与资源 bucket 分离;凭据未设置时回退使用 ALIYUN_OSS_ACCESS_KEY_*,
|
||||||
|
# 但 bucket / endpoint 默认指向 AGC 发行 bucket,需要该凭据具备目标 bucket 的 PutObject 权限。
|
||||||
|
GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_BUCKET="agc-dev"
|
||||||
|
GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_ENDPOINT="oss-rg-china-mainland.aliyuncs.com"
|
||||||
|
GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_ACCESS_KEY_ID=""
|
||||||
|
GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_ACCESS_KEY_SECRET=""
|
||||||
|
|
||||||
# BgFilter 受限资源 worker。父 api-server / external-generation-worker 与唯一的
|
# BgFilter 受限资源 worker。父 api-server / external-generation-worker 与唯一的
|
||||||
# `GENARRATIVE_PROCESS_ROLE=bgfilter-worker` 进程必须使用同一个内部 Token。
|
# `GENARRATIVE_PROCESS_ROLE=bgfilter-worker` 进程必须使用同一个内部 Token。
|
||||||
# `npm run dev` 与 `npm run dev:api-server` 都会自动带起并验活唯一 worker,不要再开第二个终端重复启动。
|
# `npm run dev` 与 `npm run dev:api-server` 都会自动带起并验活唯一 worker,不要再开第二个终端重复启动。
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: project-ci-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ vi.mock('../api/adminApiClient', () => ({
|
|||||||
|
|
||||||
interface MockIntersectionObserverController {
|
interface MockIntersectionObserverController {
|
||||||
enter: (target: Element) => void;
|
enter: (target: Element) => void;
|
||||||
|
enterAll: (targets: Element[]) => void;
|
||||||
isObserved: (target: Element) => boolean;
|
isObserved: (target: Element) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +107,25 @@ function installIntersectionObserverMock(): MockIntersectionObserverController {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
enterAll(targets) {
|
||||||
|
act(() => {
|
||||||
|
for (const target of targets) {
|
||||||
|
const record = observed.get(target);
|
||||||
|
if (!record) {
|
||||||
|
throw new Error('目标缩略图尚未进入 IntersectionObserver');
|
||||||
|
}
|
||||||
|
record.callback(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
isIntersecting: true,
|
||||||
|
target,
|
||||||
|
} as IntersectionObserverEntry,
|
||||||
|
],
|
||||||
|
record.observer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
isObserved(target) {
|
isObserved(target) {
|
||||||
return observed.has(target);
|
return observed.has(target);
|
||||||
},
|
},
|
||||||
@@ -753,10 +773,10 @@ test('后台素材查询为大量同时可见的缩略图持续错峰换签', as
|
|||||||
const thumbnails = entries.map((entry) =>
|
const thumbnails = entries.map((entry) =>
|
||||||
thumbnailElementForLabel(entry.label),
|
thumbnailElementForLabel(entry.label),
|
||||||
);
|
);
|
||||||
thumbnails.forEach((thumbnail) => {
|
for (const thumbnail of thumbnails) {
|
||||||
expect(observer.isObserved(thumbnail)).toBe(true);
|
expect(observer.isObserved(thumbnail)).toBe(true);
|
||||||
observer.enter(thumbnail);
|
}
|
||||||
});
|
observer.enterAll(thumbnails);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
});
|
});
|
||||||
@@ -776,7 +796,7 @@ test('后台素材查询为大量同时可见的缩略图持续错峰换签', as
|
|||||||
await vi.advanceTimersByTimeAsync(200);
|
await vi.advanceTimersByTimeAsync(200);
|
||||||
});
|
});
|
||||||
expect(getAdminAssetReadUrl).toHaveBeenCalledTimes(105);
|
expect(getAdminAssetReadUrl).toHaveBeenCalledTimes(105);
|
||||||
});
|
}, 10_000);
|
||||||
|
|
||||||
test('后台素材查询读取更多后为新进入可视区域的素材换签', async () => {
|
test('后台素材查询读取更多后为新进入可视区域的素材换签', async () => {
|
||||||
const observer = installIntersectionObserverMock();
|
const observer = installIntersectionObserverMock();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"autoCompactTokenLimit": 64000,
|
"autoCompactTokenLimit": 64000,
|
||||||
"toolOutputTokenLimit": 12000,
|
"toolOutputTokenLimit": 12000,
|
||||||
"requestTimeoutMs": 180000,
|
"requestTimeoutMs": 180000,
|
||||||
"maxRetries": 2,
|
"maxRetries": 10,
|
||||||
"retryBackoffMs": 500
|
"retryBackoffMs": 500
|
||||||
},
|
},
|
||||||
"agentLlm": {}
|
"agentLlm": {}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@genarrative/ai-game-creator-shell",
|
"name": "@genarrative/ai-game-creator-shell",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.45",
|
"version": "0.1.47",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/start-tauri-dev.mjs",
|
"dev": "node scripts/start-tauri-dev.mjs",
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
"@tauri-apps/plugin-dialog": "^2.7.2",
|
"@tauri-apps/plugin-dialog": "^2.7.2",
|
||||||
"@tauri-apps/plugin-http": "^2.5.9",
|
"@tauri-apps/plugin-http": "^2.5.9",
|
||||||
"@tauri-apps/plugin-opener": "~2",
|
"@tauri-apps/plugin-opener": "~2",
|
||||||
|
"@tauri-apps/plugin-updater": "2.11.0",
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
"@vitejs/plugin-react": "^5.0.4",
|
||||||
"focus-trap-react": "^12.0.3",
|
"focus-trap-react": "^12.0.3",
|
||||||
"lexical": "^0.47.0",
|
"lexical": "^0.47.0",
|
||||||
@@ -57,6 +58,7 @@
|
|||||||
"react-colorful": "^5.8.0",
|
"react-colorful": "^5.8.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
|
"react-window": "^1.8.11",
|
||||||
"rehype-highlight": "^7.0.2",
|
"rehype-highlight": "^7.0.2",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
"vite": "^6.2.0",
|
"vite": "^6.2.0",
|
||||||
@@ -70,6 +72,7 @@
|
|||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@types/react-window": "^1.8.8",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.14",
|
||||||
"typescript": "~5.8.2",
|
"typescript": "~5.8.2",
|
||||||
"vitest": "^0.34.6"
|
"vitest": "^0.34.6"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { spawnSync } from 'node:child_process';
|
import { spawnSync } from 'node:child_process';
|
||||||
import { createHash } from 'node:crypto';
|
import { createHash } from 'node:crypto';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
@@ -28,14 +29,30 @@ const cargoManifestPath = path.join(appRoot, 'src-tauri', 'Cargo.toml');
|
|||||||
const cargoLockPath = path.join(appRoot, 'src-tauri', 'Cargo.lock');
|
const cargoLockPath = path.join(appRoot, 'src-tauri', 'Cargo.lock');
|
||||||
const defaultOssBaseUrl =
|
const defaultOssBaseUrl =
|
||||||
'https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc';
|
'https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc';
|
||||||
const updateManifestUrl =
|
|
||||||
process.env.VITE_AGC_UPDATE_MANIFEST_URL?.trim() ||
|
/**
|
||||||
`${process.env.AGC_UPDATE_OSS_BASE_URL?.trim() || defaultOssBaseUrl}/latest.json`;
|
* 发布渠道 → 目标平台。渠道名会进入 OSS 路径并烘焙进客户端端点,
|
||||||
|
* 一旦发布就不能改名(改名等于已发布客户端再也找不到更新)。
|
||||||
|
*/
|
||||||
|
const releaseChannels = {
|
||||||
|
'dev-win': 'windows',
|
||||||
|
'dev-mac': 'darwin',
|
||||||
|
};
|
||||||
|
|
||||||
|
function ossBaseUrl() {
|
||||||
|
return (
|
||||||
|
process.env.AGC_UPDATE_OSS_BASE_URL?.trim() || defaultOssBaseUrl
|
||||||
|
).replace(/\/+$/u, '');
|
||||||
|
}
|
||||||
|
|
||||||
function readPackageJson() {
|
function readPackageJson() {
|
||||||
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readReleaseNotes() {
|
||||||
|
return process.env.AGC_UPDATE_RELEASE_NOTES?.trim() || '';
|
||||||
|
}
|
||||||
|
|
||||||
export function compareVersions(left, right) {
|
export function compareVersions(left, right) {
|
||||||
const leftParts = left.split('.').map(Number);
|
const leftParts = left.split('.').map(Number);
|
||||||
const rightParts = right.split('.').map(Number);
|
const rightParts = right.split('.').map(Number);
|
||||||
@@ -66,26 +83,87 @@ export function nextPatchVersion(localVersion, remoteVersion) {
|
|||||||
return `${major}.${minor}.${patch + 1}`;
|
return `${major}.${minor}.${patch + 1}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readRemoteVersion() {
|
export function resolveReleasePlatform(target = releaseTarget) {
|
||||||
|
if (target.includes('windows')) return 'windows';
|
||||||
|
if (target.includes('apple-darwin')) return 'darwin';
|
||||||
|
if (target.includes('linux')) return 'linux';
|
||||||
|
throw new Error(`不支持的发布目标:${target}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveReleaseChannel(
|
||||||
|
env = process.env,
|
||||||
|
target = releaseTarget,
|
||||||
|
) {
|
||||||
|
const platform = resolveReleasePlatform(target);
|
||||||
|
const requested = env.AGC_UPDATE_CHANNEL?.trim();
|
||||||
|
if (requested) {
|
||||||
|
const channelPlatform = releaseChannels[requested];
|
||||||
|
if (!channelPlatform) {
|
||||||
|
throw new Error(
|
||||||
|
`未知发布渠道 ${requested};当前支持:${Object.keys(releaseChannels).join('、')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (channelPlatform !== platform) {
|
||||||
|
throw new Error(
|
||||||
|
`渠道 ${requested} 只能用于 ${channelPlatform} 目标,当前构建目标为 ${target}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return requested;
|
||||||
|
}
|
||||||
|
const defaultChannel = Object.entries(releaseChannels).find(
|
||||||
|
([, channelPlatform]) => channelPlatform === platform,
|
||||||
|
)?.[0];
|
||||||
|
if (!defaultChannel) {
|
||||||
|
throw new Error(
|
||||||
|
`目标 ${target} 没有默认发布渠道,请显式设置 AGC_UPDATE_CHANNEL`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return defaultChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateManifestUrl(channel = resolveReleaseChannel()) {
|
||||||
|
return `${ossBaseUrl()}/${channel}/latest.json`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新插件按运行时平台键查找清单条目:universal macOS 包同时挂
|
||||||
|
* `darwin-aarch64` 与 `darwin-x86_64`,单架构目标只挂对应键。
|
||||||
|
*/
|
||||||
|
export function resolveManifestPlatformKeys(target = releaseTarget) {
|
||||||
|
if (target === 'universal-apple-darwin') {
|
||||||
|
return ['darwin-aarch64', 'darwin-x86_64'];
|
||||||
|
}
|
||||||
|
if (target === 'aarch64-apple-darwin') return ['darwin-aarch64'];
|
||||||
|
if (target === 'x86_64-apple-darwin') return ['darwin-x86_64'];
|
||||||
|
if (target.includes('windows')) {
|
||||||
|
return [
|
||||||
|
target.startsWith('aarch64') ? 'windows-aarch64' : 'windows-x86_64',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
throw new Error(`不支持的发布目标:${target}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readRemoteVersion(channel = resolveReleaseChannel()) {
|
||||||
|
const manifestUrl = updateManifestUrl(channel);
|
||||||
let response;
|
let response;
|
||||||
try {
|
try {
|
||||||
response = await fetch(updateManifestUrl, {
|
response = await fetch(manifestUrl, {
|
||||||
headers: { Accept: 'application/json' },
|
headers: { Accept: 'application/json' },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`读取 OSS 版本清单失败:${error.message}`);
|
throw new Error(`读取 OSS 渠道清单失败:${error.message}`);
|
||||||
}
|
}
|
||||||
if (response.status === 404) return null;
|
if (response.status === 404) return null;
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`读取 OSS 版本清单失败:HTTP ${response.status}`);
|
throw new Error(`读取 OSS 渠道清单失败:HTTP ${response.status}`);
|
||||||
}
|
}
|
||||||
let manifest;
|
let manifest;
|
||||||
try {
|
try {
|
||||||
manifest = await response.json();
|
manifest = await response.json();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`OSS 版本清单不是有效 JSON:${error.message}`);
|
throw new Error(`OSS 渠道清单不是有效 JSON:${error.message}`);
|
||||||
}
|
}
|
||||||
return parseVersion(manifest?.version, 'OSS版本清单 version');
|
return parseVersion(manifest?.version, 'OSS渠道清单 version');
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceVersionLine(source, version, pattern, label) {
|
function replaceVersionLine(source, version, pattern, label) {
|
||||||
@@ -94,8 +172,9 @@ function replaceVersionLine(source, version, pattern, label) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function prepareReleaseVersion() {
|
export async function prepareReleaseVersion() {
|
||||||
|
const channel = resolveReleaseChannel();
|
||||||
const localVersion = parseVersion(readPackageJson().version, '本地版本');
|
const localVersion = parseVersion(readPackageJson().version, '本地版本');
|
||||||
const remoteVersion = await readRemoteVersion();
|
const remoteVersion = await readRemoteVersion(channel);
|
||||||
const requestedVersion = process.env.AGC_RELEASE_VERSION?.trim();
|
const requestedVersion = process.env.AGC_RELEASE_VERSION?.trim();
|
||||||
const nextVersion = requestedVersion
|
const nextVersion = requestedVersion
|
||||||
? parseVersion(requestedVersion, '指定版本')
|
? parseVersion(requestedVersion, '指定版本')
|
||||||
@@ -158,8 +237,8 @@ export async function prepareReleaseVersion() {
|
|||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
requestedVersion
|
requestedVersion
|
||||||
? `[ai-game-creator-shell] 使用指定版本 ${nextVersion}(本地 ${localVersion} / OSS ${remoteVersion ?? '不存在'})`
|
? `[ai-game-creator-shell] 渠道 ${channel} 使用指定版本 ${nextVersion}(本地 ${localVersion} / OSS ${remoteVersion ?? '不存在'})`
|
||||||
: `[ai-game-creator-shell] 版本 ${localVersion} / OSS ${remoteVersion ?? '不存在'} -> ${nextVersion}`,
|
: `[ai-game-creator-shell] 渠道 ${channel} 版本 ${localVersion} / OSS ${remoteVersion ?? '不存在'} -> ${nextVersion}`,
|
||||||
);
|
);
|
||||||
return nextVersion;
|
return nextVersion;
|
||||||
}
|
}
|
||||||
@@ -187,18 +266,43 @@ export function buildTauriBuildArguments(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 渠道端点必须由构建期注入:官方更新插件的端点配置不支持运行期改渠道。 */
|
||||||
|
export function createChannelConfig(channel = resolveReleaseChannel()) {
|
||||||
|
return {
|
||||||
|
plugins: {
|
||||||
|
updater: {
|
||||||
|
endpoints: [updateManifestUrl(channel)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeChannelConfigFile(channel) {
|
||||||
|
const configPath = path.join(
|
||||||
|
os.tmpdir(),
|
||||||
|
`agc-tauri-channel-${channel}.json`,
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
configPath,
|
||||||
|
`${JSON.stringify(createChannelConfig(channel), null, 2)}\n`,
|
||||||
|
);
|
||||||
|
return configPath;
|
||||||
|
}
|
||||||
|
|
||||||
export function runTauriBuild(args = []) {
|
export function runTauriBuild(args = []) {
|
||||||
|
const tauriArguments = buildTauriBuildArguments(args);
|
||||||
|
if (!tauriArguments.includes('--config') && !tauriArguments.includes('-c')) {
|
||||||
|
const channel = resolveReleaseChannel();
|
||||||
|
const configPath = writeChannelConfigFile(channel);
|
||||||
|
console.log(
|
||||||
|
`[ai-game-creator-shell] 渠道 ${channel} 端点配置:${configPath}`,
|
||||||
|
);
|
||||||
|
tauriArguments.push('--config', configPath);
|
||||||
|
}
|
||||||
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||||
const result = spawnSync(
|
const result = spawnSync(
|
||||||
npmCommand,
|
npmCommand,
|
||||||
[
|
['--prefix', '../..', 'exec', 'tauri', '--', ...tauriArguments],
|
||||||
'--prefix',
|
|
||||||
'../..',
|
|
||||||
'exec',
|
|
||||||
'tauri',
|
|
||||||
'--',
|
|
||||||
...buildTauriBuildArguments(args),
|
|
||||||
],
|
|
||||||
{ cwd: appRoot, stdio: 'inherit', shell: process.platform === 'win32' },
|
{ cwd: appRoot, stdio: 'inherit', shell: process.platform === 'win32' },
|
||||||
);
|
);
|
||||||
if (result.error) throw result.error;
|
if (result.error) throw result.error;
|
||||||
@@ -216,10 +320,14 @@ function listFiles(root) {
|
|||||||
function artifactPriority(filePath) {
|
function artifactPriority(filePath) {
|
||||||
const name = path.basename(filePath).toLowerCase();
|
const name = path.basename(filePath).toLowerCase();
|
||||||
if (releaseTarget.includes('windows')) return name.endsWith('.exe') ? 0 : 99;
|
if (releaseTarget.includes('windows')) return name.endsWith('.exe') ? 0 : 99;
|
||||||
if (process.platform === 'darwin') return name.endsWith('.dmg') ? 0 : 99;
|
// 更新链路要的是 updater 产物(macOS 为 .app.tar.gz),dmg 只作人工分发。
|
||||||
if (name.endsWith('.appimage')) return 0;
|
if (releaseTarget.includes('apple-darwin')) {
|
||||||
if (name.endsWith('.deb')) return 1;
|
return name.endsWith('.app.tar.gz') ? 0 : 99;
|
||||||
if (name.endsWith('.rpm')) return 2;
|
}
|
||||||
|
if (name.endsWith('.appimage.tar.gz')) return 0;
|
||||||
|
if (name.endsWith('.appimage')) return 1;
|
||||||
|
if (name.endsWith('.deb')) return 2;
|
||||||
|
if (name.endsWith('.rpm')) return 3;
|
||||||
return 99;
|
return 99;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,36 +350,100 @@ export function selectReleaseArtifact(files) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createUpdateManifest(artifactPath) {
|
function readUpdaterSignature(artifactPath) {
|
||||||
|
const signaturePath = `${artifactPath}.sig`;
|
||||||
|
if (!fs.existsSync(signaturePath)) {
|
||||||
|
throw new Error(
|
||||||
|
`缺少更新包签名:${signaturePath};需要 bundle.createUpdaterArtifacts 与签名私钥(TAURI_SIGNING_PRIVATE_KEY / TAURI_SIGNING_PRIVATE_KEY_PATH)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const signature = fs.readFileSync(signaturePath, 'utf8').trim();
|
||||||
|
if (!signature) throw new Error(`更新包签名为空:${signaturePath}`);
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createUpdateManifest(
|
||||||
|
artifactPath,
|
||||||
|
{
|
||||||
|
channel = resolveReleaseChannel(),
|
||||||
|
target = releaseTarget,
|
||||||
|
publishedAt = new Date().toISOString(),
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
const signature = readUpdaterSignature(artifactPath);
|
||||||
|
const version = readPackageJson().version;
|
||||||
|
const fileName = path.basename(artifactPath);
|
||||||
|
const url = `${ossBaseUrl()}/${channel}/${encodeURIComponent(version)}/${encodeURIComponent(fileName)}`;
|
||||||
|
const platforms = {};
|
||||||
|
for (const key of resolveManifestPlatformKeys(target)) {
|
||||||
|
platforms[key] = { signature, url };
|
||||||
|
}
|
||||||
|
const notes = readReleaseNotes();
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
...(notes ? { notes } : {}),
|
||||||
|
pub_date: publishedAt,
|
||||||
|
platforms,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 旧协议(sha256)清单:只用于把已发布客户端带到新渠道协议,一个版本周期后整条删除。 */
|
||||||
|
export function createLegacyUpdateManifest(
|
||||||
|
artifactPath,
|
||||||
|
{ channel = resolveReleaseChannel() } = {},
|
||||||
|
) {
|
||||||
const bytes = fs.readFileSync(artifactPath);
|
const bytes = fs.readFileSync(artifactPath);
|
||||||
const version = readPackageJson().version;
|
const version = readPackageJson().version;
|
||||||
const fileName = path.basename(artifactPath);
|
const fileName = path.basename(artifactPath);
|
||||||
const baseUrl = (
|
const notes = readReleaseNotes();
|
||||||
process.env.AGC_UPDATE_OSS_BASE_URL?.trim() || defaultOssBaseUrl
|
|
||||||
).replace(/\/+$/u, '');
|
|
||||||
const encodedFileName = encodeURIComponent(fileName).replace(/%2F/giu, '/');
|
|
||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
downloadUrl: `${baseUrl}/${encodeURIComponent(version)}/${encodedFileName}`,
|
downloadUrl: `${ossBaseUrl()}/${channel}/${encodeURIComponent(version)}/${encodeURIComponent(fileName)}`,
|
||||||
sha256: createHash('sha256').update(bytes).digest('hex'),
|
sha256: createHash('sha256').update(bytes).digest('hex'),
|
||||||
size: bytes.length,
|
size: bytes.length,
|
||||||
...(process.env.AGC_UPDATE_RELEASE_NOTES?.trim()
|
...(notes ? { releaseNotes: notes } : {}),
|
||||||
? { releaseNotes: process.env.AGC_UPDATE_RELEASE_NOTES.trim() }
|
|
||||||
: {}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateUpdateManifest() {
|
export function generateUpdateManifest() {
|
||||||
|
const channel = resolveReleaseChannel();
|
||||||
const artifact = selectReleaseArtifact(listFiles(bundleRoot));
|
const artifact = selectReleaseArtifact(listFiles(bundleRoot));
|
||||||
if (!artifact) {
|
if (!artifact) {
|
||||||
throw new Error(`未找到可发布的 AGC 安装包:${bundleRoot}`);
|
throw new Error(`未找到可发布的 AGC 安装包:${bundleRoot}`);
|
||||||
}
|
}
|
||||||
const manifest = createUpdateManifest(artifact);
|
const manifest = createUpdateManifest(artifact, { channel });
|
||||||
const manifestPath = path.join(bundleRoot, 'latest.json');
|
const manifestPath = path.join(bundleRoot, 'latest.json');
|
||||||
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
||||||
console.log(`[ai-game-creator-shell] 已生成 ${manifestPath}`);
|
const legacyManifest =
|
||||||
|
channel === 'dev-win'
|
||||||
|
? createLegacyUpdateManifest(artifact, { channel })
|
||||||
|
: null;
|
||||||
|
const legacyManifestPath = legacyManifest
|
||||||
|
? path.join(bundleRoot, 'legacy-latest.json')
|
||||||
|
: null;
|
||||||
|
if (legacyManifest && legacyManifestPath) {
|
||||||
|
fs.writeFileSync(
|
||||||
|
legacyManifestPath,
|
||||||
|
`${JSON.stringify(legacyManifest, null, 2)}\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
`[ai-game-creator-shell] 渠道 ${channel}:已生成 ${manifestPath}`,
|
||||||
|
);
|
||||||
console.log(`[ai-game-creator-shell] 安装包:${artifact}`);
|
console.log(`[ai-game-creator-shell] 安装包:${artifact}`);
|
||||||
return { artifact, manifestPath, manifest };
|
if (legacyManifestPath) {
|
||||||
|
console.log(
|
||||||
|
`[ai-game-creator-shell] 旧协议迁移清单:${legacyManifestPath}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
channel,
|
||||||
|
artifact,
|
||||||
|
manifest,
|
||||||
|
manifestPath,
|
||||||
|
legacyManifest,
|
||||||
|
legacyManifestPath,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,24 +1,61 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { readFileSync } from 'node:fs';
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
import { test } from 'node:test';
|
import { test } from 'node:test';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
compareVersions,
|
compareVersions,
|
||||||
|
createChannelConfig,
|
||||||
|
createLegacyUpdateManifest,
|
||||||
createUpdateManifest,
|
createUpdateManifest,
|
||||||
nextPatchVersion,
|
nextPatchVersion,
|
||||||
|
resolveManifestPlatformKeys,
|
||||||
|
resolveReleaseChannel,
|
||||||
selectReleaseArtifact,
|
selectReleaseArtifact,
|
||||||
|
updateManifestUrl,
|
||||||
} from './build-release.mjs';
|
} from './build-release.mjs';
|
||||||
|
|
||||||
test('selects an explicit release artifact when configured', () => {
|
const windowsTarget = 'x86_64-pc-windows-msvc';
|
||||||
const artifactPath = new URL('../package.json', import.meta.url).pathname;
|
const universalTarget = 'universal-apple-darwin';
|
||||||
const previous = process.env.AGC_UPDATE_ARTIFACT;
|
|
||||||
process.env.AGC_UPDATE_ARTIFACT = artifactPath;
|
function withEnv(overrides, run) {
|
||||||
try {
|
const previous = new Map();
|
||||||
assert.equal(selectReleaseArtifact([]), artifactPath);
|
for (const [key, value] of Object.entries(overrides)) {
|
||||||
} finally {
|
previous.set(key, process.env[key]);
|
||||||
if (previous === undefined) delete process.env.AGC_UPDATE_ARTIFACT;
|
if (value === undefined) delete process.env[key];
|
||||||
else process.env.AGC_UPDATE_ARTIFACT = previous;
|
else process.env[key] = value;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
return run();
|
||||||
|
} finally {
|
||||||
|
for (const [key, value] of previous) {
|
||||||
|
if (value === undefined) delete process.env[key];
|
||||||
|
else process.env[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function withSignedArtifact(fileName, run) {
|
||||||
|
const directory = mkdtempSync(path.join(os.tmpdir(), 'agc-release-test-'));
|
||||||
|
try {
|
||||||
|
const artifact = path.join(directory, fileName);
|
||||||
|
writeFileSync(artifact, 'installation package');
|
||||||
|
writeFileSync(`${artifact}.sig`, 'signature-content\n');
|
||||||
|
return run(artifact);
|
||||||
|
} finally {
|
||||||
|
rmSync(directory, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('selects an explicit release artifact when configured', () => {
|
||||||
|
const artifactPath = fileURLToPath(
|
||||||
|
new URL('../package.json', import.meta.url),
|
||||||
|
);
|
||||||
|
withEnv({ AGC_UPDATE_ARTIFACT: artifactPath }, () => {
|
||||||
|
assert.equal(selectReleaseArtifact([]), artifactPath);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('does not select unsupported files', () => {
|
test('does not select unsupported files', () => {
|
||||||
@@ -28,47 +65,123 @@ test('does not select unsupported files', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('manifest contains version, download URL and integrity fields', () => {
|
test('resolves the channel from the target platform and rejects mismatches', () => {
|
||||||
const manifest = createUpdateManifest(
|
assert.equal(resolveReleaseChannel({}, windowsTarget), 'dev-win');
|
||||||
new URL('../package.json', import.meta.url).pathname,
|
assert.equal(resolveReleaseChannel({}, universalTarget), 'dev-mac');
|
||||||
|
assert.equal(
|
||||||
|
resolveReleaseChannel({ AGC_UPDATE_CHANNEL: 'dev-mac' }, universalTarget),
|
||||||
|
'dev-mac',
|
||||||
);
|
);
|
||||||
assert.match(manifest.version, /^\d+\.\d+\.\d+$/u);
|
assert.throws(
|
||||||
assert.match(
|
() =>
|
||||||
manifest.downloadUrl,
|
resolveReleaseChannel({ AGC_UPDATE_CHANNEL: 'dev-mac' }, windowsTarget),
|
||||||
new RegExp(`/agc/${manifest.version}/package\\.json$`, 'u'),
|
/只能用于 darwin 目标/u,
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
resolveReleaseChannel({ AGC_UPDATE_CHANNEL: 'beta-win' }, windowsTarget),
|
||||||
|
/未知发布渠道/u,
|
||||||
);
|
);
|
||||||
assert.equal(manifest.sha256.length, 64);
|
|
||||||
assert.equal(typeof manifest.size, 'number');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('manifest preserves multiline release notes', () => {
|
test('channel manifest URL and build-time endpoint follow the channel', () => {
|
||||||
const previous = process.env.AGC_UPDATE_RELEASE_NOTES;
|
withEnv({ AGC_UPDATE_OSS_BASE_URL: undefined }, () => {
|
||||||
process.env.AGC_UPDATE_RELEASE_NOTES = '第一行\n第二行\r\n第三行';
|
assert.equal(
|
||||||
try {
|
updateManifestUrl('dev-win'),
|
||||||
const manifest = createUpdateManifest(
|
'https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/dev-win/latest.json',
|
||||||
new URL('../package.json', import.meta.url).pathname,
|
);
|
||||||
|
assert.deepEqual(createChannelConfig('dev-mac'), {
|
||||||
|
plugins: {
|
||||||
|
updater: {
|
||||||
|
endpoints: [
|
||||||
|
'https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/dev-mac/latest.json',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('universal macOS builds publish one artifact under both platform keys', () => {
|
||||||
|
assert.deepEqual(resolveManifestPlatformKeys(universalTarget), [
|
||||||
|
'darwin-aarch64',
|
||||||
|
'darwin-x86_64',
|
||||||
|
]);
|
||||||
|
assert.deepEqual(resolveManifestPlatformKeys(windowsTarget), [
|
||||||
|
'windows-x86_64',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('channel manifest carries version, platform keys and signature', () => {
|
||||||
|
withSignedArtifact('陶泥儿_0.1.48_x64-setup.exe', (artifact) => {
|
||||||
|
withEnv({ AGC_UPDATE_RELEASE_NOTES: '修复与改进' }, () => {
|
||||||
|
const manifest = createUpdateManifest(artifact, {
|
||||||
|
channel: 'dev-win',
|
||||||
|
target: windowsTarget,
|
||||||
|
publishedAt: '2026-09-17T00:00:00.000Z',
|
||||||
|
});
|
||||||
|
assert.match(manifest.version, /^\d+\.\d+\.\d+$/u);
|
||||||
|
assert.equal(manifest.notes, '修复与改进');
|
||||||
|
assert.equal(manifest.pub_date, '2026-09-17T00:00:00.000Z');
|
||||||
|
assert.deepEqual(Object.keys(manifest.platforms), ['windows-x86_64']);
|
||||||
|
assert.equal(
|
||||||
|
manifest.platforms['windows-x86_64'].signature,
|
||||||
|
'signature-content',
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
manifest.platforms['windows-x86_64'].url,
|
||||||
|
new RegExp(`/agc/dev-win/${manifest.version}/`, 'u'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('missing signature fails the channel manifest closed', () => {
|
||||||
|
const directory = mkdtempSync(path.join(os.tmpdir(), 'agc-release-test-'));
|
||||||
|
try {
|
||||||
|
const artifact = path.join(directory, '陶泥儿_0.1.48_x64-setup.exe');
|
||||||
|
writeFileSync(artifact, 'installation package');
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
createUpdateManifest(artifact, {
|
||||||
|
channel: 'dev-win',
|
||||||
|
target: windowsTarget,
|
||||||
|
}),
|
||||||
|
/缺少更新包签名/u,
|
||||||
);
|
);
|
||||||
assert.equal(manifest.releaseNotes, '第一行\n第二行\r\n第三行');
|
|
||||||
} finally {
|
} finally {
|
||||||
if (previous === undefined) delete process.env.AGC_UPDATE_RELEASE_NOTES;
|
rmSync(directory, { recursive: true, force: true });
|
||||||
else process.env.AGC_UPDATE_RELEASE_NOTES = previous;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('next release version follows the higher local or OSS version', () => {
|
test('legacy manifest keeps the sha256 contract of published clients', () => {
|
||||||
|
withSignedArtifact('陶泥儿_0.1.48_x64-setup.exe', (artifact) => {
|
||||||
|
const legacy = createLegacyUpdateManifest(artifact, {
|
||||||
|
channel: 'dev-win',
|
||||||
|
});
|
||||||
|
assert.match(legacy.version, /^\d+\.\d+\.\d+$/u);
|
||||||
|
assert.equal(legacy.sha256.length, 64);
|
||||||
|
assert.equal(legacy.size, 'installation package'.length);
|
||||||
|
assert.match(legacy.downloadUrl, /\/agc\/dev-win\/[\d.]+\//u);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('next release version follows the higher local or channel version', () => {
|
||||||
assert.equal(compareVersions('0.1.15', '0.1.12'), 1);
|
assert.equal(compareVersions('0.1.15', '0.1.12'), 1);
|
||||||
assert.equal(nextPatchVersion('0.1.12', '0.1.15'), '0.1.16');
|
assert.equal(nextPatchVersion('0.1.12', '0.1.15'), '0.1.16');
|
||||||
assert.equal(nextPatchVersion('0.1.18', '0.1.15'), '0.1.19');
|
assert.equal(nextPatchVersion('0.1.18', '0.1.15'), '0.1.19');
|
||||||
assert.equal(nextPatchVersion('0.1.12', null), '0.1.13');
|
assert.equal(nextPatchVersion('0.1.12', null), '0.1.13');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('release upload forces overwrite for versioned artifact and latest pointer', () => {
|
test('release upload forces overwrite for artifact, signature and channel pointers', () => {
|
||||||
const source = readFileSync(
|
const source = readFileSync(
|
||||||
new URL('./release-upload.mjs', import.meta.url),
|
new URL('./release-upload.mjs', import.meta.url),
|
||||||
'utf8',
|
'utf8',
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
(source.match(/runOssutil\(\['cp', '--force'/gu) ?? []).length,
|
(source.match(/runOssutil\(\[\s*'cp',\s*'--force'/gu) ?? []).length,
|
||||||
2,
|
4,
|
||||||
);
|
);
|
||||||
|
assert.match(source, /agc\/\$\{channel\}\/latest\.json/u);
|
||||||
|
assert.match(source, /agc\/latest\.json/u);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -121,6 +121,10 @@ const allowedUncalledTauriCommands = [
|
|||||||
'open_game_creator_launcher_window',
|
'open_game_creator_launcher_window',
|
||||||
'open_game_creator_workspace_window',
|
'open_game_creator_workspace_window',
|
||||||
'read_direct_project_conversation',
|
'read_direct_project_conversation',
|
||||||
|
// 项目定时快照上传只在 Rust 侧触发(周期定时器 / 工作区窗口关闭)与排障调用;
|
||||||
|
// 按产品口径不做客户端可见界面,因此同 `open_game_creator_*_window` 一样按 native-only 登记。
|
||||||
|
'read_local_project_snapshot_state',
|
||||||
|
'sync_local_project_snapshot',
|
||||||
'reset_design_agent_session',
|
'reset_design_agent_session',
|
||||||
'stop_local_game_preview_if_matches',
|
'stop_local_game_preview_if_matches',
|
||||||
'start_game_creator_external_mcp',
|
'start_game_creator_external_mcp',
|
||||||
|
|||||||
@@ -724,6 +724,7 @@ function canvasAssetCall(agentId) {
|
|||||||
assetKind: 'art-spritesheet',
|
assetKind: 'art-spritesheet',
|
||||||
assetLabel: '游戏首版核心美术素材',
|
assetLabel: '游戏首版核心美术素材',
|
||||||
replaceExisting: false,
|
replaceExisting: false,
|
||||||
|
sliceMode: 'connected-components',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2691,7 +2692,7 @@ function createDeterministicCanvasFixture(apiKey) {
|
|||||||
'deterministic spritesheet fixture',
|
'deterministic spritesheet fixture',
|
||||||
model: 'deterministic-canvas-v1',
|
model: 'deterministic-canvas-v1',
|
||||||
provider: 'deterministic-loopback',
|
provider: 'deterministic-loopback',
|
||||||
sliceLayout: 'grid-2x2',
|
sliceMode: 'connected-components',
|
||||||
spritesheetResource: {
|
spritesheetResource: {
|
||||||
resourceId,
|
resourceId,
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* `agc` 开发启动下发给 Vite 的客户端特性开关默认值。
|
||||||
|
*
|
||||||
|
* 开发态默认关闭客户端更新检查:`npm run agc` / `agc:serve` 启动的客户端不请求 OSS
|
||||||
|
* 更新清单,也不显示更新入口。需要联调更新流程时显式传
|
||||||
|
* `VITE_AGC_ENABLE_APP_UPDATE_CHECK=1`;此处不覆盖已经显式配置的取值。
|
||||||
|
*/
|
||||||
|
const agcAppUpdateCheckEnvKey = 'VITE_AGC_ENABLE_APP_UPDATE_CHECK';
|
||||||
|
|
||||||
|
function withAgcDevFeatureFlags(env = process.env) {
|
||||||
|
if (String(env[agcAppUpdateCheckEnvKey] ?? '').trim()) {
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...env,
|
||||||
|
[agcAppUpdateCheckEnvKey]: '0',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { agcAppUpdateCheckEnvKey, withAgcDevFeatureFlags };
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* 发布上传的 OSS 命令行整理:把 ossutil 参数与凭据整理成可执行或可打印的形式,
|
||||||
|
* 便于在 dry-run 下核对将要执行的上传,同时保证任何输出都不回显凭据明文。
|
||||||
|
*/
|
||||||
|
const redactedCredential = '<redacted>';
|
||||||
|
|
||||||
|
export function readReleaseDryRun(env = process.env) {
|
||||||
|
const value = env.AGC_RELEASE_DRY_RUN?.trim().toLowerCase();
|
||||||
|
return value === '1' || value === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
function quoteArgument(value) {
|
||||||
|
return /[\s"']/u.test(value) ? JSON.stringify(value) : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatOssutilCommand({ binary, args, endpoint, credentials }) {
|
||||||
|
const parts = [binary, ...args, '--endpoint', endpoint];
|
||||||
|
if (credentials) {
|
||||||
|
parts.push(
|
||||||
|
'--access-key-id',
|
||||||
|
redactedCredential,
|
||||||
|
'--access-key-secret',
|
||||||
|
redactedCredential,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return parts.map(quoteArgument).join(' ');
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { test } from 'node:test';
|
||||||
|
|
||||||
|
import { formatOssutilCommand, readReleaseDryRun } from './release-oss.mjs';
|
||||||
|
|
||||||
|
test('dry run only accepts explicit truthy values', () => {
|
||||||
|
assert.equal(readReleaseDryRun({}), false);
|
||||||
|
assert.equal(readReleaseDryRun({ AGC_RELEASE_DRY_RUN: '1' }), true);
|
||||||
|
assert.equal(readReleaseDryRun({ AGC_RELEASE_DRY_RUN: ' true ' }), true);
|
||||||
|
assert.equal(readReleaseDryRun({ AGC_RELEASE_DRY_RUN: '0' }), false);
|
||||||
|
assert.equal(readReleaseDryRun({ AGC_RELEASE_DRY_RUN: '' }), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('printed upload command keeps arguments and hides credentials', () => {
|
||||||
|
const command = formatOssutilCommand({
|
||||||
|
binary: 'ossutil',
|
||||||
|
args: [
|
||||||
|
'cp',
|
||||||
|
'--force',
|
||||||
|
'陶泥儿 0.1.48.exe',
|
||||||
|
'oss://agc-dev/agc/dev-win/x.exe',
|
||||||
|
],
|
||||||
|
endpoint: 'oss-rg-china-mainland.aliyuncs.com',
|
||||||
|
credentials: true,
|
||||||
|
});
|
||||||
|
assert.match(command, /^ossutil cp --force /u);
|
||||||
|
assert.match(command, /"陶泥儿 0\.1\.48\.exe"/u);
|
||||||
|
assert.match(command, /oss:\/\/agc-dev\/agc\/dev-win\/x\.exe/u);
|
||||||
|
assert.match(
|
||||||
|
command,
|
||||||
|
/--access-key-id <redacted> --access-key-secret <redacted>/u,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uploader gates every ossutil call behind the dry run switch', () => {
|
||||||
|
const source = readFileSync(
|
||||||
|
new URL('./release-upload.mjs', import.meta.url),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
assert.match(source, /const dryRun = readReleaseDryRun\(\);/u);
|
||||||
|
assert.match(source, /if \(dryRun\) \{/u);
|
||||||
|
assert.match(source, /dry-run:未写入任何 OSS 对象/u);
|
||||||
|
});
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { spawnSync } from 'node:child_process';
|
import { spawnSync } from 'node:child_process';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import { formatOssutilCommand, readReleaseDryRun } from './release-oss.mjs';
|
||||||
|
|
||||||
const bucket = process.env.AGC_OSS_BUCKET?.trim() || 'agc-dev';
|
const bucket = process.env.AGC_OSS_BUCKET?.trim() || 'agc-dev';
|
||||||
const endpoint =
|
const endpoint =
|
||||||
process.env.AGC_OSS_ENDPOINT?.trim() || 'oss-rg-china-mainland.aliyuncs.com';
|
process.env.AGC_OSS_ENDPOINT?.trim() || 'oss-rg-china-mainland.aliyuncs.com';
|
||||||
@@ -8,6 +10,7 @@ if (!/^[a-z0-9][a-z0-9.-]{1,62}$/u.test(bucket) || /[\r\n\0]/u.test(endpoint)) {
|
|||||||
throw new Error('OSS bucket 或 endpoint 配置无效');
|
throw new Error('OSS bucket 或 endpoint 配置无效');
|
||||||
}
|
}
|
||||||
process.env.AGC_UPDATE_OSS_BASE_URL ||= `https://${bucket}.${endpoint}/agc`;
|
process.env.AGC_UPDATE_OSS_BASE_URL ||= `https://${bucket}.${endpoint}/agc`;
|
||||||
|
const dryRun = readReleaseDryRun();
|
||||||
|
|
||||||
const { generateUpdateManifest, prepareReleaseVersion, runTauriBuild } =
|
const { generateUpdateManifest, prepareReleaseVersion, runTauriBuild } =
|
||||||
await import('./build-release.mjs');
|
await import('./build-release.mjs');
|
||||||
@@ -19,6 +22,18 @@ function runOssutil(args) {
|
|||||||
if (Boolean(accessKeyId) !== Boolean(accessKeySecret)) {
|
if (Boolean(accessKeyId) !== Boolean(accessKeySecret)) {
|
||||||
throw new Error('OSS AccessKey ID 和 Secret 必须同时提供');
|
throw new Error('OSS AccessKey ID 和 Secret 必须同时提供');
|
||||||
}
|
}
|
||||||
|
if (dryRun) {
|
||||||
|
// 演练:只打印将要执行的上传,凭据以占位符呈现,不写入 OSS。
|
||||||
|
console.log(
|
||||||
|
`[dry-run] ${formatOssutilCommand({
|
||||||
|
binary,
|
||||||
|
args,
|
||||||
|
endpoint,
|
||||||
|
credentials: Boolean(accessKeyId),
|
||||||
|
})}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const credentialArgs = accessKeyId
|
const credentialArgs = accessKeyId
|
||||||
? ['--access-key-id', accessKeyId, '--access-key-secret', accessKeySecret]
|
? ['--access-key-id', accessKeyId, '--access-key-secret', accessKeySecret]
|
||||||
: [];
|
: [];
|
||||||
@@ -38,11 +53,40 @@ function runOssutil(args) {
|
|||||||
|
|
||||||
await prepareReleaseVersion();
|
await prepareReleaseVersion();
|
||||||
runTauriBuild([]);
|
runTauriBuild([]);
|
||||||
const { artifact, manifestPath, manifest } = generateUpdateManifest();
|
const { artifact, channel, legacyManifestPath, manifest, manifestPath } =
|
||||||
const artifactKey = `agc/${manifest.version}/${path.basename(artifact)}`;
|
generateUpdateManifest();
|
||||||
|
const artifactKey = `agc/${channel}/${manifest.version}/${path.basename(artifact)}`;
|
||||||
// Jenkins/ossutil 默认会在目标对象已存在时交互询问并按默认值跳过;
|
// Jenkins/ossutil 默认会在目标对象已存在时交互询问并按默认值跳过;
|
||||||
// 发布清单是固定的 latest 指针,必须显式覆盖,否则流水线会误报成功但远端仍保留旧版本。
|
// 发布清单是固定的 latest 指针,必须显式覆盖,否则流水线会误报成功但远端仍保留旧版本。
|
||||||
runOssutil(['cp', '--force', artifact, `oss://${bucket}/${artifactKey}`]);
|
runOssutil(['cp', '--force', artifact, `oss://${bucket}/${artifactKey}`]);
|
||||||
runOssutil(['cp', '--force', manifestPath, `oss://${bucket}/agc/latest.json`]);
|
runOssutil([
|
||||||
|
'cp',
|
||||||
|
'--force',
|
||||||
|
`${artifact}.sig`,
|
||||||
|
`oss://${bucket}/${artifactKey}.sig`,
|
||||||
|
]);
|
||||||
|
runOssutil([
|
||||||
|
'cp',
|
||||||
|
'--force',
|
||||||
|
manifestPath,
|
||||||
|
`oss://${bucket}/agc/${channel}/latest.json`,
|
||||||
|
]);
|
||||||
console.log(`[ai-game-creator-shell] 已上传 oss://${bucket}/${artifactKey}`);
|
console.log(`[ai-game-creator-shell] 已上传 oss://${bucket}/${artifactKey}`);
|
||||||
console.log(`[ai-game-creator-shell] 已上传 oss://${bucket}/agc/latest.json`);
|
console.log(
|
||||||
|
`[ai-game-creator-shell] 已上传 oss://${bucket}/agc/${channel}/latest.json`,
|
||||||
|
);
|
||||||
|
if (legacyManifestPath) {
|
||||||
|
// 迁移桥:让仍走旧 sha256 清单的已发布客户端升级到新协议,一个版本周期后删除。
|
||||||
|
runOssutil([
|
||||||
|
'cp',
|
||||||
|
'--force',
|
||||||
|
legacyManifestPath,
|
||||||
|
`oss://${bucket}/agc/latest.json`,
|
||||||
|
]);
|
||||||
|
console.log(
|
||||||
|
`[ai-game-creator-shell] 已上传迁移指针 oss://${bucket}/agc/latest.json`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (dryRun) {
|
||||||
|
console.log('[ai-game-creator-shell] dry-run:未写入任何 OSS 对象');
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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 { withAgcDevFeatureFlags } from './dev-feature-flags.mjs';
|
||||||
import { resolveAgcDevEndpoint, withAgcDevEndpointEnv } from './dev-port.mjs';
|
import { resolveAgcDevEndpoint, withAgcDevEndpointEnv } from './dev-port.mjs';
|
||||||
|
|
||||||
const appRoot = fileURLToPath(new URL('..', import.meta.url));
|
const appRoot = fileURLToPath(new URL('..', import.meta.url));
|
||||||
@@ -104,7 +105,7 @@ const child = spawn(
|
|||||||
],
|
],
|
||||||
{
|
{
|
||||||
cwd: appRoot,
|
cwd: appRoot,
|
||||||
env: withAgcDevEndpointEnv(endpoint),
|
env: withAgcDevFeatureFlags(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,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
stopWindowsProcessTree,
|
stopWindowsProcessTree,
|
||||||
stopWindowsWorktreeProcesses,
|
stopWindowsWorktreeProcesses,
|
||||||
} from '../../../scripts/dev-windows-process.mjs';
|
} from '../../../scripts/dev-windows-process.mjs';
|
||||||
|
import { withAgcDevFeatureFlags } from './dev-feature-flags.mjs';
|
||||||
import {
|
import {
|
||||||
agcVitePortEnvKey,
|
agcVitePortEnvKey,
|
||||||
readAgcDevEndpoint,
|
readAgcDevEndpoint,
|
||||||
@@ -978,7 +979,10 @@ async function startVite(apiTarget, endpoint = readAgcDevEndpoint()) {
|
|||||||
'--port',
|
'--port',
|
||||||
String(endpoint.port),
|
String(endpoint.port),
|
||||||
],
|
],
|
||||||
{ cwd: appRoot, env: withAgcDevEndpointEnv(endpoint) },
|
{
|
||||||
|
cwd: appRoot,
|
||||||
|
env: withAgcDevFeatureFlags(withAgcDevEndpointEnv(endpoint)),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+240
-12
@@ -814,6 +814,16 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
@@ -837,7 +847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics-types",
|
"core-graphics-types",
|
||||||
"foreign-types 0.5.0",
|
"foreign-types 0.5.0",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -850,7 +860,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1405,6 +1415,16 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filetime"
|
||||||
|
version = "0.2.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -1725,7 +1745,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "genarrative-ai-game-creator-shell"
|
name = "genarrative-ai-game-creator-shell"
|
||||||
version = "0.1.45"
|
version = "0.1.47"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"agent-runtime-core",
|
"agent-runtime-core",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -1747,7 +1767,6 @@ dependencies = [
|
|||||||
"oxc_parser",
|
"oxc_parser",
|
||||||
"oxc_semantic",
|
"oxc_semantic",
|
||||||
"oxc_span",
|
"oxc_span",
|
||||||
"percent-encoding",
|
|
||||||
"platform-agent",
|
"platform-agent",
|
||||||
"platform-llm",
|
"platform-llm",
|
||||||
"portable-pty",
|
"portable-pty",
|
||||||
@@ -1766,6 +1785,7 @@ dependencies = [
|
|||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-http",
|
"tauri-plugin-http",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
|
"tauri-plugin-updater",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.8.2",
|
"toml 0.8.2",
|
||||||
@@ -1776,7 +1796,7 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
"zip",
|
"zip 2.4.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2220,9 +2240,11 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"windows-registry",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2506,6 +2528,36 @@ dependencies = [
|
|||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni"
|
||||||
|
version = "0.22.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"combine",
|
||||||
|
"jni-macros",
|
||||||
|
"jni-sys 0.4.1",
|
||||||
|
"log",
|
||||||
|
"simd_cesu8",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"walkdir",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni-macros"
|
||||||
|
version = "0.22.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustc_version",
|
||||||
|
"simd_cesu8",
|
||||||
|
"syn 2.0.118",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jni-sys"
|
name = "jni-sys"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -2819,6 +2871,12 @@ dependencies = [
|
|||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minisign-verify"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -3234,6 +3292,18 @@ dependencies = [
|
|||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-osa-kit"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
"objc2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"objc2-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-quartz-core"
|
name = "objc2-quartz-core"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -3378,6 +3448,20 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "osakit"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
|
||||||
|
dependencies = [
|
||||||
|
"objc2",
|
||||||
|
"objc2-foundation",
|
||||||
|
"objc2-osa-kit",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "outref"
|
name = "outref"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -4360,15 +4444,20 @@ dependencies = [
|
|||||||
"http-body",
|
"http-body",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"hyper-rustls",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"rustls-platform-verifier",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
@@ -4482,6 +4571,33 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-platform-verifier"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation 0.10.1",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"jni 0.22.4",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"rustls",
|
||||||
|
"rustls-native-certs",
|
||||||
|
"rustls-platform-verifier-android",
|
||||||
|
"rustls-webpki",
|
||||||
|
"security-framework",
|
||||||
|
"security-framework-sys",
|
||||||
|
"webpki-root-certs",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-platform-verifier-android"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.103.13"
|
version = "0.103.13"
|
||||||
@@ -4609,7 +4725,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
@@ -4952,6 +5068,22 @@ version = "0.3.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd_cesu8"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
|
||||||
|
dependencies = [
|
||||||
|
"rustc_version",
|
||||||
|
"simdutf8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simdutf8"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "similar"
|
name = "similar"
|
||||||
version = "2.7.0"
|
version = "2.7.0"
|
||||||
@@ -5175,6 +5307,27 @@ dependencies = [
|
|||||||
"syn 2.0.118",
|
"syn 2.0.118",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"system-configuration-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration-sys"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-deps"
|
name = "system-deps"
|
||||||
version = "6.2.2"
|
version = "6.2.2"
|
||||||
@@ -5196,7 +5349,7 @@ checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dbus",
|
"dbus",
|
||||||
@@ -5206,7 +5359,7 @@ dependencies = [
|
|||||||
"gdkwayland-sys",
|
"gdkwayland-sys",
|
||||||
"gdkx11-sys",
|
"gdkx11-sys",
|
||||||
"gtk",
|
"gtk",
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"ndk",
|
"ndk",
|
||||||
@@ -5239,6 +5392,17 @@ dependencies = [
|
|||||||
"syn 2.0.118",
|
"syn 2.0.118",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tar"
|
||||||
|
version = "0.4.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
|
||||||
|
dependencies = [
|
||||||
|
"filetime",
|
||||||
|
"libc",
|
||||||
|
"xattr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "target-lexicon"
|
name = "target-lexicon"
|
||||||
version = "0.12.16"
|
version = "0.12.16"
|
||||||
@@ -5262,7 +5426,7 @@ dependencies = [
|
|||||||
"gtk",
|
"gtk",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"http",
|
"http",
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
@@ -5477,6 +5641,39 @@ dependencies = [
|
|||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-updater"
|
||||||
|
version = "2.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b28d8cabdeb0564f03ae261963de4bc3d98321cd3d213e76a81b7d344e5df606"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"dirs",
|
||||||
|
"flate2",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"infer",
|
||||||
|
"log",
|
||||||
|
"minisign-verify",
|
||||||
|
"osakit",
|
||||||
|
"percent-encoding",
|
||||||
|
"reqwest 0.13.4",
|
||||||
|
"rustls",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tar",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
"zip 4.6.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.11.3"
|
version = "2.11.3"
|
||||||
@@ -5487,7 +5684,7 @@ dependencies = [
|
|||||||
"dpi",
|
"dpi",
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
"objc2-web-kit",
|
"objc2-web-kit",
|
||||||
@@ -5510,7 +5707,7 @@ checksum = "fe41e015bf8fc4d6477ff4926a0ef769dc64ff34c7b0038b6f7cacae892acb5c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"log",
|
"log",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
@@ -6586,6 +6783,15 @@ dependencies = [
|
|||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-root-certs"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "1.0.9"
|
version = "1.0.9"
|
||||||
@@ -7192,7 +7398,7 @@ dependencies = [
|
|||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
"javascriptcore-rs",
|
"javascriptcore-rs",
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"libc",
|
"libc",
|
||||||
"ndk",
|
"ndk",
|
||||||
"objc2",
|
"objc2",
|
||||||
@@ -7256,6 +7462,16 @@ version = "0.13.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xattr"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rustix",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
@@ -7437,6 +7653,18 @@ dependencies = [
|
|||||||
"zopfli",
|
"zopfli",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zip"
|
||||||
|
version = "4.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
"crc32fast",
|
||||||
|
"indexmap 2.14.0",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "genarrative-ai-game-creator-shell"
|
name = "genarrative-ai-game-creator-shell"
|
||||||
version = "0.1.45"
|
version = "0.1.47"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
# 模板库假数据注入(仅本地页面压测/演示用):只有显式开启该 feature 才会编译并在读取清单后
|
||||||
|
# 把条目循环补齐成假数据;计数由 AGC_TEMPLATE_LIBRARY_SYNTHETIC_COUNT 控制(默认 1000)。
|
||||||
|
template-library-fixtures = []
|
||||||
cocos-editor = ["cocos-editor-bridge/process-discovery"]
|
cocos-editor = ["cocos-editor-bridge/process-discovery"]
|
||||||
cocos-editor-execute = ["cocos-editor", "cocos-editor-bridge/windows-bootstrap"]
|
cocos-editor-execute = ["cocos-editor", "cocos-editor-bridge/windows-bootstrap"]
|
||||||
cocos-editor-injection = ["cocos-editor-execute", "cocos-editor-bridge/windows-injection"]
|
cocos-editor-injection = ["cocos-editor-execute", "cocos-editor-bridge/windows-injection"]
|
||||||
@@ -47,7 +50,6 @@ similar = "2.7"
|
|||||||
platform-llm = { path = "../../../server-rs/crates/platform-llm" }
|
platform-llm = { path = "../../../server-rs/crates/platform-llm" }
|
||||||
platform-agent = { path = "../../../server-rs/crates/platform-agent" }
|
platform-agent = { path = "../../../server-rs/crates/platform-agent" }
|
||||||
portable-pty = "0.9"
|
portable-pty = "0.9"
|
||||||
percent-encoding = "2"
|
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "native-tls", "stream"] }
|
reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "native-tls", "stream"] }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
shared-contracts = { path = "../../../server-rs/crates/shared-contracts", default-features = false }
|
shared-contracts = { path = "../../../server-rs/crates/shared-contracts", default-features = false }
|
||||||
@@ -55,6 +57,7 @@ tauri = { version = "2.11.2", features = [] }
|
|||||||
tauri-plugin-dialog = "2.7.1"
|
tauri-plugin-dialog = "2.7.1"
|
||||||
tauri-plugin-http = { version = "2.5.9", default-features = false, features = ["charset", "cookies", "http2", "rustls-tls"] }
|
tauri-plugin-http = { version = "2.5.9", default-features = false, features = ["charset", "cookies", "http2", "rustls-tls"] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
|
tauri-plugin-updater = "2.11.0"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
ttf-parser = "0.25.1"
|
ttf-parser = "0.25.1"
|
||||||
|
|||||||
@@ -14,13 +14,13 @@
|
|||||||
"allow": [
|
"allow": [
|
||||||
{ "url": "https://dev.genarrative.world/api/*" },
|
{ "url": "https://dev.genarrative.world/api/*" },
|
||||||
{ "url": "https://www.genarrative.world/api/*" },
|
{ "url": "https://www.genarrative.world/api/*" },
|
||||||
{ "url": "https://agc-dev.oss-rg-china-mainland.aliyuncs.com/*" },
|
|
||||||
{ "url": "https://*/api/*" },
|
{ "url": "https://*/api/*" },
|
||||||
{ "url": "http://localhost:*/*" },
|
{ "url": "http://localhost:*/*" },
|
||||||
{ "url": "http://127.0.0.1:*/*" }
|
{ "url": "http://127.0.0.1:*/*" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"opener:default",
|
"opener:default",
|
||||||
|
"updater:default",
|
||||||
"dialog:allow-open",
|
"dialog:allow-open",
|
||||||
"dialog:allow-save"
|
"dialog:allow-save"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
{"type":"function","function":{"name":"get_workflow_status","description":"读取当前策划工作流状态,只返回阶段列表、当前阶段、已批准阶段和待审批阶段;不推进阶段、不提交审批、不修改文件。","parameters":{"type":"object","properties":{},"additionalProperties":false}}},
|
{"type":"function","function":{"name":"get_workflow_status","description":"读取当前策划工作流状态,只返回阶段列表、当前阶段、已批准阶段和待审批阶段;不推进阶段、不提交审批、不修改文件。","parameters":{"type":"object","properties":{},"additionalProperties":false}}},
|
||||||
{"type":"function","function":{"name":"list_resources","description":"列出固定资源的逻辑目录、资源 ID、标题和简介。资源是只读的随包文档;不要猜测物理路径。","parameters":{"type":"object","properties":{},"additionalProperties":false}}},
|
{"type":"function","function":{"name":"list_resources","description":"列出固定资源的逻辑目录、资源 ID、标题和简介。资源是只读的随包文档;不要猜测物理路径。","parameters":{"type":"object","properties":{},"additionalProperties":false}}},
|
||||||
{"type":"function","function":{"name":"read_resource","description":"读取一份固定资源文档全文。每次读取一个 resource_id;资源只读。读到未实现占位文档时由你自行判断和处理。","parameters":{"type":"object","properties":{"resource_id":{"type":"string"}},"required":["resource_id"],"additionalProperties":false}}},
|
{"type":"function","function":{"name":"read_resource","description":"读取一份固定资源文档全文。每次读取一个 resource_id;资源只读。读到未实现占位文档时由你自行判断和处理。","parameters":{"type":"object","properties":{"resource_id":{"type":"string"}},"required":["resource_id"],"additionalProperties":false}}},
|
||||||
{"type":"function","function":{"name":"patch_file","description":"局部修改 UTF-8 文件。使用 old_text/new_text,或使用 edits 一次进行多个独立替换;每个 old_text 必须非空且在原文件中唯一,匹配失败、重复或范围重叠时不修改文件。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"},"old_text":{"type":"string"},"new_text":{"type":"string"},"edits":{"type":"array","items":{"type":"object","properties":{"old_text":{"type":"string"},"new_text":{"type":"string"}},"required":["old_text","new_text"],"additionalProperties":false}}},"required":["path"],"additionalProperties":false}}},
|
{"type":"function","function":{"name":"patch_file","description":"局部修改 UTF-8 文件。使用 old_text/new_text,或使用 edits 一次进行多个独立替换;每个 old_text 必须非空且在原文件中唯一。所有 edit 会一次性校验;任何失败都不修改文件,错误会列出各失败项及可唯一匹配的其余项。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"},"old_text":{"type":"string"},"new_text":{"type":"string"},"edits":{"type":"array","items":{"type":"object","properties":{"old_text":{"type":"string"},"new_text":{"type":"string"}},"required":["old_text","new_text"],"additionalProperties":false}}},"required":["path"],"additionalProperties":false}}},
|
||||||
{"type":"function","function":{"name":"delete_path","description":"谨慎使用;永久删除工作区内的文件或目录;目录会连同全部内容递归删除,不备份。先确认目标及删除范围。path 使用相对路径,不能删除工作区根目录,也不能经过链接。","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"],"additionalProperties":false}}},
|
{"type":"function","function":{"name":"delete_path","description":"谨慎使用;永久删除工作区内的文件或目录;目录会连同全部内容递归删除,不备份。先确认目标及删除范围。path 使用相对路径,不能删除工作区根目录,也不能经过链接。","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"],"additionalProperties":false}}},
|
||||||
{"type":"function","function":{"name":"list_dir","description":"列出工作目录内的文件和目录。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"],"additionalProperties":false}}},
|
{"type":"function","function":{"name":"list_dir","description":"列出工作目录内的文件和目录。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"],"additionalProperties":false}}},
|
||||||
{"type":"function","function":{"name":"read_file","description":"读取工作目录内的 UTF-8 文本文件。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"],"additionalProperties":false}}},
|
{"type":"function","function":{"name":"read_file","description":"读取工作目录内的 UTF-8 文本文件。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"],"additionalProperties":false}}},
|
||||||
|
|||||||
+2
-2
@@ -13,8 +13,8 @@ Let the client derive projections from real disk changes and trusted tool result
|
|||||||
2. Before using or deriving an existing registered asset, call `agc_list_registered_assets` and select its `localAssetId`. If the user points to an existing project file that is not listed, first call `agc_list_project_files`; only entries with `assetImportable=true` (recognized image, font, audio, video, document, or code files) may be passed to `agc_import_account_assets.localPaths`. Then re-read `agc_list_registered_assets`; never infer a source identity from a filename or fabricate a localAssetId.
|
2. Before using or deriving an existing registered asset, call `agc_list_registered_assets` and select its `localAssetId`. If the user points to an existing project file that is not listed, first call `agc_list_project_files`; only entries with `assetImportable=true` (recognized image, font, audio, video, document, or code files) may be passed to `agc_import_account_assets.localPaths`. Then re-read `agc_list_registered_assets`; never infer a source identity from a filename or fabricate a localAssetId.
|
||||||
3. Keep read scopes separate: `asset.list` is the current project manifest, `asset.library.list` is the signed-in account library, and the web project's canvas resource read model is the authoritative canvas list. The account library is not the complete canvas list.
|
3. Keep read scopes separate: `asset.list` is the current project manifest, `asset.library.list` is the signed-in account library, and the web project's canvas resource read model is the authoritative canvas list. The account library is not the complete canvas list.
|
||||||
4. Use `canvas.asset_import` for safe account/canvas asset IDs or project-relative local paths. The client rechecks ownership and validates bytes; host absolute paths require native UI file-picker authorization.
|
4. Use `canvas.asset_import` for safe account/canvas asset IDs or project-relative local paths. The client rechecks ownership and validates bytes; host absolute paths require native UI file-picker authorization.
|
||||||
5. When the user explicitly asks to create or derive video, character animation, sound effect, or background music, call `agc_create_or_derive_resource`. Use `create` only for video/audio without a source and `derive` with a registered `sourceLocalAssetId`; character animation is always derived from an image.
|
5. When the user explicitly asks to create or derive video, character animation, sound effect, or background music, call `agc_create_or_derive_resource`. Use `create` only for video/audio without a source and `derive` with a registered `sourceLocalAssetId`; character animation is always derived from an image. Keep `prompt` inside the per-kind limit that the client really enforces: background music at most 140 characters, sound effect at most 1900, video and character animation at most 4000. A longer prompt is rejected before submission, so write the short version first instead of retrying the same text.
|
||||||
6. When the user explicitly asks to remove an image background, call `agc_remove_background` with a registered image `sourceLocalAssetId` and an output name. The client requires the signed-in account, owns canvas/folder context and task identity, and returns only bounded queue state.
|
6. When the user explicitly asks to remove an image background, call `agc_remove_background` with a registered image `sourceLocalAssetId` and `assetName`. Optional `backgroundMode` is `complex` (semantic foreground segmentation; default) or `flat` (solid-colour background removal). Prefer `flat` when the background is known to be solid. Only `flat` accepts optional `screenColor`: `auto`, `#RRGGBB`, or omitted for automatic detection by the service. Do not select a colour on behalf of `auto`. The client requires the signed-in account, owns canvas/folder context and task identity, and returns only bounded queue state.
|
||||||
7. Preserve existing relative paths when a small edit is sufficient so client resource identities remain stable.
|
7. Preserve existing relative paths when a small edit is sufficient so client resource identities remain stable.
|
||||||
8. Do not edit `.agent/manifest.json`, revision counters, version records, resource IDs, canvas identities, source provenance, generation ledgers, or browser receipts by hand.
|
8. Do not edit `.agent/manifest.json`, revision counters, version records, resource IDs, canvas identities, source provenance, generation ledgers, or browser receipts by hand.
|
||||||
9. Do not create a version when no game file changed. The client compares content fingerprints and advances revision only after an actual source change.
|
9. Do not create a version when no game file changed. The client compares content fingerprints and advances revision only after an actual source change.
|
||||||
|
|||||||
+3
-1
@@ -14,4 +14,6 @@ Read scopes remain separate: `asset.list` is the current project's local manifes
|
|||||||
|
|
||||||
`agc_create_or_derive_resource` accepts only semantic intent. The client resolves `sourceLocalAssetId`, creates stable request identities, recovers matching pending operations, serializes paid submissions, writes supported media into the current canvas and same-name asset folder, validates downloaded bytes, commits the local manifest transaction, and returns redacted warnings. A tool error or timeout is not permission to generate again with a new identity.
|
`agc_create_or_derive_resource` accepts only semantic intent. The client resolves `sourceLocalAssetId`, creates stable request identities, recovers matching pending operations, serializes paid submissions, writes supported media into the current canvas and same-name asset folder, validates downloaded bytes, commits the local manifest transaction, and returns redacted warnings. A tool error or timeout is not permission to generate again with a new identity.
|
||||||
|
|
||||||
`agc_remove_background` is the semantic image post-processing path. It accepts only a registered image `sourceLocalAssetId` and output name; the client resolves the formal source resource, canvas/folder context, stable operation identity, idempotency key, and authenticated External v1 `/api/external/v1/editor/images/background-removals` call. Its result is bounded queue state; Codex must not poll internal workers, construct source URLs, or retry with a new identity after an uncertain response.
|
`prompt` limits are per kind and are enforced before any paid submission: background music accepts 1-140 characters, sound effect 1-1900, video and character animation 1-4000, and image editing (`agc_edit_image`) 1-32000. The client composes the submitted request from a fixed prefix plus your prompt, so an over-limit prompt fails locally with the exact limit; shorten the text rather than resubmitting the same value. `agc_edit_image` remains the image path; this tool never generates or edits still images.
|
||||||
|
|
||||||
|
`agc_remove_background` accepts a registered image `sourceLocalAssetId`, `assetName`, and optional `backgroundMode` and `screenColor`. `complex` uses semantic segmentation to identify the foreground; `flat` removes a solid-colour background. Prefer `flat` when the background is known to be solid; omitting the mode selects `complex`. Only `flat` accepts a colour: `auto`, `#RRGGBB`, or omitted for automatic service detection. Never infer a concrete colour for `auto`. Empty or invalid values and colour without `flat` are rejected. The client resolves the formal source resource, canvas/folder context, stable operation identity, idempotency key, and authenticated External v1 `/api/external/v1/editor/images/background-removals` call. Mode and colour are part of request identity. Its result is bounded queue state; Codex must not poll internal workers, construct source URLs, or retry with a new identity after an uncertain response.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user