summer-engine 4.3.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +3 -1
- package/.codex-plugin/plugin.json +5 -3
- package/.cursor-plugin/plugin.json +4 -2
- package/.factory-plugin/plugin.json +4 -2
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +87 -0
- package/README.md +67 -27
- package/commands/start.md +19 -15
- package/commands/summer.md +1 -0
- package/dist/cli/command-usage.d.ts +3 -1
- package/dist/cli/command-usage.js +10 -4
- package/dist/cli/commands/create.d.ts +1 -25
- package/dist/cli/commands/create.js +10 -138
- package/dist/cli/commands/dev.d.ts +66 -0
- package/dist/cli/commands/dev.js +234 -0
- package/dist/cli/commands/install.d.ts +15 -58
- package/dist/cli/commands/install.js +31 -481
- package/dist/cli/commands/login.js +1 -1
- package/dist/cli/commands/ops.d.ts +17 -1
- package/dist/cli/commands/ops.js +97 -9
- package/dist/cli/commands/preview.d.ts +58 -0
- package/dist/cli/commands/preview.js +181 -0
- package/dist/cli/commands/run.d.ts +1 -17
- package/dist/cli/commands/run.js +17 -206
- package/dist/cli/index.js +5 -0
- package/dist/core/agent-host.d.ts +3 -0
- package/dist/core/agent-host.js +1 -1
- package/dist/core/api-client.d.ts +10 -2
- package/dist/core/api-client.js +13 -8
- package/dist/core/capabilities/agent-playbook.js +1 -0
- package/dist/core/capabilities/apps.d.ts +9 -9
- package/dist/core/capabilities/asset-import.d.ts +4 -1
- package/dist/core/capabilities/asset-import.js +15 -6
- package/dist/core/capabilities/capture-gameplay.js +1 -1
- package/dist/core/capabilities/connect-signal.d.ts +2 -2
- package/dist/core/capabilities/content-make/index.d.ts +81 -0
- package/dist/core/capabilities/content-make/index.js +706 -0
- package/dist/core/capabilities/content-make/media.d.ts +57 -0
- package/dist/core/capabilities/content-make/media.js +172 -0
- package/dist/core/capabilities/content-make/plan.d.ts +130 -0
- package/dist/core/capabilities/content-make/plan.js +433 -0
- package/dist/core/capabilities/content-make/render.d.ts +64 -0
- package/dist/core/capabilities/content-make/render.js +370 -0
- package/dist/core/capabilities/content-make/study.d.ts +13 -0
- package/dist/core/capabilities/content-make/study.js +165 -0
- package/dist/core/capabilities/content-make/types.d.ts +173 -0
- package/dist/core/capabilities/content-make/types.js +6 -0
- package/dist/core/capabilities/content-make/voice.d.ts +7 -0
- package/dist/core/capabilities/content-make/voice.js +27 -0
- package/dist/core/capabilities/events.d.ts +2 -2
- package/dist/core/capabilities/export-game.d.ts +4 -1
- package/dist/core/capabilities/export-game.js +4 -3
- package/dist/core/capabilities/export-presets.d.ts +2 -1
- package/dist/core/capabilities/export-presets.js +2 -1
- package/dist/core/capabilities/go-live.d.ts +14 -0
- package/dist/core/capabilities/go-live.js +408 -0
- package/dist/core/capabilities/image-generation.d.ts +6 -6
- package/dist/core/capabilities/item-manifest.d.ts +92 -0
- package/dist/core/capabilities/item-manifest.js +247 -0
- package/dist/core/capabilities/placement.d.ts +7 -7
- package/dist/core/capabilities/projects-local.d.ts +68 -0
- package/dist/core/capabilities/projects-local.js +172 -0
- package/dist/core/capabilities/publish-build.d.ts +13 -2
- package/dist/core/capabilities/publish-build.js +87 -21
- package/dist/core/capabilities/publish-download.js +1 -1
- package/dist/core/capabilities/runtime-control.d.ts +7 -2
- package/dist/core/capabilities/runtime-control.js +27 -1
- package/dist/core/capabilities/seeing/args.d.ts +2 -2
- package/dist/core/capabilities/store-checks.d.ts +45 -0
- package/dist/core/capabilities/store-checks.js +85 -0
- package/dist/core/capabilities/store-details.d.ts +37 -0
- package/dist/core/capabilities/store-details.js +94 -0
- package/dist/core/capabilities/summer-bundle.d.ts +21 -2
- package/dist/core/capabilities/summer-bundle.js +54 -7
- package/dist/core/capabilities/sync-items.d.ts +89 -0
- package/dist/core/capabilities/sync-items.js +352 -0
- package/dist/core/capabilities/tool-dispatch.js +66 -6
- package/dist/core/capabilities/video-ogv.d.ts +49 -0
- package/dist/core/capabilities/video-ogv.js +260 -0
- package/dist/core/capability-skew.js +2 -2
- package/dist/core/cli-usage-beacon.js +26 -2
- package/dist/core/cli-usage.d.ts +5 -2
- package/dist/core/cli-usage.js +8 -2
- package/dist/core/engine-install.d.ts +53 -0
- package/dist/core/engine-install.js +93 -0
- package/dist/core/engine-installer.d.ts +134 -0
- package/dist/core/engine-installer.js +760 -0
- package/dist/core/engine-launch.d.ts +126 -0
- package/dist/core/engine-launch.js +398 -0
- package/dist/core/engine-self-start.d.ts +63 -0
- package/dist/core/engine-self-start.js +282 -0
- package/dist/core/engine.d.ts +7 -1
- package/dist/core/engine.js +9 -2
- package/dist/core/error-codes.d.ts +4 -1
- package/dist/core/error-codes.js +7 -3
- package/dist/core/library-read.d.ts +2 -2
- package/dist/core/library-search.d.ts +2 -2
- package/dist/core/local-project.d.ts +50 -0
- package/dist/core/local-project.js +234 -0
- package/dist/core/ops/areas.js +9 -3
- package/dist/core/ops/catalog.d.ts +9 -0
- package/dist/core/ops/catalog.js +56 -4
- package/dist/core/ops/engine-answer.d.ts +4 -1
- package/dist/core/ops/engine-answer.js +16 -3
- package/dist/core/ops/engine.js +4 -4
- package/dist/core/ops/save-files.d.ts +6 -1
- package/dist/core/ops/save-files.js +67 -50
- package/dist/core/ops/search.js +9 -2
- package/dist/core/preview/input.d.ts +35 -0
- package/dist/core/preview/input.js +105 -0
- package/dist/core/preview/launch-config.d.ts +52 -0
- package/dist/core/preview/launch-config.js +137 -0
- package/dist/core/preview/page.d.ts +22 -0
- package/dist/core/preview/page.js +232 -0
- package/dist/core/preview/preview-source.d.ts +179 -0
- package/dist/core/preview/preview-source.js +350 -0
- package/dist/core/preview/server.d.ts +26 -0
- package/dist/core/preview/server.js +218 -0
- package/dist/core/telemetry.d.ts +20 -16
- package/dist/core/telemetry.js +92 -16
- package/dist/core/templates.d.ts +20 -0
- package/dist/core/templates.js +300 -3
- package/dist/core/tool-profiles.d.ts +2 -2
- package/dist/core/tool-profiles.js +1 -0
- package/dist/core/usage-fields.d.ts +41 -0
- package/dist/core/usage-fields.js +150 -0
- package/dist/core/util/sleep.d.ts +3 -0
- package/dist/core/util/sleep.js +12 -0
- package/dist/mcp/ops-surface.js +28 -2
- package/dist/mcp/server.d.ts +14 -4
- package/dist/mcp/server.js +42 -8
- package/dist/mcp/tool-surface.d.ts +8 -0
- package/dist/mcp/tool-surface.js +19 -2
- package/dist/mcp/tools/creator-tools.js +92 -14
- package/dist/mcp/tools/debug-tools.js +2 -0
- package/dist/mcp/tools/local-project-tools.d.ts +6 -0
- package/dist/mcp/tools/local-project-tools.js +33 -0
- package/dist/mcp/tools/navigation-tools.js +2 -1
- package/dist/mcp/tools/project-tools.js +5 -8
- package/dist/mcp/tools/seeing-tools.js +1 -1
- package/dist/mcp/tools/visual-tools.js +1 -1
- package/dist/mcp/tools/with-engine.js +1 -1
- package/gemini-extension.json +3 -1
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +18 -5
- package/library/skills/README.md +12 -8
- package/library/skills/animated-loop/SKILL.md +10 -16
- package/library/skills/cinematic-cutscene/SKILL.md +7 -18
- package/library/skills/headless-scripting/SKILL.md +7 -4
- package/library/skills/in-game-purchases/SKILL.md +25 -8
- package/library/skills/in-game-purchases/resource.yaml +2 -1
- package/library/skills/launch-ready/SKILL.md +351 -0
- package/library/skills/launch-ready/resource.yaml +45 -0
- package/library/skills/make-content/SKILL.md +202 -0
- package/library/skills/make-content/resource.yaml +32 -0
- package/library/skills/make-game/SKILL.md +2 -0
- package/library/skills/make-game/resource.yaml +2 -1
- package/library/skills/navigate-summer/SKILL.md +4 -2
- package/library/skills/new-project/SKILL.md +15 -22
- package/library/skills/new-project/resource.yaml +2 -1
- package/library/skills/painted-25d-world/SKILL.md +366 -0
- package/library/skills/painted-25d-world/_building-blocks/blender-route.md +33 -0
- package/library/skills/painted-25d-world/_building-blocks/blockout_builder.gd +149 -0
- package/library/skills/painted-25d-world/_building-blocks/capture_paint_view.gd +32 -0
- package/library/skills/painted-25d-world/_building-blocks/paint_projection.gdshader +23 -0
- package/library/skills/painted-25d-world/_building-blocks/paintcam_check.gd +25 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_door.gd +17 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_location.gd +56 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_player.gd +125 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_world.gd +111 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/cam.py +57 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/detail4k.py +81 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/overlay.py +17 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/preview.py +60 -0
- package/library/skills/painted-25d-world/resource.yaml +39 -0
- package/library/skills/publish-your-game/SKILL.md +13 -14
- package/library/skills/publish-your-game/resource.yaml +4 -3
- package/library/skills/release-checklist/SKILL.md +188 -0
- package/library/skills/release-checklist/resource.yaml +40 -0
- package/library/skills/release-readiness/SKILL.md +1 -1
- package/library/skills/release-readiness/resource.yaml +1 -1
- package/library/skills/store-art/SKILL.md +1 -1
- package/library/skills/store-art/resource.yaml +1 -1
- package/library/skills/store-listing/SKILL.md +1 -1
- package/library/skills/store-listing/resource.yaml +1 -1
- package/library/skills/summer-cli/SKILL.md +2 -0
- package/library/skills/summer-store/SKILL.md +295 -39
- package/library/skills/summer-store/resource.yaml +9 -3
- package/library/skills/trailer-shot/SKILL.md +3 -3
- package/library/templates/README.md +5 -0
- package/library/tools/content-make/resource.yaml +132 -0
- package/library/tools/frame-shot/GUIDE.md +1 -1
- package/library/tools/go-live/resource.yaml +67 -0
- package/library/tools/import-from-url/GUIDE.md +1 -0
- package/library/tools/import-from-url-batch/GUIDE.md +1 -1
- package/library/tools/play/GUIDE.md +2 -0
- package/library/tools/projects-create-local/resource.yaml +55 -0
- package/library/tools/projects-open-local/resource.yaml +51 -0
- package/library/tools/publish-build/resource.yaml +3 -3
- package/library/tools/screenshot/GUIDE.md +1 -1
- package/library/tools/store-set-details/resource.yaml +114 -0
- package/library/tools/sync-items/resource.yaml +72 -0
- package/package.json +1 -1
- package/registry/generated/aliases.json +6 -0
- package/registry/generated/counts.json +3 -3
- package/registry/generated/gemini-extension.json +3 -1
- package/registry/generated/index.json +463 -37
- package/registry/generated/marketplace.claude.json +1 -1
- package/registry/generated/plugin.claude-directory.json +4 -2
- package/registry/generated/plugin.claude.json +3 -1
- package/registry/generated/plugin.codex.json +5 -3
- package/registry/generated/plugin.cursor.json +4 -2
- package/registry/generated/plugin.factory.json +4 -2
- package/registry/generated/server.json +2 -2
- package/registry/generated/skills-index.md +12 -8
- package/registry/generated/skills-registry.json +62 -5
- package/registry/operations/legacy-names.json +40 -0
- package/registry/operations/public-catalog.json +10144 -387
|
@@ -9,6 +9,7 @@ import { appendStoreJsonLine } from "../store.js";
|
|
|
9
9
|
import { readJsonResponse } from "../util/http.js";
|
|
10
10
|
import { readLastExport } from "./export-game.js";
|
|
11
11
|
import { publishDownload } from "./publish-download.js";
|
|
12
|
+
import { CHECK_FIXES, summarizeChecks } from "./store-checks.js";
|
|
12
13
|
import { BuildToolError, declarationMismatch, readSummerBundle } from "./summer-bundle.js";
|
|
13
14
|
/**
|
|
14
15
|
* summer_publish_build: upload a summer.games export to the creator's game
|
|
@@ -51,7 +52,7 @@ const INTAKE_FAILURES = {
|
|
|
51
52
|
template_set_unavailable: "Summer Games cannot run this Summer version yet. If you exported with the newest Summer Engine, Summer Games may still be turning that version on: retry the same upload later, there is nothing to update. With an older engine, update Summer Engine and export again.",
|
|
52
53
|
client_version_conflict: "This version number already belongs to a different export. Use a new clientVersion.",
|
|
53
54
|
artifact_exists: "This exact export is already uploaded for this game. Export a new version and upload that.",
|
|
54
|
-
approval_denied: "Summer Games
|
|
55
|
+
approval_denied: "Summer Games declined this game, so it cannot take new builds.",
|
|
55
56
|
capacity_limit_reached: "Summer Games is busy right now. Try again in a few minutes.",
|
|
56
57
|
};
|
|
57
58
|
export const defaultDependencies = {
|
|
@@ -60,14 +61,14 @@ export const defaultDependencies = {
|
|
|
60
61
|
now: Date.now,
|
|
61
62
|
token: () => getStoreAccessToken(),
|
|
62
63
|
};
|
|
63
|
-
export function storeError(status, code, message, requestId) {
|
|
64
|
+
export function storeError(status, code, message, requestId, detail) {
|
|
64
65
|
if (status === 401) {
|
|
65
66
|
// The token was sent and refused: signing in again gives the same token
|
|
66
67
|
// shape and the same answer, so it is not the first thing to try.
|
|
67
68
|
return new BuildToolError("store_auth_refused", `The Summer store did not accept this sign-in${message ? `: ${message.replace(/\.$/, "")}` : ""}.`, 'Recovery: run "summer doctor" (Store line) to see the store\'s answer. Signing in again does not change a refusal of a valid sign-in. Until the store accepts agent sign-in for this account, upload the .zip in Studio (Store > your game > Exports).', status, requestId ? { requestId } : undefined);
|
|
68
69
|
}
|
|
69
70
|
if (status === 403 && code === "owner_approval_required") {
|
|
70
|
-
return new BuildToolError("owner_approval_required", message || "Only the game's owner can do this.", "Recovery: nothing went live.
|
|
71
|
+
return new BuildToolError("owner_approval_required", message || "Only the game's owner can do this.", "Recovery: nothing went live. The person's Agent permissions do not let this agent publish: ask them to turn on Publish (Studio > Agent permissions), or send them the approval link store.publish returns. A live game's new web build or download is finished by the owner in Studio.", status);
|
|
71
72
|
}
|
|
72
73
|
if (status === 403) {
|
|
73
74
|
return new BuildToolError("store_forbidden", message || "This account cannot change that game.", "Recovery: check that the game belongs to the signed-in account.", status);
|
|
@@ -78,7 +79,7 @@ export function storeError(status, code, message, requestId) {
|
|
|
78
79
|
if (status === 503 || code === "store_unavailable") {
|
|
79
80
|
return new BuildToolError("store_unavailable", message || "Summer Games is not reachable right now.", "Recovery: try again in a few minutes.", status);
|
|
80
81
|
}
|
|
81
|
-
return new BuildToolError(code, INTAKE_FAILURES[code] ?? message ?? `The Summer store refused the request (${status}).`, "Recovery: read the message, fix the export or version, and retry.", status);
|
|
82
|
+
return new BuildToolError(code, INTAKE_FAILURES[code] ?? message ?? `The Summer store refused the request (${status}).`, CHECK_FIXES[code] ? `Recovery: ${CHECK_FIXES[code].fix}` : "Recovery: read the message, fix the export or version, and retry.", status, { ...(requestId ? { requestId } : {}), ...detail });
|
|
82
83
|
}
|
|
83
84
|
export function createStore(baseUrl, token, deps) {
|
|
84
85
|
return {
|
|
@@ -93,6 +94,7 @@ export function createStore(baseUrl, token, deps) {
|
|
|
93
94
|
authorization: `Bearer ${token}`,
|
|
94
95
|
...(options.body === undefined ? {} : { "content-type": "application/json" }),
|
|
95
96
|
...(options.key ? { "idempotency-key": options.key } : {}),
|
|
97
|
+
...options.headers,
|
|
96
98
|
},
|
|
97
99
|
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
98
100
|
redirect: "error",
|
|
@@ -107,7 +109,7 @@ export function createStore(baseUrl, token, deps) {
|
|
|
107
109
|
if (!response.ok) {
|
|
108
110
|
const error = body.error && typeof body.error === "object" ? body.error : {};
|
|
109
111
|
const requestId = [error.requestId, body.requestId, response.headers.get("x-request-id")].find((value) => typeof value === "string" && value);
|
|
110
|
-
throw storeError(response.status, typeof error.code === "string" ? error.code : `http_${response.status}`, typeof error.message === "string" ? error.message : undefined, requestId);
|
|
112
|
+
throw storeError(response.status, typeof error.code === "string" ? error.code : `http_${response.status}`, typeof error.message === "string" ? error.message : undefined, requestId, Array.isArray(error.missing) ? { missing: error.missing } : undefined);
|
|
111
113
|
}
|
|
112
114
|
return body;
|
|
113
115
|
},
|
|
@@ -312,18 +314,18 @@ async function listGames(deps) {
|
|
|
312
314
|
}
|
|
313
315
|
}
|
|
314
316
|
/** gameId is required: the error carries the person's games, or why the store did not list them. */
|
|
315
|
-
async function listGamesOrThrow(deps) {
|
|
317
|
+
export async function listGamesOrThrow(deps) {
|
|
316
318
|
const listed = await listGames(deps);
|
|
317
319
|
if ("games" in listed) {
|
|
318
320
|
throw new BuildToolError("game_required", listed.games.length
|
|
319
321
|
? "gameId is required: pick one of your store games below (the export is uploaded to one existing game)."
|
|
320
322
|
: "gameId is required, and this account has no store game yet.", listed.games.length
|
|
321
323
|
? "Recovery: call again with gameId from the list."
|
|
322
|
-
: "Recovery: create the store game first (
|
|
324
|
+
: "Recovery: create the store game first (games.create), then call again with its gameId.", undefined, { games: listed.games });
|
|
323
325
|
}
|
|
324
326
|
throw new BuildToolError("game_required", `gameId is required, and the store did not list your games: ${listed.error}`, "Recovery: copy the game id from Studio (the store page URL) and retry.", undefined, { listError: listed.error });
|
|
325
327
|
}
|
|
326
|
-
async function storeToken(deps) {
|
|
328
|
+
export async function storeToken(deps) {
|
|
327
329
|
try {
|
|
328
330
|
return await deps.token();
|
|
329
331
|
}
|
|
@@ -350,7 +352,7 @@ export async function checkStoreGame(gameId, exportTargets, deps) {
|
|
|
350
352
|
? `There is no store game "${gameId}" for this account, so nothing can be uploaded to it.`
|
|
351
353
|
: `The store did not find game "${gameId}", and did not list your games either: ${listed.error}`, "games" in listed && listed.games.length
|
|
352
354
|
? "Recovery: call again with gameId from the list."
|
|
353
|
-
: "Recovery: create the store game first (
|
|
355
|
+
: "Recovery: create the store game first (games.create), or copy its id from Studio (the store page URL).", 404, "games" in listed ? { games: listed.games } : undefined);
|
|
354
356
|
}
|
|
355
357
|
throw error;
|
|
356
358
|
}
|
|
@@ -359,10 +361,57 @@ export async function checkStoreGame(gameId, exportTargets, deps) {
|
|
|
359
361
|
const catalog = new Set(targets.map((target) => target.replace(/-(universal|x64)$/, "")));
|
|
360
362
|
const unlisted = exportTargets.filter((target) => !catalog.has(target));
|
|
361
363
|
const warnings = unlisted.length
|
|
362
|
-
? [`The store page of "${game.name ?? gameId}" does not list ${unlisted.join(", ")} (it lists ${targets.join(", ") || "no platforms"}). Players on ${unlisted.join(", ")} will not see this build until the store page lists them: add them with
|
|
364
|
+
? [`The store page of "${game.name ?? gameId}" does not list ${unlisted.join(", ")} (it lists ${targets.join(", ") || "no platforms"}). Players on ${unlisted.join(", ")} will not see this build until the store page lists them: add them with store.update_listing.`]
|
|
363
365
|
: [];
|
|
364
366
|
return { game: { gameId, name: typeof game.name === "string" ? game.name : null, status: typeof game.status === "string" ? game.status : null, targets }, warnings };
|
|
365
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Seal the upload. While Summer's malware scan of the stored bytes runs, the
|
|
370
|
+
* store answers 409 malware_scan_pending; that is a wait, not a failure.
|
|
371
|
+
*/
|
|
372
|
+
async function sealWhenScanned(store, publicationPath, key, deps, deadline) {
|
|
373
|
+
for (;;) {
|
|
374
|
+
try {
|
|
375
|
+
await store.call("POST", `${publicationPath}:source-complete`, { body: {}, key });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
if (!(error instanceof BuildToolError && error.code === "malware_scan_pending") || deps.now() >= deadline)
|
|
380
|
+
throw error;
|
|
381
|
+
await deps.sleep(POLL_MS);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Why the platform did not take a Build to players when asked: the Game's
|
|
387
|
+
* approval state as the platform reports it. Staff review is becoming a
|
|
388
|
+
* step after launch, so a pending review is reported as Summer's hold, not
|
|
389
|
+
* as something the creator waits on or fixes.
|
|
390
|
+
*/
|
|
391
|
+
export async function platformHold(store, gameId) {
|
|
392
|
+
let approval = {};
|
|
393
|
+
try {
|
|
394
|
+
approval = await store.call("GET", `games/${seg(gameId)}/approval`);
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
// The hold is still reported without the approval state.
|
|
398
|
+
}
|
|
399
|
+
const state = typeof approval.state === "string" ? approval.state : "unknown";
|
|
400
|
+
const review = approval.latestReview && typeof approval.latestReview === "object" ? approval.latestReview : null;
|
|
401
|
+
if (state === "REJECTED" || state === "REVOKED" || review?.decision === "CHANGES_REQUESTED") {
|
|
402
|
+
return {
|
|
403
|
+
by: "summer_decision",
|
|
404
|
+
approvalState: state,
|
|
405
|
+
...(review ? { decision: review.decision, decidedBy: review.decidedBy ?? null, feedback: review.feedback || null } : {}),
|
|
406
|
+
message: "Summer decided against this game going live as it is. Apply the feedback (store.status shows it in full), then publish again.",
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
by: "summer",
|
|
411
|
+
approvalState: state,
|
|
412
|
+
message: "Every automated check is done; the platform has not released this game's builds yet. That is on Summer's side and needs nothing from you. The Build is kept, and summer_go_live takes it live and reports the moment players get it.",
|
|
413
|
+
};
|
|
414
|
+
}
|
|
366
415
|
export async function publishBuild(input, overrides = {}) {
|
|
367
416
|
const deps = { ...defaultDependencies, ...overrides };
|
|
368
417
|
const gameId = input.gameId?.trim();
|
|
@@ -450,7 +499,7 @@ export async function publishBuild(input, overrides = {}) {
|
|
|
450
499
|
}
|
|
451
500
|
const partHashes = plan.partSizeBytes === PART_BYTES ? digest.parts : (await hashParts(file, plan.partSizeBytes)).parts;
|
|
452
501
|
await uploadParts(async (numbers) => readGrants(await store.call("POST", `${publicationPath}:signParts`, { body: { parts: numbers.map((n) => ({ partNumber: n, sha256: partHashes[n - 1] })) } })), file, digest.sizeBytes, plan, deps);
|
|
453
|
-
await store
|
|
502
|
+
await sealWhenScanned(store, publicationPath, key(`seal:${publicationId}`), deps, deps.now() + (input.waitSeconds ?? DEFAULT_WAIT_SECONDS) * 1000);
|
|
454
503
|
uploaded = true;
|
|
455
504
|
}
|
|
456
505
|
const deadline = deps.now() + (input.waitSeconds ?? DEFAULT_WAIT_SECONDS) * 1000;
|
|
@@ -470,7 +519,9 @@ export async function publishBuild(input, overrides = {}) {
|
|
|
470
519
|
? "Recovery: fix what the message names, export again and retry; the same clientVersion still works, because a refused upload records no version."
|
|
471
520
|
: code === "template_set_unavailable"
|
|
472
521
|
? `Recovery: ${INTAKE_FAILURES.template_set_unavailable.replace(/^Summer Games cannot run this Summer version yet\. /, "")} The same clientVersion works on the retry.`
|
|
473
|
-
:
|
|
522
|
+
: CHECK_FIXES[code]
|
|
523
|
+
? `Recovery: ${CHECK_FIXES[code].fix}`
|
|
524
|
+
: "Recovery: export again, use a new clientVersion, and retry.", undefined, { publicationId, failedCheck: { code, owner: CHECK_FIXES[code]?.owner ?? "creator" } });
|
|
474
525
|
}
|
|
475
526
|
if (deps.now() >= deadline) {
|
|
476
527
|
const result = {
|
|
@@ -503,9 +554,17 @@ export async function publishBuild(input, overrides = {}) {
|
|
|
503
554
|
});
|
|
504
555
|
clientPackageId = named.package?.id ?? null;
|
|
505
556
|
}
|
|
557
|
+
// Summer's automated checks on the client pack, one per platform: wait for
|
|
558
|
+
// them within the same budget, so the answer says whether the build passed.
|
|
559
|
+
let checks = summarizeChecks(publication);
|
|
560
|
+
while (checks.state === "checking" && deps.now() < deadline) {
|
|
561
|
+
await deps.sleep(POLL_MS);
|
|
562
|
+
publication = await store.call("GET", publicationPath);
|
|
563
|
+
checks = summarizeChecks(publication);
|
|
564
|
+
}
|
|
506
565
|
let published = false;
|
|
507
|
-
let
|
|
508
|
-
if (publish) {
|
|
566
|
+
let hold = null;
|
|
567
|
+
if (publish && checks.state !== "failed") {
|
|
509
568
|
try {
|
|
510
569
|
await store.call("POST", `${publicationPath}:publish`, { body: {}, key: key(`publish:${publicationId}`) });
|
|
511
570
|
published = true;
|
|
@@ -513,13 +572,14 @@ export async function publishBuild(input, overrides = {}) {
|
|
|
513
572
|
catch (error) {
|
|
514
573
|
if (!(error instanceof BuildToolError && error.status === 409 && error.code === NOT_ACTIONABLE))
|
|
515
574
|
throw error;
|
|
516
|
-
|
|
575
|
+
hold = await platformHold(store, gameId);
|
|
517
576
|
}
|
|
518
577
|
}
|
|
519
|
-
await appendStoreJsonLine("creator-audit.jsonl", { ...audit, outcome: "succeeded", publicationId, buildId, published });
|
|
578
|
+
await appendStoreJsonLine("creator-audit.jsonl", { ...audit, outcome: "succeeded", publicationId, buildId, published, checks: checks.state });
|
|
579
|
+
const failed = checks.checks.filter((check) => check.state === "failed");
|
|
520
580
|
return {
|
|
521
581
|
ok: true,
|
|
522
|
-
status: published ? "published" : "uploaded",
|
|
582
|
+
status: checks.state === "failed" ? "checks_failed" : published ? "published" : "uploaded",
|
|
523
583
|
gameId,
|
|
524
584
|
publicationId,
|
|
525
585
|
buildId,
|
|
@@ -528,11 +588,17 @@ export async function publishBuild(input, overrides = {}) {
|
|
|
528
588
|
clientVersion,
|
|
529
589
|
sha256: digest.sha256,
|
|
530
590
|
sizeBytes: digest.sizeBytes,
|
|
591
|
+
// passed: every platform passed Summer's automated checks; checking: call again to keep waiting.
|
|
592
|
+
checksState: checks.state,
|
|
593
|
+
checks: checks.checks,
|
|
594
|
+
...(checks.packFailure ? { packFailure: checks.packFailure } : {}),
|
|
531
595
|
published,
|
|
532
|
-
...(
|
|
533
|
-
next:
|
|
534
|
-
? "
|
|
535
|
-
:
|
|
596
|
+
...(hold ? { hold } : {}),
|
|
597
|
+
next: checks.state === "failed"
|
|
598
|
+
? `Summer's automated checks failed${failed.length ? ` on ${failed.map((check) => check.platform).join(", ")}` : ""}. Apply each fix, export again and upload with a new clientVersion.`
|
|
599
|
+
: checks.state === "checking"
|
|
600
|
+
? "Summer is still checking the client pack. Call summer_publish_build again with the same file and clientVersion to keep waiting; nothing is uploaded twice."
|
|
601
|
+
: `Every automated check passed. Take it live with summer_go_live (gameId ${gameId}, publicationId ${publicationId}).`,
|
|
536
602
|
};
|
|
537
603
|
}
|
|
538
604
|
catch (error) {
|
|
@@ -156,7 +156,7 @@ export async function publishDownload(input, deps) {
|
|
|
156
156
|
resumed,
|
|
157
157
|
...(version.playUrl ? { playUrl: version.playUrl } : {}),
|
|
158
158
|
next: outcome === "ready"
|
|
159
|
-
? "The file is checked. Players get it once the store page is live; submit the page with
|
|
159
|
+
? "The file is checked. Players get it once the store page is live; submit the page with store.publish (the owner approves)."
|
|
160
160
|
: "Summer is still checking the file. Call summer_publish_build again with the same file and clientVersion to keep waiting; nothing is uploaded twice.",
|
|
161
161
|
};
|
|
162
162
|
}
|
|
@@ -138,7 +138,7 @@ export declare const runtimeSpawnArgsSchema: z.ZodObject<{
|
|
|
138
138
|
mode: z.ZodOptional<z.ZodEnum<["queue_free", "free"]>>;
|
|
139
139
|
instance: z.ZodOptional<z.ZodString>;
|
|
140
140
|
}, "strip", z.ZodTypeAny, {
|
|
141
|
-
action: "
|
|
141
|
+
action: "free" | "spawn";
|
|
142
142
|
mode?: "free" | "queue_free" | undefined;
|
|
143
143
|
name?: string | undefined;
|
|
144
144
|
instance?: string | undefined;
|
|
@@ -147,7 +147,7 @@ export declare const runtimeSpawnArgsSchema: z.ZodObject<{
|
|
|
147
147
|
parent?: string | undefined;
|
|
148
148
|
props?: Record<string, string | number | boolean> | undefined;
|
|
149
149
|
}, {
|
|
150
|
-
action: "
|
|
150
|
+
action: "free" | "spawn";
|
|
151
151
|
mode?: "free" | "queue_free" | undefined;
|
|
152
152
|
name?: string | undefined;
|
|
153
153
|
instance?: string | undefined;
|
|
@@ -552,3 +552,8 @@ export interface PlayGameClient extends CapabilityAdvertisingClient {
|
|
|
552
552
|
* `scene` into the op and would drop agent/seed/instance.
|
|
553
553
|
*/
|
|
554
554
|
export declare function playGame(client: PlayGameClient, args: PlayGameArgs): Promise<unknown>;
|
|
555
|
+
/**
|
|
556
|
+
* How many game clients a play started, for the usage line: the Local Play
|
|
557
|
+
* echo's players + spectators, else 1. Null when the play failed.
|
|
558
|
+
*/
|
|
559
|
+
export declare function playClientCount(result: unknown): number | null;
|
|
@@ -30,6 +30,7 @@ import { z } from "zod";
|
|
|
30
30
|
import { ToolInputError } from "../tool-errors.js";
|
|
31
31
|
import { missingEngineOpResult } from "../capability-skew.js";
|
|
32
32
|
import { extractOpError, getFailureReason, withOldEngineHint } from "./engine-receipt.js";
|
|
33
|
+
import { noteToolUsage } from "../usage-fields.js";
|
|
33
34
|
// ---------------------------------------------------------------------------
|
|
34
35
|
// Op kinds
|
|
35
36
|
// ---------------------------------------------------------------------------
|
|
@@ -538,7 +539,7 @@ export const inputEventSchema = z.object({
|
|
|
538
539
|
pressed: z.boolean().optional().describe("type:'action'|'key' — press (default true) or release."),
|
|
539
540
|
strength: z.number().optional().describe("type:'action' — press strength 0..1 (default 1). type:'axis' — signed strength; negative selects action_negative."),
|
|
540
541
|
hold_ms: z.number().int().optional().describe("type:'action'|'key' — auto-release after this many ms (0 = stays pressed until a release event)."),
|
|
541
|
-
keycode: z.number().int().optional().describe("type:'key' — Godot Key enum value (e.g. 32 = Space,
|
|
542
|
+
keycode: z.number().int().optional().describe("type:'key' — Godot Key enum value (e.g. 32 = Space, 4194321 = Right arrow)."),
|
|
542
543
|
physical_keycode: z.number().int().optional().describe("type:'key' — physical Key enum value (alternative to keycode)."),
|
|
543
544
|
position: z
|
|
544
545
|
.tuple([z.number(), z.number()])
|
|
@@ -1058,6 +1059,31 @@ export function withPlayPostureEcho(result, args) {
|
|
|
1058
1059
|
* `scene` into the op and would drop agent/seed/instance.
|
|
1059
1060
|
*/
|
|
1060
1061
|
export async function playGame(client, args) {
|
|
1062
|
+
const result = await playGameOnce(client, args);
|
|
1063
|
+
const instanceCount = playClientCount(result);
|
|
1064
|
+
if (instanceCount !== null)
|
|
1065
|
+
noteToolUsage({ instanceCount });
|
|
1066
|
+
return result;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* How many game clients a play started, for the usage line: the Local Play
|
|
1070
|
+
* echo's players + spectators, else 1. Null when the play failed.
|
|
1071
|
+
*/
|
|
1072
|
+
export function playClientCount(result) {
|
|
1073
|
+
if (!result || typeof result !== "object" || extractOpError(result))
|
|
1074
|
+
return null;
|
|
1075
|
+
const envelope = result;
|
|
1076
|
+
if (envelope.ok === false)
|
|
1077
|
+
return null;
|
|
1078
|
+
const payload = envelope.results?.[0] ?? envelope;
|
|
1079
|
+
const local = payload.local_play;
|
|
1080
|
+
if (local && typeof local === "object" && local.applied === true) {
|
|
1081
|
+
const count = (value) => (typeof value === "number" && Number.isInteger(value) && value > 0 ? value : 0);
|
|
1082
|
+
return Math.max(1, count(local.players) + count(local.spectators));
|
|
1083
|
+
}
|
|
1084
|
+
return 1;
|
|
1085
|
+
}
|
|
1086
|
+
async function playGameOnce(client, args) {
|
|
1061
1087
|
if (!playNeedsOp(args))
|
|
1062
1088
|
return client.play(args.scene);
|
|
1063
1089
|
// Validate the combination first (nothing sent); then, because an engine
|
|
@@ -119,11 +119,11 @@ export declare const frameNodesArgsSchema: z.ZodObject<{
|
|
|
119
119
|
}, "strict", z.ZodTypeAny, {
|
|
120
120
|
nodes: string[];
|
|
121
121
|
fill?: number | undefined;
|
|
122
|
+
from?: string | undefined;
|
|
122
123
|
scenePath?: string | undefined;
|
|
123
124
|
fov?: number | undefined;
|
|
124
125
|
marks?: boolean | undefined;
|
|
125
126
|
max_marks?: number | undefined;
|
|
126
|
-
from?: string | undefined;
|
|
127
127
|
direction?: "top" | "front" | "iso" | "back" | "left" | "right" | undefined;
|
|
128
128
|
bookmark_name?: string | undefined;
|
|
129
129
|
max_size?: number | undefined;
|
|
@@ -131,11 +131,11 @@ export declare const frameNodesArgsSchema: z.ZodObject<{
|
|
|
131
131
|
}, {
|
|
132
132
|
nodes: string[];
|
|
133
133
|
fill?: number | undefined;
|
|
134
|
+
from?: string | undefined;
|
|
134
135
|
scenePath?: string | undefined;
|
|
135
136
|
fov?: number | undefined;
|
|
136
137
|
marks?: boolean | undefined;
|
|
137
138
|
max_marks?: number | undefined;
|
|
138
|
-
from?: string | undefined;
|
|
139
139
|
direction?: "top" | "front" | "iso" | "back" | "left" | "right" | undefined;
|
|
140
140
|
bookmark_name?: string | undefined;
|
|
141
141
|
max_size?: number | undefined;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Summer's automated checks on an uploaded game, read the way an agent can act
|
|
3
|
+
* on them: one entry per check with its state, the platform's code and a fix.
|
|
4
|
+
*
|
|
5
|
+
* The codes and their meaning come from the Summer platform's management API
|
|
6
|
+
* (api/management/openapi.yaml, build-publications and client packages) and
|
|
7
|
+
* its pipeline docs (docs/build-publications.md, docs/client-pack-publication.md).
|
|
8
|
+
* Two kinds of failure are kept apart, because only one is the creator's to fix:
|
|
9
|
+
* - creator: the export or its content (a fix is named);
|
|
10
|
+
* - summer: a problem on Summer's side (nothing to change; it retries or
|
|
11
|
+
* Summer resolves it).
|
|
12
|
+
*/
|
|
13
|
+
export type CheckOwner = "creator" | "summer";
|
|
14
|
+
export interface CheckFix {
|
|
15
|
+
owner: CheckOwner;
|
|
16
|
+
fix: string;
|
|
17
|
+
}
|
|
18
|
+
/** Why an upload or one of its checks failed, and what to do about it. */
|
|
19
|
+
export declare const CHECK_FIXES: Record<string, CheckFix>;
|
|
20
|
+
export interface PlatformCheck {
|
|
21
|
+
platform: string;
|
|
22
|
+
state: "checking" | "passed" | "failed";
|
|
23
|
+
code?: string;
|
|
24
|
+
message?: string;
|
|
25
|
+
owner?: CheckOwner;
|
|
26
|
+
fix?: string;
|
|
27
|
+
}
|
|
28
|
+
/** A failure line with its owner and fix (unknown codes are the creator's, with the platform's message). */
|
|
29
|
+
export declare function describeFailure(code: string | null | undefined, message: string | null | undefined): {
|
|
30
|
+
code: string;
|
|
31
|
+
message?: string;
|
|
32
|
+
owner: CheckOwner;
|
|
33
|
+
fix: string;
|
|
34
|
+
};
|
|
35
|
+
/** Each platform the client pack is checked on, from a build publication's clientPackage. */
|
|
36
|
+
export declare function platformChecks(clientPackage: Record<string, unknown> | null | undefined): PlatformCheck[];
|
|
37
|
+
export interface ChecksSummary {
|
|
38
|
+
/** passed: every platform passed; failed: the pack or a platform failed; checking: not done yet. */
|
|
39
|
+
state: "checking" | "passed" | "failed";
|
|
40
|
+
checks: PlatformCheck[];
|
|
41
|
+
/** The pack itself was refused (state rejected), before any platform ran. */
|
|
42
|
+
packFailure?: ReturnType<typeof describeFailure>;
|
|
43
|
+
}
|
|
44
|
+
/** Where the automated checks of one build publication stand. */
|
|
45
|
+
export declare function summarizeChecks(publication: Record<string, any>): ChecksSummary;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Summer's automated checks on an uploaded game, read the way an agent can act
|
|
3
|
+
* on them: one entry per check with its state, the platform's code and a fix.
|
|
4
|
+
*
|
|
5
|
+
* The codes and their meaning come from the Summer platform's management API
|
|
6
|
+
* (api/management/openapi.yaml, build-publications and client packages) and
|
|
7
|
+
* its pipeline docs (docs/build-publications.md, docs/client-pack-publication.md).
|
|
8
|
+
* Two kinds of failure are kept apart, because only one is the creator's to fix:
|
|
9
|
+
* - creator: the export or its content (a fix is named);
|
|
10
|
+
* - summer: a problem on Summer's side (nothing to change; it retries or
|
|
11
|
+
* Summer resolves it).
|
|
12
|
+
*/
|
|
13
|
+
/** Why an upload or one of its checks failed, and what to do about it. */
|
|
14
|
+
export const CHECK_FIXES = {
|
|
15
|
+
// Intake (build-publications errorCode).
|
|
16
|
+
bundle_invalid: { owner: "creator", fix: "Export again with summer_export_game (the summer.games preset) and upload the .zip exactly as Summer Engine wrote it." },
|
|
17
|
+
declaration_mismatch: { owner: "creator", fix: "Export again with summer_export_game alignDeclaration:true, or with targets equal to what summer.build.json declares." },
|
|
18
|
+
client_version_conflict: { owner: "creator", fix: "This version number already names different bytes: upload with a new clientVersion." },
|
|
19
|
+
artifact_exists: { owner: "creator", fix: "These exact bytes are already a build of this game: use that build (store.status lists it) or export a new version." },
|
|
20
|
+
engine_build_unreleased: {
|
|
21
|
+
owner: "creator",
|
|
22
|
+
fix: "The export came from a Summer Engine build Summer never released (a development or nightly build). Install the released Summer Engine, export again and upload.",
|
|
23
|
+
},
|
|
24
|
+
attempts_exhausted: { owner: "creator", fix: "Summer could not read the upload in five tries, usually because it is very large. Make the export smaller (remove unused assets), export again and upload with a new clientVersion." },
|
|
25
|
+
approval_denied: { owner: "creator", fix: "Summer declined this game. Read the decision and its feedback with store.status before uploading again." },
|
|
26
|
+
malware_detected: { owner: "creator", fix: "The malware scan found a threat in the upload. Check the project's files, plugins and add-ons, export again on a clean machine and upload with a new clientVersion." },
|
|
27
|
+
malware_scan_unsupported: { owner: "creator", fix: "The malware scanner could not read this file. Upload the .zip exactly as summer_export_game wrote it (no re-zipping, no password)." },
|
|
28
|
+
materializing_failed: { owner: "creator", fix: "Summer could not unpack the game's server. Read errorMessage, fix the server export, export again and upload with a new clientVersion." },
|
|
29
|
+
building_failed: { owner: "creator", fix: "The game's server image did not build. Read errorMessage, fix it, export again and upload with a new clientVersion." },
|
|
30
|
+
verifying_failed: {
|
|
31
|
+
owner: "creator",
|
|
32
|
+
fix: "The secrets scan found a key or password in the server. Remove it from the project (scripts, .env and config files, the export's include filters), revoke it where it was issued, export again and upload with a new clientVersion.",
|
|
33
|
+
},
|
|
34
|
+
// Client pack and per-platform checks (client packages).
|
|
35
|
+
package_not_verified: {
|
|
36
|
+
owner: "creator",
|
|
37
|
+
fix: "The client pack failed inspection: its main scene must open, and it must carry the export stamp Summer Engine writes. Read errorMessage, fix the project, export again and upload with a new clientVersion.",
|
|
38
|
+
},
|
|
39
|
+
client_platform_unavailable: {
|
|
40
|
+
owner: "creator",
|
|
41
|
+
fix: "Summer cannot run this kind of game on this platform yet (for example web for a game with a server). Export without that platform; ship web as a download (summer_export_game format download).",
|
|
42
|
+
},
|
|
43
|
+
qualification_revoked: { owner: "creator", fix: "Summer withdrew this platform's check result. Export a new version and upload it." },
|
|
44
|
+
// Summer's side: nothing for the creator to change.
|
|
45
|
+
template_set_unavailable: { owner: "summer", fix: "Summer Games is still turning this Summer version on. Nothing to change: retry the same upload later." },
|
|
46
|
+
capacity_limit_reached: { owner: "summer", fix: "Summer Games is busy. Retry the same upload in a few minutes." },
|
|
47
|
+
verification_unavailable: { owner: "summer", fix: "Summer could not run this platform's check right now and retries it. Nothing to change." },
|
|
48
|
+
verification_blocked: { owner: "summer", fix: "Summer held this platform's check for its team. Nothing to change in the game; it continues once Summer resolves it." },
|
|
49
|
+
};
|
|
50
|
+
/** A failure line with its owner and fix (unknown codes are the creator's, with the platform's message). */
|
|
51
|
+
export function describeFailure(code, message) {
|
|
52
|
+
const known = code ? CHECK_FIXES[code] : undefined;
|
|
53
|
+
return {
|
|
54
|
+
code: code || "check_failed",
|
|
55
|
+
...(message ? { message: message.length > 400 ? `${message.slice(0, 400)}...` : message } : {}),
|
|
56
|
+
owner: known?.owner ?? "creator",
|
|
57
|
+
fix: known?.fix ?? "Read message, fix what it names in the project, export again and upload with a new clientVersion.",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const text = (value) => (typeof value === "string" && value ? value : undefined);
|
|
61
|
+
/** Each platform the client pack is checked on, from a build publication's clientPackage. */
|
|
62
|
+
export function platformChecks(clientPackage) {
|
|
63
|
+
const verifications = Array.isArray(clientPackage?.verifications) ? clientPackage.verifications : [];
|
|
64
|
+
return verifications.map((check) => {
|
|
65
|
+
const platform = text(check.platform) ?? "unknown";
|
|
66
|
+
if (check.state === "ready" && text(check.qualificationId))
|
|
67
|
+
return { platform, state: "passed" };
|
|
68
|
+
if (check.state === "failed")
|
|
69
|
+
return { platform, state: "failed", ...describeFailure(text(check.errorCode), text(check.errorMessage)) };
|
|
70
|
+
return { platform, state: "checking" };
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** Where the automated checks of one build publication stand. */
|
|
74
|
+
export function summarizeChecks(publication) {
|
|
75
|
+
const pack = publication.clientPackage && typeof publication.clientPackage === "object" ? publication.clientPackage : null;
|
|
76
|
+
if (pack?.state === "rejected") {
|
|
77
|
+
return { state: "failed", checks: platformChecks(pack), packFailure: describeFailure("package_not_verified", text(pack.verificationError)) };
|
|
78
|
+
}
|
|
79
|
+
const checks = platformChecks(pack);
|
|
80
|
+
// Wait for every platform: a failure on one does not stop the others from passing.
|
|
81
|
+
if (!pack || pack.state !== "uploaded" || checks.length === 0 || checks.some((check) => check.state === "checking")) {
|
|
82
|
+
return { state: "checking", checks };
|
|
83
|
+
}
|
|
84
|
+
return { state: checks.some((check) => check.state === "failed") ? "failed" : "passed", checks };
|
|
85
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type PublishBuildDependencies } from "./publish-build.js";
|
|
2
|
+
/**
|
|
3
|
+
* summer_store_set_details: the store page sections the hosted
|
|
4
|
+
* store.update_listing does not edit, on the store page draft:
|
|
5
|
+
* genres, features, credits, language support, system requirements, the
|
|
6
|
+
* content questionnaire (contentSurvey) and the AI disclosure. Same route as
|
|
7
|
+
* Studio's store page editor:
|
|
8
|
+
*
|
|
9
|
+
* GET games/{g}/revisions the newest revision
|
|
10
|
+
* POST games/{g}/revisions a new draft from it, when it is not a draft
|
|
11
|
+
* PATCH games/{g}/revisions/{r} the sections, with the revision's ETag
|
|
12
|
+
*
|
|
13
|
+
* Each section given replaces that section; null clears it; an omitted one
|
|
14
|
+
* keeps its value (summer-platform api/management/openapi.yaml,
|
|
15
|
+
* PatchRevision and the store page section schemas). Saving never publishes.
|
|
16
|
+
*/
|
|
17
|
+
export declare const STORE_FEATURES: readonly ["single_player", "online_multiplayer", "local_multiplayer", "online_coop", "local_coop", "cross_platform", "controller_full", "controller_partial", "cloud_saves", "plays_in_browser", "level_editor", "achievements", "in_game_purchases"];
|
|
18
|
+
export declare const AI_USES: readonly ["assets", "code", "generatedGameplay"];
|
|
19
|
+
export declare const SURVEY_LEVELS: {
|
|
20
|
+
readonly violence: readonly ["none", "fantasy", "realistic"];
|
|
21
|
+
readonly bloodGore: readonly ["none", "mild", "intense"];
|
|
22
|
+
readonly sexualContent: readonly ["none", "suggestive", "explicit"];
|
|
23
|
+
readonly nudity: readonly ["none", "partial", "full"];
|
|
24
|
+
readonly language: readonly ["none", "mild", "strong"];
|
|
25
|
+
readonly substances: readonly ["none", "referenced", "used"];
|
|
26
|
+
readonly gambling: readonly ["none", "simulated", "realMoney"];
|
|
27
|
+
};
|
|
28
|
+
export declare const DETAIL_SECTIONS: readonly ["genres", "features", "credits", "languageSupport", "systemRequirements", "contentSurvey", "aiDisclosure"];
|
|
29
|
+
type Section = (typeof DETAIL_SECTIONS)[number];
|
|
30
|
+
export type StoreDetailsInput = {
|
|
31
|
+
gameId?: string;
|
|
32
|
+
face: "cli" | "mcp";
|
|
33
|
+
} & Partial<Record<Section, unknown>>;
|
|
34
|
+
export declare function setStoreDetails(input: StoreDetailsInput, overrides?: Partial<PublishBuildDependencies>): Promise<Record<string, unknown> & {
|
|
35
|
+
ok: true;
|
|
36
|
+
}>;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { resolveGatewayUrl } from "../config.js";
|
|
3
|
+
import { createStore, defaultDependencies, listGamesOrThrow, storeToken } from "./publish-build.js";
|
|
4
|
+
import { BuildToolError } from "./summer-bundle.js";
|
|
5
|
+
/**
|
|
6
|
+
* summer_store_set_details: the store page sections the hosted
|
|
7
|
+
* store.update_listing does not edit, on the store page draft:
|
|
8
|
+
* genres, features, credits, language support, system requirements, the
|
|
9
|
+
* content questionnaire (contentSurvey) and the AI disclosure. Same route as
|
|
10
|
+
* Studio's store page editor:
|
|
11
|
+
*
|
|
12
|
+
* GET games/{g}/revisions the newest revision
|
|
13
|
+
* POST games/{g}/revisions a new draft from it, when it is not a draft
|
|
14
|
+
* PATCH games/{g}/revisions/{r} the sections, with the revision's ETag
|
|
15
|
+
*
|
|
16
|
+
* Each section given replaces that section; null clears it; an omitted one
|
|
17
|
+
* keeps its value (summer-platform api/management/openapi.yaml,
|
|
18
|
+
* PatchRevision and the store page section schemas). Saving never publishes.
|
|
19
|
+
*/
|
|
20
|
+
export const STORE_FEATURES = [
|
|
21
|
+
"single_player",
|
|
22
|
+
"online_multiplayer",
|
|
23
|
+
"local_multiplayer",
|
|
24
|
+
"online_coop",
|
|
25
|
+
"local_coop",
|
|
26
|
+
"cross_platform",
|
|
27
|
+
"controller_full",
|
|
28
|
+
"controller_partial",
|
|
29
|
+
"cloud_saves",
|
|
30
|
+
"plays_in_browser",
|
|
31
|
+
"level_editor",
|
|
32
|
+
"achievements",
|
|
33
|
+
"in_game_purchases",
|
|
34
|
+
];
|
|
35
|
+
export const AI_USES = ["assets", "code", "generatedGameplay"];
|
|
36
|
+
export const SURVEY_LEVELS = {
|
|
37
|
+
violence: ["none", "fantasy", "realistic"],
|
|
38
|
+
bloodGore: ["none", "mild", "intense"],
|
|
39
|
+
sexualContent: ["none", "suggestive", "explicit"],
|
|
40
|
+
nudity: ["none", "partial", "full"],
|
|
41
|
+
language: ["none", "mild", "strong"],
|
|
42
|
+
substances: ["none", "referenced", "used"],
|
|
43
|
+
gambling: ["none", "simulated", "realMoney"],
|
|
44
|
+
};
|
|
45
|
+
export const DETAIL_SECTIONS = ["genres", "features", "credits", "languageSupport", "systemRequirements", "contentSurvey", "aiDisclosure"];
|
|
46
|
+
const IN_REVIEW = ["SUBMITTED", "VERIFYING", "SCHEDULED"];
|
|
47
|
+
const seg = encodeURIComponent;
|
|
48
|
+
/** The store page refusals this tool can name before anything is sent. */
|
|
49
|
+
function checkSections(input) {
|
|
50
|
+
const ai = input.aiDisclosure;
|
|
51
|
+
if (ai && Array.isArray(ai.uses) && ai.uses.includes("generatedGameplay") && !ai.description?.trim()) {
|
|
52
|
+
throw new BuildToolError("ai_disclosure_incomplete", "aiDisclosure uses generatedGameplay, which needs a description of what is generated while people play and its guardrails.", "Recovery: add aiDisclosure.description, then call again.");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function editableDraft(store, gameId, key) {
|
|
56
|
+
const revisions = await store.call("GET", `games/${seg(gameId)}/revisions?limit=20`);
|
|
57
|
+
const newest = (revisions.items ?? []).reduce((best, item) => (!best || Number(item.sequence ?? 0) > Number(best.sequence ?? 0) ? item : best), null);
|
|
58
|
+
if (!newest)
|
|
59
|
+
throw new BuildToolError("no_store_page", "This game has no store page yet.", "Recovery: create it with games.create, then call again.");
|
|
60
|
+
if (newest.state === "DRAFT")
|
|
61
|
+
return newest;
|
|
62
|
+
if (IN_REVIEW.includes(newest.state)) {
|
|
63
|
+
throw new BuildToolError("page_publishing", "The store page is being published right now, so it cannot change until that finishes.", "Recovery: read summer_go_live (or store.status) for where it stands; once it is live, call this again to edit a new draft.");
|
|
64
|
+
}
|
|
65
|
+
return store.call("POST", `games/${seg(gameId)}/revisions`, { body: {}, key });
|
|
66
|
+
}
|
|
67
|
+
export async function setStoreDetails(input, overrides = {}) {
|
|
68
|
+
const deps = { ...defaultDependencies, ...overrides };
|
|
69
|
+
const gameId = input.gameId?.trim();
|
|
70
|
+
if (!gameId)
|
|
71
|
+
return listGamesOrThrow(deps);
|
|
72
|
+
const patch = Object.fromEntries(DETAIL_SECTIONS.filter((section) => input[section] !== undefined).map((section) => [section, input[section]]));
|
|
73
|
+
if (!Object.keys(patch).length) {
|
|
74
|
+
throw new BuildToolError("nothing_to_change", "No store page section was given.", `Recovery: pass at least one of ${DETAIL_SECTIONS.join(", ")}.`);
|
|
75
|
+
}
|
|
76
|
+
checkSections(input);
|
|
77
|
+
const store = createStore(await resolveGatewayUrl(), await storeToken(deps), deps);
|
|
78
|
+
const key = (step) => createHash("sha256").update(`store-details:${gameId}:${step}:${JSON.stringify(patch)}`).digest("hex");
|
|
79
|
+
const draft = await editableDraft(store, gameId, key("fork"));
|
|
80
|
+
const saved = await store.call("PATCH", `games/${seg(gameId)}/revisions/${seg(String(draft.revisionId))}`, {
|
|
81
|
+
body: patch,
|
|
82
|
+
key: key(`patch:${draft.revisionId}:${draft.etagRevision}`),
|
|
83
|
+
headers: { "x-summer-revision": `"game-revision:${draft.revisionId}:${draft.etagRevision}"` },
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
status: "saved",
|
|
88
|
+
gameId,
|
|
89
|
+
revisionId: saved.revisionId ?? draft.revisionId,
|
|
90
|
+
state: saved.state ?? draft.state,
|
|
91
|
+
saved: Object.fromEntries(Object.keys(patch).map((section) => [section, saved[section] ?? patch[section]])),
|
|
92
|
+
next: "Saved on the store page draft; nothing is published. Read it with store.get_listing; take it live with summer_go_live.",
|
|
93
|
+
};
|
|
94
|
+
}
|