summer-engine 3.5.0 → 3.5.2
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 +2 -1
- package/.codex-plugin/plugin.json +2 -1
- package/.cursor-plugin/plugin.json +2 -1
- package/.factory-plugin/plugin.json +2 -1
- package/CHANGELOG.md +23 -0
- package/README.md +3 -3
- package/commands/summer.md +1 -0
- package/dist/cli/commands/setup.js +2 -2
- package/dist/core/capabilities/publish-build.d.ts +4 -0
- package/dist/core/capabilities/publish-build.js +67 -34
- package/dist/core/capabilities/runtime-control.d.ts +2 -2
- package/dist/installer/agent-config.d.ts +5 -5
- package/dist/installer/agent-config.js +96 -32
- package/dist/installer/agent-table.d.ts +7 -6
- package/dist/installer/agent-table.js +117 -6
- package/dist/mcp/hosted-mount.js +2 -1
- package/dist/mcp/provider-safe-schema.d.ts +10 -0
- package/dist/mcp/provider-safe-schema.js +96 -0
- package/dist/mcp/server.js +3 -0
- package/gemini-extension.json +2 -1
- package/integrations/README.md +6 -0
- package/library/skills/README.md +3 -2
- package/library/skills/export-and-ship/SKILL.md +2 -0
- package/library/skills/make-game/SKILL.md +21 -2
- package/library/skills/make-game/resource.yaml +1 -0
- package/library/skills/multiplayer/SKILL.md +3 -1
- package/library/skills/multiplayer/resource.yaml +1 -0
- package/library/skills/multiplayer-movement/SKILL.md +1 -1
- package/library/skills/multiplayer-movement/files.md +2 -3
- package/library/skills/multiplayer-project/SKILL.md +20 -4
- package/library/skills/multiplayer-project/files.md +2 -3
- package/library/skills/multiplayer-publish/SKILL.md +86 -5
- package/library/skills/multiplayer-publish/resource.yaml +1 -0
- package/library/skills/multiplayer-state/SKILL.md +1 -1
- package/library/skills/multiplayer-state/files.md +2 -3
- package/library/skills/multiplayer-testing/SKILL.md +13 -0
- package/library/skills/multiplayer-testing/resource.yaml +1 -0
- package/library/skills/publish-your-game/SKILL.md +2 -0
- package/library/skills/release-readiness/SKILL.md +298 -0
- package/library/skills/release-readiness/resource.yaml +41 -0
- package/library/skills/summer-parties/SKILL.md +1 -1
- package/package.json +1 -1
- package/registry/generated/counts.json +2 -2
- package/registry/generated/gemini-extension.json +2 -1
- package/registry/generated/index.json +63 -11
- package/registry/generated/marketplace.claude.json +1 -1
- package/registry/generated/plugin.claude.json +2 -1
- package/registry/generated/plugin.codex.json +2 -1
- package/registry/generated/plugin.cursor.json +2 -1
- package/registry/generated/plugin.factory.json +2 -1
- package/registry/generated/skills-index.md +3 -2
- package/registry/generated/skills-registry.json +14 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
{
|
|
12
12
|
"name": "summer",
|
|
13
13
|
"source": "./",
|
|
14
|
-
"version": "3.5.
|
|
14
|
+
"version": "3.5.2",
|
|
15
15
|
"description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 105-tool MCP bridge to the local desktop app.",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "Summer Engine",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"_generated": "GENERATED from integrations/claude — do not edit; npm run generate:registry",
|
|
4
4
|
"name": "summer",
|
|
5
|
-
"version": "3.5.
|
|
5
|
+
"version": "3.5.2",
|
|
6
6
|
"description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 105-tool MCP bridge to the local desktop app.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Summer Engine",
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
"./library/skills/psx-retro-rendering/",
|
|
97
97
|
"./library/skills/publish-your-game/",
|
|
98
98
|
"./library/skills/realtime-wet-surfaces/",
|
|
99
|
+
"./library/skills/release-readiness/",
|
|
99
100
|
"./library/skills/remote-deploy/",
|
|
100
101
|
"./library/skills/retarget/",
|
|
101
102
|
"./library/skills/running-in-the-cloud/",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_generated": "GENERATED from integrations/codex — do not edit; npm run generate:registry",
|
|
3
3
|
"name": "summer",
|
|
4
|
-
"version": "3.5.
|
|
4
|
+
"version": "3.5.2",
|
|
5
5
|
"description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 105-tool MCP bridge. Build games by talking.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Summer Engine",
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"./library/skills/psx-retro-rendering/",
|
|
92
92
|
"./library/skills/publish-your-game/",
|
|
93
93
|
"./library/skills/realtime-wet-surfaces/",
|
|
94
|
+
"./library/skills/release-readiness/",
|
|
94
95
|
"./library/skills/remote-deploy/",
|
|
95
96
|
"./library/skills/retarget/",
|
|
96
97
|
"./library/skills/running-in-the-cloud/",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"_generated": "GENERATED from integrations/cursor — do not edit; npm run generate:registry",
|
|
4
4
|
"name": "summer",
|
|
5
5
|
"displayName": "Summer",
|
|
6
|
-
"version": "3.5.
|
|
6
|
+
"version": "3.5.2",
|
|
7
7
|
"description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 105-tool MCP bridge to the local desktop app.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Summer Engine",
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
"./library/skills/psx-retro-rendering/",
|
|
104
104
|
"./library/skills/publish-your-game/",
|
|
105
105
|
"./library/skills/realtime-wet-surfaces/",
|
|
106
|
+
"./library/skills/release-readiness/",
|
|
106
107
|
"./library/skills/remote-deploy/",
|
|
107
108
|
"./library/skills/retarget/",
|
|
108
109
|
"./library/skills/running-in-the-cloud/",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"_generated": "GENERATED from integrations/factory — do not edit; npm run generate:registry",
|
|
3
3
|
"name": "summer",
|
|
4
4
|
"description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 105-tool MCP bridge to the local desktop app.",
|
|
5
|
-
"version": "3.5.
|
|
5
|
+
"version": "3.5.2",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Summer Engine",
|
|
8
8
|
"email": "founders@summerengine.com",
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
"./library/skills/psx-retro-rendering/",
|
|
89
89
|
"./library/skills/publish-your-game/",
|
|
90
90
|
"./library/skills/realtime-wet-surfaces/",
|
|
91
|
+
"./library/skills/release-readiness/",
|
|
91
92
|
"./library/skills/remote-deploy/",
|
|
92
93
|
"./library/skills/retarget/",
|
|
93
94
|
"./library/skills/running-in-the-cloud/",
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@ All notable changes to summer-engine will be documented here. Following [Keep a
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
Hosted games: agents check that players can actually join before a release.
|
|
8
|
+
|
|
9
|
+
- **`release-readiness` check 7: export with a released Summer, and prove the scripts compile on the runtime players get.** Players' apps run a bundle on the released Summer of the version its export stamp names. A development build that still stamps an older released version can use APIs that release lacks; its scripts then fail to compile on every player's device (`Parse Error: Static function "…" not found`, `Failed to load script`) and the game opens to an empty window, while every upload check passes. The check reads the stamp from `summer-bundle.json`, compares `engineSha` with the release's, and runs `client.pck` on that released Summer with `--headless --verbose --main-pack`, failing on `SCRIPT ERROR`, `Parse Error` or `Failed to load script`. `multiplayer-publish` adds the run as step 4e, and `export-and-ship` says to export summer.games bundles with the released editor only.
|
|
10
|
+
|
|
11
|
+
- **New `release-readiness` skill.** Before a version reaches players, it runs six checks on the summer.games `.zip` you upload: the bundle's `server.pck`, started with no scene, loads the authority scene; a real online join with the released version reaches the game; online play is reachable by default; the server saves online progress through player data; players can get back to the title; and `Summer.initialize` passes no hard-coded Game ID. It reports pass, fail, not applicable or not run for each, and grows by one check after every release that fails in a way none of them caught. The same checklist is in the docs at `/publishing/release-readiness`.
|
|
12
|
+
- **The multiplayer skills call `Summer.initialize()` with no Game ID.** The summer.games bundle leaves `gameId` out, so an ID in code (or a fallback when it is missing) fails on the hosted server with `game_identity_mismatch` and every online join ends in `world_start_timeout`.
|
|
13
|
+
- `multiplayer-publish` explains why the server preset needs the `summer_authority` custom feature, adds a pre-upload step that runs the bundle's `server.pck` with no scene, a real online join after release, and what an empty server log does and doesn't mean. `multiplayer-testing` lists what Local Play can't catch; `make-game` plans online play reachable by default, server-saved progress and a way back to the title, and runs `release-readiness` before shipping.
|
|
14
|
+
|
|
15
|
+
## [3.5.2] - 2026-10-10
|
|
16
|
+
|
|
17
|
+
Summer works with Kimi Code and other agents whose model checks tool schemas strictly.
|
|
18
|
+
|
|
19
|
+
- Kimi Code no longer fails every prompt with `400 tools.function.parameters is not a valid moonshot flavored json schema ... items must be an object` when Summer is connected. Ten tools (`summer_align_distribute_3d`, `summer_raycast`, `summer_instantiate_scene`, `summer_capture_gameplay`, `summer_test_placement`, `summer_snap_to_surface`, `summer_navigation_probe`, `summer_attach_to_surface`, `summer_repeat_along`, `summer_game_input`) described their `[x, y, z]` and `[x, y]` vectors in a form Moonshot rejects. Every tool now lists its parameters in the JSON Schema subset strict providers accept: a vector is an array of numbers with an exact length, and no schema points into another with `$ref`. The hosted store tools get the same treatment. The tools still reject a vector of the wrong length.
|
|
20
|
+
- If you added Summer tools to Kimi's `disabledTools` to work around this, remove them after updating, then restart Kimi Code.
|
|
21
|
+
|
|
22
|
+
## [3.5.1] - 2026-10-10
|
|
23
|
+
|
|
24
|
+
Summer Games now connects from almost every agent setup supports, and large store uploads finish.
|
|
25
|
+
|
|
26
|
+
- **Summer Games in more agents.** `summer setup` now adds `summer-games` (`https://mcp.summer.games/mcp`: friends, parties and messages as you) for Cursor, GitHub Copilot in VS Code, the Copilot CLI and Copilot in JetBrains, Zed, Cline and Cline CLI, Kilo Code, OpenCode, Devin Desktop (Windsurf), Antigravity, Kiro, Trae, Warp, Goose, Hermes, Crush, Amp, Factory Droid, Junie, Qwen Code, Kimi Code, Grok Build and Mistral Vibe, as well as Claude Code and Codex. Each gets the entry in its own format, and setup prints the one sign-in step for that agent. As in 3.5.0, setup adds no second entry when the same address is already listed under another name, and `--no-games` skips it.
|
|
27
|
+
- Not included, because they cannot sign in to it from their config file: Claude Desktop (add Summer Games as a custom connector in its settings), Roo Code and Rovo Dev (no sign-in for remote servers), and Visual Studio, Qoder and LM Studio (sign-in not yet checked).
|
|
28
|
+
- `summer_publish_build` and `summer_publish_download` no longer fail an upload of 3 or more parts with `store_sign_failed: The store did not sign part N` when the store signed every part. Each sign now asks for every part still to upload, a part counts as unsigned only when the store was asked for it and left it out, a signing error is retried like a failed part upload, and the first failure stops the other parts' uploads.
|
|
29
|
+
|
|
7
30
|
## [3.5.0] - 2026-10-10
|
|
8
31
|
|
|
9
32
|
Setup now connects Summer Games too, and agents that publish a game get further on their own. 3.4.2 was never published; its changes ship here.
|
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ The first run will:
|
|
|
57
57
|
4. Open your browser for one-click sign-in.
|
|
58
58
|
5. Scaffold a starter project and open the engine.
|
|
59
59
|
|
|
60
|
-
In Claude Code and
|
|
60
|
+
In most agents (Claude Code, Codex, Cursor, GitHub Copilot (VS Code, CLI, JetBrains), Zed, Cline, Kilo Code, OpenCode, Devin Desktop, Antigravity, Kiro, Trae, Warp, Goose, Hermes, Crush, Amp, Factory, Junie, Qwen Code, Kimi Code, Grok Build and Mistral Vibe), setup also connects Summer Games (`summer-games`), so the agent can see your friends, start parties and send messages as you. You sign in once; setup prints the step for your agent (Claude Code: `/mcp`, choose `summer-games`; Codex: `/mcp login summer-games`; Cursor: Connect next to `summer-games` in its MCP settings). A summer.games page opens in your browser; sign in and choose Allow.
|
|
61
61
|
|
|
62
62
|
Then ask the agent to build something.
|
|
63
63
|
|
|
@@ -125,7 +125,7 @@ npx -y summer-engine@latest setup claude-code --yes --force
|
|
|
125
125
|
|
|
126
126
|
Replace `claude-code` with the user's actual agent: `claude-desktop`, `codex`, `cursor`, `windsurf`, `antigravity`, `cline`, `cline-cli`, `kilo-code`, `github-copilot`, `vscode-copilot`, `visual-studio`, `copilot-jetbrains`, `opencode`, `zed`, `kiro`, `goose`, `hermes`, `trae`, `qwen-code`, `kimi-code`, `crush`, `amp`, `factory`, `junie`, `warp`, `rovo-dev`, `qoder`, `grok-build`, `mistral-vibe`, `lm-studio`. Use `github-copilot` for Copilot CLI, `vscode-copilot` for GitHub Copilot Chat/Agent in VS Code, `visual-studio` for Copilot in Visual Studio, and `copilot-jetbrains` for Copilot in JetBrains IDEs. Legacy targets `gemini` and `roo-code` still work; for Google's agent use `antigravity`. This installs **every skill in the library** to `~/.claude/skills/<slug>/SKILL.md` (or the agent's own skill directory), writes the MCP server config, runs doctor, and prints what it installed and where. Preview skills are included and labelled; `--stable-only` skips them. `--force` wipes stale skill copies first. Don't use `--recommended` from this playbook; it installs only a subset.
|
|
127
127
|
|
|
128
|
-
**Summer Games:** for `claude-
|
|
128
|
+
**Summer Games:** for every agent whose sign-in summer.games accepts (all targets except `claude-desktop`, `visual-studio`, `qoder`, `rovo-dev`, `lm-studio` and the legacy ones), setup also adds the remote `summer-games` MCP (`https://mcp.summer.games/mcp`) and prints a `Sign in once:` line. Tell the user that line once, in plain words. Their browser opens summer.games; they sign in and choose Allow. Do not wait for it; continue the playbook. If the user already added the same URL under another name (for example `summer`), setup keeps theirs and adds no second entry.
|
|
129
129
|
|
|
130
130
|
**First-install detection:** if `~/.claude/skills/` didn't exist before this command, Claude Code wasn't watching it and won't auto-detect the new files this session. Tell the user **once**: "Skills installed - restart your agent so they load." On subsequent installs (directory already existed), skills auto-detect mid-session and no restart is needed. **You can detect first-install vs upgrade by checking if `~/.claude/skills/` existed before Step 1; record the result before running setup.**
|
|
131
131
|
|
|
@@ -374,7 +374,7 @@ matchmaking are not promised by this package.
|
|
|
374
374
|
| `summer tool <name> --args '<json>'` | Run any Summer tool from the shell — the same implementation the MCP tool uses. `summer tool --list` prints them all. |
|
|
375
375
|
| `summer mcp [--project <path> \| --instance <id>]` | Start MCP; normally auto-binds from the agent's project directory. |
|
|
376
376
|
| `summer mcp setup <agent>` | Deprecated alias of `summer setup <agent>`. |
|
|
377
|
-
| `summer setup <agent> [--yes] [--force] [--recommended] [--stable-only] [--no-games]` | One shot: MCP config + all skills, preview included (`--recommended` for the subset, `--stable-only` to skip preview) + doctor. Idempotent. For Claude
|
|
377
|
+
| `summer setup <agent> [--yes] [--force] [--recommended] [--stable-only] [--no-games]` | One shot: MCP config + all skills, preview included (`--recommended` for the subset, `--stable-only` to skip preview) + doctor. Idempotent. For most agents (not Claude Desktop, Visual Studio, Qoder, Rovo Dev, LM Studio or the legacy targets) it also adds `summer-games` (`https://mcp.summer.games/mcp`: friends, parties and messages as you) unless the same URL is already listed; you sign in once through summer.games. `--no-games` skips it. |
|
|
378
378
|
|
|
379
379
|
Agents: `claude-code`, `claude-desktop`, `codex`, `cursor`, `windsurf`, `antigravity`, `cline`, `cline-cli`, `kilo-code`, `github-copilot`, `vscode-copilot`, `visual-studio`, `copilot-jetbrains`, `opencode`, `zed`, `kiro`, `goose`, `hermes`, `trae`, `qwen-code`, `kimi-code`, `crush`, `amp`, `factory`, `junie`, `warp`, `rovo-dev`, `qoder`, `grok-build`, `mistral-vibe`, `lm-studio`. Legacy (still accepted, warn on use): `gemini`, `roo-code`. Aliases: `devin`/`devin-desktop` → `windsurf`, `agy` → `antigravity`, `droid` → `factory`, `copilot` → `github-copilot`, `vscode` → `vscode-copilot`, `kilo` → `kilo-code`, `qwen` → `qwen-code`, `kimi` → `kimi-code`. Scopes: `--scope user` (default), `--scope project`.
|
|
380
380
|
|
package/commands/summer.md
CHANGED
|
@@ -47,6 +47,7 @@ Skills are invoked by their bare slug, such as `brainstorm-game`. There is no `s
|
|
|
47
47
|
| add an animation to [character] | `generate-motion` |
|
|
48
48
|
| wire up a state machine for [character] | `animation-tree` |
|
|
49
49
|
| the game feels mushy / add juice | `game-feel` |
|
|
50
|
+
| is it ready to release / check before publishing / online joins fail with `world_start_timeout` | `release-readiness` |
|
|
50
51
|
| ship a trailer | `trailer-shot` |
|
|
51
52
|
| cutscene for [X] | `cinematic-cutscene` |
|
|
52
53
|
| let me play it | `play` |
|
|
@@ -7,7 +7,7 @@ import { TOOLKIT_VERSION as cliVersion } from "../../core/version.js";
|
|
|
7
7
|
import { resolveDefaultChannel } from "../../installer/version-check.js";
|
|
8
8
|
import { agentLabel } from "../../installer/agent-table.js";
|
|
9
9
|
export const setupCommand = new Command("setup")
|
|
10
|
-
.description("Configure Summer Engine for an AI agent and run diagnostics. For
|
|
10
|
+
.description("Configure Summer Engine for an AI agent and run diagnostics. For agents that can sign in to it, it also adds Summer Games (summer-games).")
|
|
11
11
|
.argument("[agent]", `Agent to configure: ${supportedAgents.join(", ")}`)
|
|
12
12
|
.option("--agent <agent>", `Agent to configure: ${supportedAgents.join(", ")}`)
|
|
13
13
|
.option("--scope <scope>", "Configuration scope: user or project", "user")
|
|
@@ -16,7 +16,7 @@ export const setupCommand = new Command("setup")
|
|
|
16
16
|
.option("--local-dev", "Point the agent at this checkout's built CLI (node <repo>/dist/bin/summer.js mcp) instead of npx summer-engine@latest — for testing unpublished builds. SUMMER_DEV=1 does the same.")
|
|
17
17
|
.option("--channel <dist-tag>", "npm dist-tag the agent's MCP entry runs: npx -y summer-engine@<dist-tag> mcp (default latest; use next while a release soaks on the next tag). SUMMER_CHANNEL does the same.")
|
|
18
18
|
.option("--yes", "Apply practical setup steps without prompting")
|
|
19
|
-
.option("--no-games", "Do not add the Summer Games MCP (summer-games: friends, parties and messages as you, signed in through summer.games
|
|
19
|
+
.option("--no-games", "Do not add the Summer Games MCP (summer-games: friends, parties and messages as you, signed in through summer.games)")
|
|
20
20
|
.option("--json", "Print setup result as JSON")
|
|
21
21
|
.option("--force", "Overwrite existing skill content (passes --force through to skills install)")
|
|
22
22
|
.option("--recommended", "Install only the recommended skill subset instead of the whole library")
|
|
@@ -51,6 +51,10 @@ export declare function readGrants(body: Record<string, any>): Grant[];
|
|
|
51
51
|
* Upload the given parts: signed in batches, PUT from disk, re-signed when
|
|
52
52
|
* refused or about to expire. `sign` asks the store for URLs (build
|
|
53
53
|
* publications bind each to its part SHA-256; store versions to its length).
|
|
54
|
+
* Each sign asks for every part not yet uploaded, including the parts other
|
|
55
|
+
* workers hold, so one sign usually covers them all. A part fails to sign only
|
|
56
|
+
* when the store was asked for it and did not sign it; the first fatal error
|
|
57
|
+
* stops the other workers and aborts their uploads.
|
|
54
58
|
*/
|
|
55
59
|
export declare function uploadParts(sign: (partNumbers: number[]) => Promise<Grant[]>, file: string, sizeBytes: number, plan: {
|
|
56
60
|
partSizeBytes: number;
|
|
@@ -185,19 +185,28 @@ async function readPart(path, partNumber, partSize, sizeBytes) {
|
|
|
185
185
|
* Upload the given parts: signed in batches, PUT from disk, re-signed when
|
|
186
186
|
* refused or about to expire. `sign` asks the store for URLs (build
|
|
187
187
|
* publications bind each to its part SHA-256; store versions to its length).
|
|
188
|
+
* Each sign asks for every part not yet uploaded, including the parts other
|
|
189
|
+
* workers hold, so one sign usually covers them all. A part fails to sign only
|
|
190
|
+
* when the store was asked for it and did not sign it; the first fatal error
|
|
191
|
+
* stops the other workers and aborts their uploads.
|
|
188
192
|
*/
|
|
189
193
|
export async function uploadParts(sign, file, sizeBytes, plan, deps, parts = Array.from({ length: plan.partCount }, (_, index) => index + 1)) {
|
|
190
194
|
const queue = [...parts];
|
|
195
|
+
const unfinished = new Set(parts);
|
|
191
196
|
const grants = new Map();
|
|
197
|
+
const stop = new AbortController();
|
|
198
|
+
let failure;
|
|
192
199
|
let signing = null;
|
|
193
200
|
const fresh = (grant) => grant !== undefined && grant.expiresAt - deps.now() > 60_000;
|
|
194
201
|
const grantFor = async (partNumber) => {
|
|
195
|
-
for (
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
for (;;) {
|
|
203
|
+
const grant = grants.get(partNumber);
|
|
204
|
+
if (fresh(grant))
|
|
205
|
+
return grant;
|
|
198
206
|
if (!signing) {
|
|
199
|
-
const
|
|
200
|
-
|
|
207
|
+
const others = [...unfinished].filter((n) => n !== partNumber && !fresh(grants.get(n)));
|
|
208
|
+
const batch = [partNumber, ...others].slice(0, MAX_SIGNED_PARTS);
|
|
209
|
+
const done = sign(batch)
|
|
201
210
|
.then((signed) => {
|
|
202
211
|
for (const grant of signed)
|
|
203
212
|
grants.set(grant.partNumber, grant);
|
|
@@ -205,43 +214,67 @@ export async function uploadParts(sign, file, sizeBytes, plan, deps, parts = Arr
|
|
|
205
214
|
.finally(() => {
|
|
206
215
|
signing = null;
|
|
207
216
|
});
|
|
217
|
+
signing = { batch, done };
|
|
218
|
+
}
|
|
219
|
+
const { batch, done } = signing;
|
|
220
|
+
await done;
|
|
221
|
+
// The store was asked for this part and did not sign it (or signed it already expiring).
|
|
222
|
+
if (batch.includes(partNumber) && !fresh(grants.get(partNumber))) {
|
|
223
|
+
throw new BuildToolError("store_sign_failed", `The store did not sign part ${partNumber}.`, "Recovery: retry; a retry continues the same upload.");
|
|
208
224
|
}
|
|
209
|
-
await signing;
|
|
210
225
|
}
|
|
211
|
-
throw new BuildToolError("store_sign_failed", `The store did not sign part ${partNumber}.`, "Recovery: retry; a retry continues the same upload.");
|
|
212
226
|
};
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
227
|
+
const upload = async (partNumber) => {
|
|
228
|
+
const body = await readPart(file, partNumber, plan.partSizeBytes, sizeBytes);
|
|
229
|
+
for (let attempt = 0;; attempt++) {
|
|
230
|
+
let status = null;
|
|
231
|
+
let error = null;
|
|
232
|
+
try {
|
|
217
233
|
const grant = await grantFor(partNumber);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
234
|
+
const response = await deps.fetch(grant.url, {
|
|
235
|
+
method: "PUT",
|
|
236
|
+
headers: grant.headers,
|
|
237
|
+
body,
|
|
238
|
+
redirect: "error",
|
|
239
|
+
signal: AbortSignal.any([stop.signal, AbortSignal.timeout(600_000)]),
|
|
240
|
+
});
|
|
241
|
+
if (response.ok)
|
|
242
|
+
return;
|
|
243
|
+
status = response.status;
|
|
244
|
+
}
|
|
245
|
+
catch (caught) {
|
|
246
|
+
error = caught;
|
|
247
|
+
}
|
|
248
|
+
if (stop.signal.aborted)
|
|
249
|
+
return;
|
|
250
|
+
// An expired or refused signature gets a new URL on the next try.
|
|
251
|
+
if (status === 400 || status === 403)
|
|
252
|
+
grants.delete(partNumber);
|
|
253
|
+
if (attempt >= PART_RETRIES) {
|
|
254
|
+
if (error instanceof BuildToolError)
|
|
255
|
+
throw error;
|
|
256
|
+
throw new BuildToolError("store_part_upload_failed", `Part ${partNumber} of ${plan.partCount} did not upload${status ? ` (${status})` : ""}.`, "Recovery: retry with the same file and clientVersion; the upload continues where it stopped.");
|
|
257
|
+
}
|
|
258
|
+
await deps.sleep(500 * 2 ** attempt);
|
|
259
|
+
if (stop.signal.aborted)
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
const worker = async () => {
|
|
264
|
+
for (let partNumber = queue.shift(); partNumber !== undefined && !stop.signal.aborted; partNumber = queue.shift()) {
|
|
265
|
+
try {
|
|
266
|
+
await upload(partNumber);
|
|
267
|
+
unfinished.delete(partNumber);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
failure ??= error;
|
|
271
|
+
stop.abort();
|
|
241
272
|
}
|
|
242
273
|
}
|
|
243
274
|
};
|
|
244
275
|
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, queue.length) }, worker));
|
|
276
|
+
if (stop.signal.aborted)
|
|
277
|
+
throw failure;
|
|
245
278
|
}
|
|
246
279
|
export function normalizeClientVersion(value) {
|
|
247
280
|
const raw = value?.trim() ?? "";
|
|
@@ -186,8 +186,8 @@ export declare const runtimeAnimateArgsSchema: z.ZodObject<{
|
|
|
186
186
|
value?: string | number | boolean | undefined;
|
|
187
187
|
instance?: string | undefined;
|
|
188
188
|
name?: string | undefined;
|
|
189
|
-
cmd?: "state" | "pause" | "play" | "stop" | "seek" | "speed" | "travel" | "start" | "set_param" | "get_param" | undefined;
|
|
190
189
|
space?: "local" | "global" | "both" | undefined;
|
|
190
|
+
cmd?: "state" | "pause" | "play" | "stop" | "seek" | "speed" | "travel" | "start" | "set_param" | "get_param" | undefined;
|
|
191
191
|
blend?: number | undefined;
|
|
192
192
|
position?: number | undefined;
|
|
193
193
|
speed?: number | undefined;
|
|
@@ -205,8 +205,8 @@ export declare const runtimeAnimateArgsSchema: z.ZodObject<{
|
|
|
205
205
|
value?: string | number | boolean | undefined;
|
|
206
206
|
instance?: string | undefined;
|
|
207
207
|
name?: string | undefined;
|
|
208
|
-
cmd?: "state" | "pause" | "play" | "stop" | "seek" | "speed" | "travel" | "start" | "set_param" | "get_param" | undefined;
|
|
209
208
|
space?: "local" | "global" | "both" | undefined;
|
|
209
|
+
cmd?: "state" | "pause" | "play" | "stop" | "seek" | "speed" | "travel" | "start" | "set_param" | "get_param" | undefined;
|
|
210
210
|
blend?: number | undefined;
|
|
211
211
|
position?: number | undefined;
|
|
212
212
|
speed?: number | undefined;
|
|
@@ -64,10 +64,10 @@ export interface RemoteMcpResult {
|
|
|
64
64
|
signIn: string;
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
|
-
* Adds the Summer Games MCP next to summer-engine in the same config file
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
67
|
+
* Adds the Summer Games MCP next to summer-engine in the same config file, in
|
|
68
|
+
* the agent's own entry shape (remoteMcp in agent-table.ts). An entry that
|
|
69
|
+
* already points at the same URL, under any name, counts as added and is left
|
|
70
|
+
* as it is. Returns null for agents without remoteMcp.
|
|
71
71
|
*/
|
|
72
72
|
export declare function configureSummerGamesMcp(options: Pick<AgentConfigOptions, "agent" | "scope" | "dryRun" | "cwd" | "env">): Promise<RemoteMcpResult | null>;
|
|
73
73
|
export declare const DEFAULT_CHANNEL = "latest";
|
|
@@ -85,4 +85,4 @@ export declare function mcpEntry(format: McpFormat, server: StdioMcpServerConfig
|
|
|
85
85
|
export declare function resolvePackageRoot(): string;
|
|
86
86
|
/** Directory name Gemini expects the extension under; the manifest `name` must match it. */
|
|
87
87
|
export declare const GEMINI_EXTENSION_DIR_NAME = "summer-engine";
|
|
88
|
-
export declare function upsertTomlArrayBlock(content: string, block: string): string;
|
|
88
|
+
export declare function upsertTomlArrayBlock(content: string, block: string, name?: string): string;
|
|
@@ -79,10 +79,10 @@ export async function configureAgentMcp(options) {
|
|
|
79
79
|
export const SUMMER_GAMES_MCP_SERVER_NAME = "summer-games";
|
|
80
80
|
export const SUMMER_GAMES_MCP_URL = "https://mcp.summer.games/mcp";
|
|
81
81
|
/**
|
|
82
|
-
* Adds the Summer Games MCP next to summer-engine in the same config file
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
82
|
+
* Adds the Summer Games MCP next to summer-engine in the same config file, in
|
|
83
|
+
* the agent's own entry shape (remoteMcp in agent-table.ts). An entry that
|
|
84
|
+
* already points at the same URL, under any name, counts as added and is left
|
|
85
|
+
* as it is. Returns null for agents without remoteMcp.
|
|
86
86
|
*/
|
|
87
87
|
export async function configureSummerGamesMcp(options) {
|
|
88
88
|
const spec = agentSpec(options.agent);
|
|
@@ -92,39 +92,68 @@ export async function configureSummerGamesMcp(options) {
|
|
|
92
92
|
const target = resolveConfigTarget(spec, options.scope, resolve(options.cwd ?? process.cwd()), env);
|
|
93
93
|
const write = !options.dryRun;
|
|
94
94
|
const url = SUMMER_GAMES_MCP_URL;
|
|
95
|
+
const name = SUMMER_GAMES_MCP_SERVER_NAME;
|
|
96
|
+
const entry = spec.remoteMcp.entry(url);
|
|
97
|
+
const key = topLevelKey(spec.format);
|
|
95
98
|
let existing;
|
|
96
99
|
let changed = false;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
switch (spec.format) {
|
|
101
|
+
case "toml":
|
|
102
|
+
case "toml-array": {
|
|
103
|
+
const current = await readTextFileIfExists(target.path);
|
|
104
|
+
existing = spec.format === "toml" ? findTomlServerByUrl(current, url) : findTomlArrayServerByUrl(current, url);
|
|
105
|
+
if (existing)
|
|
106
|
+
break;
|
|
107
|
+
const next = spec.format === "toml"
|
|
108
|
+
? upsertTomlTable(current, renderTomlTable(`[mcp_servers.${name}]`, entry), name)
|
|
109
|
+
: upsertTomlArrayBlock(current, renderTomlTable("[[mcp_servers]]", entry), name);
|
|
103
110
|
changed = current !== next;
|
|
104
111
|
if (write && changed)
|
|
105
112
|
await writeTextFile(target.path, next);
|
|
113
|
+
break;
|
|
106
114
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
case "yaml-goose":
|
|
116
|
+
case "yaml-hermes": {
|
|
117
|
+
const current = await readTextFileIfExists(target.path);
|
|
118
|
+
const parsed = current.trim() === "" ? {} : parseDocument(current).toJS();
|
|
119
|
+
existing = isJsonObject(parsed) ? findServerByUrl([parsed[key]], url) : undefined;
|
|
120
|
+
if (existing)
|
|
121
|
+
break;
|
|
122
|
+
({ changed } = await upsertYamlConfig(target.path, [key, name], entry, write));
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case "json-gemini":
|
|
126
|
+
throw new Error("The Gemini extension manifest has no Summer Games entry.");
|
|
127
|
+
default: {
|
|
128
|
+
const current = await readJsonConfig(target.path);
|
|
129
|
+
const maps = [current[key]];
|
|
130
|
+
// Claude Code's ~/.claude.json also keeps per-project servers (`claude
|
|
131
|
+
// mcp add` without --scope user) under projects.<dir>.mcpServers.
|
|
132
|
+
if (isJsonObject(current.projects)) {
|
|
133
|
+
for (const project of Object.values(current.projects)) {
|
|
134
|
+
if (isJsonObject(project))
|
|
135
|
+
maps.push(project.mcpServers);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
existing = findServerByUrl(maps, url);
|
|
139
|
+
if (existing)
|
|
140
|
+
break;
|
|
112
141
|
const next = copyJsonObject(current);
|
|
113
|
-
next
|
|
142
|
+
next[key] = mergeNamedObject(next[key], name, entry, key);
|
|
114
143
|
const nextRendered = renderJsonFile(next);
|
|
115
144
|
changed = renderJsonFile(current) !== nextRendered;
|
|
116
145
|
if (write && changed)
|
|
117
146
|
await writeTextFile(target.path, nextRendered);
|
|
118
147
|
}
|
|
119
148
|
}
|
|
120
|
-
const serverName = existing ??
|
|
149
|
+
const serverName = existing ?? name;
|
|
121
150
|
return {
|
|
122
151
|
serverName,
|
|
123
152
|
url,
|
|
124
153
|
path: target.path,
|
|
125
154
|
changed,
|
|
126
155
|
wrote: write && changed,
|
|
127
|
-
...(existing && existing !==
|
|
156
|
+
...(existing && existing !== name ? { existingName: existing } : {}),
|
|
128
157
|
signIn: spec.remoteMcp.signIn(serverName),
|
|
129
158
|
};
|
|
130
159
|
}
|
|
@@ -142,27 +171,62 @@ function sameMcpUrl(a, b) {
|
|
|
142
171
|
};
|
|
143
172
|
return normalize(a) === normalize(b);
|
|
144
173
|
}
|
|
145
|
-
/**
|
|
146
|
-
*
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (isJsonObject(config.projects)) {
|
|
151
|
-
for (const project of Object.values(config.projects)) {
|
|
152
|
-
if (isJsonObject(project))
|
|
153
|
-
maps.push(project.mcpServers);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
174
|
+
/** Agents name a remote server's address url, serverUrl (Windsurf,
|
|
175
|
+
* Antigravity), httpUrl (Qwen Code) or uri (Goose). */
|
|
176
|
+
const REMOTE_URL_FIELDS = ["url", "serverUrl", "httpUrl", "uri"];
|
|
177
|
+
/** The name of a server with this URL in any of these name -> entry maps. */
|
|
178
|
+
function findServerByUrl(maps, url) {
|
|
156
179
|
for (const map of maps) {
|
|
157
180
|
if (!isJsonObject(map))
|
|
158
181
|
continue;
|
|
159
182
|
for (const [name, entry] of Object.entries(map)) {
|
|
160
|
-
if (isJsonObject(entry) && sameMcpUrl(entry
|
|
183
|
+
if (isJsonObject(entry) && REMOTE_URL_FIELDS.some((field) => sameMcpUrl(entry[field], url)))
|
|
161
184
|
return name;
|
|
162
185
|
}
|
|
163
186
|
}
|
|
164
187
|
return undefined;
|
|
165
188
|
}
|
|
189
|
+
/** The name of a [[mcp_servers]] block (Mistral Vibe) whose url is this URL. */
|
|
190
|
+
function findTomlArrayServerByUrl(content, url) {
|
|
191
|
+
let block;
|
|
192
|
+
const done = () => (block?.matches ? block.name : undefined);
|
|
193
|
+
for (const line of content.split(/\r?\n/)) {
|
|
194
|
+
if (/^\s*\[/.test(line)) {
|
|
195
|
+
if (done())
|
|
196
|
+
return done();
|
|
197
|
+
block = /^\s*\[\[mcp_servers\]\]\s*(?:#.*)?$/.test(line) ? { matches: false } : undefined;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (!block)
|
|
201
|
+
continue;
|
|
202
|
+
const field = /^\s*(name|url)\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(line);
|
|
203
|
+
if (!field)
|
|
204
|
+
continue;
|
|
205
|
+
const value = field[2] ?? field[3];
|
|
206
|
+
if (field[1] === "name")
|
|
207
|
+
block.name = value;
|
|
208
|
+
else if (sameMcpUrl(value, url))
|
|
209
|
+
block.matches = true;
|
|
210
|
+
}
|
|
211
|
+
return done();
|
|
212
|
+
}
|
|
213
|
+
/** A TOML table: the header line, then key = value for each entry field. */
|
|
214
|
+
function renderTomlTable(header, entry) {
|
|
215
|
+
return `${[header, ...Object.entries(entry).map(([field, value]) => `${field} = ${tomlValue(value)}`)].join("\n")}\n`;
|
|
216
|
+
}
|
|
217
|
+
function tomlValue(value) {
|
|
218
|
+
if (typeof value === "string")
|
|
219
|
+
return tomlString(value);
|
|
220
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
221
|
+
return String(value);
|
|
222
|
+
if (Array.isArray(value))
|
|
223
|
+
return `[${value.map(tomlValue).join(", ")}]`;
|
|
224
|
+
if (isJsonObject(value)) {
|
|
225
|
+
const fields = Object.entries(value).map(([field, inner]) => `${field} = ${tomlValue(inner)}`);
|
|
226
|
+
return fields.length === 0 ? "{}" : `{ ${fields.join(", ")} }`;
|
|
227
|
+
}
|
|
228
|
+
throw new Error(`Cannot write ${JSON.stringify(value)} as TOML.`);
|
|
229
|
+
}
|
|
166
230
|
/** The name of a [mcp_servers.<name>] table whose url is this URL. */
|
|
167
231
|
function findTomlServerByUrl(content, url) {
|
|
168
232
|
let table;
|
|
@@ -430,10 +494,10 @@ async function upsertTomlArrayConfig(path, server, write) {
|
|
|
430
494
|
}
|
|
431
495
|
return { changed };
|
|
432
496
|
}
|
|
433
|
-
export function upsertTomlArrayBlock(content, block) {
|
|
497
|
+
export function upsertTomlArrayBlock(content, block, name = SUMMER_MCP_SERVER_NAME) {
|
|
434
498
|
const lines = content.split(/\r?\n/);
|
|
435
499
|
const header = /^\s*\[\[mcp_servers\]\]\s*(?:#.*)?$/;
|
|
436
|
-
const nameLine = new RegExp(`^\\s*name\\s*=\\s*"${
|
|
500
|
+
const nameLine = new RegExp(`^\\s*name\\s*=\\s*"${name}"\\s*(?:#.*)?$`);
|
|
437
501
|
let start = -1;
|
|
438
502
|
let end = lines.length;
|
|
439
503
|
for (let index = 0; index < lines.length; index += 1) {
|
|
@@ -22,7 +22,6 @@ export type ConfigScope = "user" | "project";
|
|
|
22
22
|
* json-gemini the Gemini extension manifest (legacy)
|
|
23
23
|
*/
|
|
24
24
|
export type McpFormat = "json" | "json-stdio" | "json-vscode" | "json-copilot" | "json-opencode" | "json-servers" | "json-zed" | "json-amp" | "json-crush" | "json-transport" | "toml" | "toml-array" | "yaml-goose" | "yaml-hermes" | "json-gemini";
|
|
25
|
-
export type RemoteMcpFormat = "json-http" | "toml-url";
|
|
26
25
|
export type SkillLocationKind = "skill-dir" | "cursor-rule-dir" | "windsurf-rule-file" | "cline-rule-dir" | "opencode-skill-dir";
|
|
27
26
|
export interface PathContext {
|
|
28
27
|
home: string;
|
|
@@ -72,14 +71,16 @@ export interface AgentSpec {
|
|
|
72
71
|
envOverride: string;
|
|
73
72
|
format: McpFormat;
|
|
74
73
|
/**
|
|
75
|
-
*
|
|
76
|
-
* own
|
|
77
|
-
*
|
|
78
|
-
* toml-url [mcp_servers.<name>] url (Codex)
|
|
74
|
+
* The Summer Games player MCP (a remote server that signs in with OAuth on
|
|
75
|
+
* its own) in this agent's file, under the same key as summer-engine.
|
|
76
|
+
* entry is the server's value; for TOML it is written as key = value lines.
|
|
79
77
|
* signIn tells the person how to sign in once, for the entry's name.
|
|
78
|
+
* Absent: setup links the engine only (no OAuth sign-in for remote servers,
|
|
79
|
+
* no file entry for them, or a sign-in api.summer.games has not been checked
|
|
80
|
+
* to accept).
|
|
80
81
|
*/
|
|
81
82
|
remoteMcp?: {
|
|
82
|
-
|
|
83
|
+
entry: (url: string) => Record<string, unknown>;
|
|
83
84
|
signIn: (name: string) => string;
|
|
84
85
|
};
|
|
85
86
|
mcp: {
|