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
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opening a project in Summer Engine for an agent: what the MCP does when no
|
|
3
|
+
* editor is reachable (src/mcp/server.ts getClient), and the
|
|
4
|
+
* projects.open_local operation (core/capabilities/projects-local.ts), for
|
|
5
|
+
* agents with no shell.
|
|
6
|
+
*
|
|
7
|
+
* installed, a project, nothing open -> launch it (background), wait, connect
|
|
8
|
+
* not installed -> install, only with the person's yes
|
|
9
|
+
* (projects.open_local install:true, or `summer dev`)
|
|
10
|
+
* no project for this session -> projects.create_local (or `summer dev`)
|
|
11
|
+
* another project open -> name that project
|
|
12
|
+
*
|
|
13
|
+
* SUMMER_AUTO_LAUNCH=0 turns the launch on a tool call off; projects.open_local
|
|
14
|
+
* is an explicit ask and still opens.
|
|
15
|
+
*/
|
|
16
|
+
import { execSync } from "child_process";
|
|
17
|
+
import { realpathSync } from "fs";
|
|
18
|
+
import { resolve, sep } from "path";
|
|
19
|
+
import { EngineApiClient } from "./api-client.js";
|
|
20
|
+
import { checkEngineHealth, findProjectRoot, listEngineInstances } from "./engine.js";
|
|
21
|
+
import { CLOSED_EARLY, lastLaunch, LaunchLockBusyError, launchEditor, resolveRunBinary } from "./engine-launch.js";
|
|
22
|
+
import { ENGINE_BIN_ENV } from "./engine-install.js";
|
|
23
|
+
import { installEngine } from "./engine-installer.js";
|
|
24
|
+
import { EngineNotRunningError } from "./error-codes.js";
|
|
25
|
+
import { processIsAlive } from "./util/process.js";
|
|
26
|
+
import { settlesWithin, sleep } from "./util/sleep.js";
|
|
27
|
+
export const AUTO_LAUNCH_ENV = "SUMMER_AUTO_LAUNCH";
|
|
28
|
+
/** One tool call ends within this, all steps included (under a 60 s client timeout). */
|
|
29
|
+
export const SELF_START_WAIT_MS = 40_000;
|
|
30
|
+
/** A launch this recent is still starting: wait for it, never launch a second editor. */
|
|
31
|
+
const LAUNCH_GRACE_MS = 5 * 60_000;
|
|
32
|
+
export const DEV_COMMAND = "npx -y summer-engine@latest dev";
|
|
33
|
+
export const SELF_START_RECOVERY = {
|
|
34
|
+
notInstalledHere: "Summer Engine is not installed. Ask the person first: this downloads and installs Summer Engine (about 660 MB, free). " +
|
|
35
|
+
`With their OK, call projects.open_local with install: true, or run: ${DEV_COMMAND} --yes (it installs the engine and opens this project).`,
|
|
36
|
+
notInstalled: "Summer Engine is not installed and this session has no project. Ask the person first: this downloads and installs Summer Engine " +
|
|
37
|
+
"(about 660 MB, free). With their OK: projects.create_local, then projects.open_local with install: true; " +
|
|
38
|
+
`or run: ${DEV_COMMAND} --yes (it installs, creates a starter project and opens it).`,
|
|
39
|
+
noProject: "No Summer project for this session. If summer dev is still opening one, wait and call again. Otherwise projects.create_local " +
|
|
40
|
+
`makes one (3d-basic by default) and projects.open_local opens it; or run: ${DEV_COMMAND} 3d-basic my-game.`,
|
|
41
|
+
busy: "Summer Engine has the project open but does not answer (busy, or still importing). Call again in 30 seconds.",
|
|
42
|
+
launchBusy: "Another Summer launch is in progress. Call again in 30 seconds.",
|
|
43
|
+
noDisplay: "This Linux session has no display (DISPLAY or WAYLAND_DISPLAY), so the editor cannot open here. " +
|
|
44
|
+
"Open the project in Summer Engine on a computer with a desktop.",
|
|
45
|
+
otherProject: (open, wanted) => `Summer Engine has ${open} open, not ${wanted}. Switch the editor to ${wanted}, or close it and call again.`,
|
|
46
|
+
starting: (root) => `Summer Engine is opening ${root}; the first open imports every asset and can take a few minutes. Call again in 30 seconds.`,
|
|
47
|
+
installing: (line) => `Summer Engine is installing (${line}). Call projects.open_local again in 30 seconds; it opens the project when the install is done.`,
|
|
48
|
+
closedEarly: (root) => `Summer Engine closed before it opened ${root}. Call again; if it closes again, open the project in Summer Engine to see why.`,
|
|
49
|
+
launchFailed: (code) => `Summer Engine could not start (${code}). Repair it: npx -y summer-engine@latest install --yes, then call again.`,
|
|
50
|
+
autoLaunchOff: (root) => `Open it with projects.open_local, or run: npx -y summer-engine@latest run ${root}`,
|
|
51
|
+
};
|
|
52
|
+
/** Projects this process launched, by root: when and which process, so a slow first start is waited on while it lives. */
|
|
53
|
+
const launched = new Map();
|
|
54
|
+
/** The answer just given: a caller's immediate retry (withEngine retries a
|
|
55
|
+
* failed connect once) gets it again instead of a second wait. */
|
|
56
|
+
let recent = null;
|
|
57
|
+
const RECENT_MS = 3_000;
|
|
58
|
+
/** The engine install this process runs for projects.open_local; it outlives
|
|
59
|
+
* one tool call, and the next call reports it or carries on. */
|
|
60
|
+
let install = null;
|
|
61
|
+
/** Test-only: forget the last answer only (withEngine's immediate retry window). */
|
|
62
|
+
export function _resetRecentForTests() {
|
|
63
|
+
recent = null;
|
|
64
|
+
}
|
|
65
|
+
/** Test-only: forget the launches and the install this process made. */
|
|
66
|
+
export function _resetSelfStartForTests() {
|
|
67
|
+
launched.clear();
|
|
68
|
+
recent = null;
|
|
69
|
+
install = null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The project this session works on: --project, else the folder the agent
|
|
73
|
+
* started in, else the project the last `summer dev` / `summer run` opened
|
|
74
|
+
* from this folder (it makes a subfolder the agent did not start in), while
|
|
75
|
+
* that launch is recent, its editor lives and the project is still there.
|
|
76
|
+
*/
|
|
77
|
+
export async function sessionProject(selection) {
|
|
78
|
+
if (selection?.projectPath)
|
|
79
|
+
return findProjectRoot(selection.projectPath);
|
|
80
|
+
if (selection?.instanceId)
|
|
81
|
+
return null;
|
|
82
|
+
const cwd = selection?.cwd ?? process.cwd();
|
|
83
|
+
const here = await findProjectRoot(cwd);
|
|
84
|
+
if (here)
|
|
85
|
+
return here;
|
|
86
|
+
const last = lastLaunch();
|
|
87
|
+
if (!last)
|
|
88
|
+
return null;
|
|
89
|
+
const root = await findProjectRoot(last.projectPath);
|
|
90
|
+
const folder = canonicalPath(cwd);
|
|
91
|
+
// Only a launch from this session's folder, or of a project below it: never another session's project.
|
|
92
|
+
const ours = root && (last.cwd === folder || root.startsWith(folder.endsWith(sep) ? folder : folder + sep));
|
|
93
|
+
return ours ? root : null;
|
|
94
|
+
}
|
|
95
|
+
function canonicalPath(path) {
|
|
96
|
+
try {
|
|
97
|
+
return realpathSync(path);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return resolve(path);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Linux with no display server: an editor window cannot open (containers, SSH, cloud agents). */
|
|
104
|
+
export function hasNoDisplay(env = process.env, platform = process.platform) {
|
|
105
|
+
return platform === "linux" && !env.DISPLAY?.trim() && !env.WAYLAND_DISPLAY?.trim();
|
|
106
|
+
}
|
|
107
|
+
/** A live editor with another project than `root`, named for the person. */
|
|
108
|
+
async function otherOpenProject(root) {
|
|
109
|
+
for (const instance of await listEngineInstances()) {
|
|
110
|
+
if (instance.resourceRoot === root)
|
|
111
|
+
continue;
|
|
112
|
+
if (await checkEngineHealth(instance.port)) {
|
|
113
|
+
return instance.projectName ? `${instance.projectName} (${instance.resourceRoot})` : instance.resourceRoot;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
async function connectWithin(selection, connect, ms) {
|
|
119
|
+
const deadline = Date.now() + ms;
|
|
120
|
+
while (true) {
|
|
121
|
+
try {
|
|
122
|
+
return await connect(selection);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
if (Date.now() >= deadline)
|
|
126
|
+
return null;
|
|
127
|
+
await sleep(1_000);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** The MCP's stdout is its protocol: the installer never prints to it or hands it to a child. */
|
|
132
|
+
const quietInstall = (opts) => installEngine(opts, { stdoutIsTTY: false, exec: (command) => execSync(command, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }) });
|
|
133
|
+
/**
|
|
134
|
+
* Install the engine in the background of this process and wait up to
|
|
135
|
+
* `waitMs` for it: true when it is installed, false while it still runs.
|
|
136
|
+
* A failed install throws its EngineInstallError (once; the next call starts over).
|
|
137
|
+
*/
|
|
138
|
+
async function installWithin(deps, waitMs) {
|
|
139
|
+
if (!install) {
|
|
140
|
+
const state = { line: "starting", done: false, error: null, promise: Promise.resolve() };
|
|
141
|
+
state.promise = (deps.install ?? quietInstall)({ log: (line) => (state.line = line.trim() || state.line), isTTY: false }).then(() => {
|
|
142
|
+
state.done = true;
|
|
143
|
+
}, (error) => {
|
|
144
|
+
state.done = true;
|
|
145
|
+
state.error = error;
|
|
146
|
+
});
|
|
147
|
+
install = state;
|
|
148
|
+
}
|
|
149
|
+
const current = install;
|
|
150
|
+
await settlesWithin(current.promise, waitMs);
|
|
151
|
+
if (!current.done)
|
|
152
|
+
return false;
|
|
153
|
+
install = null;
|
|
154
|
+
if (current.error)
|
|
155
|
+
throw current.error;
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Open `root` in the editor and return a connected client: launch it unless
|
|
160
|
+
* an editor has it, wait, and never launch it twice. Throws an
|
|
161
|
+
* EngineNotRunningError whose recovery is the one next step for the case.
|
|
162
|
+
*/
|
|
163
|
+
export async function openProjectInEditor(root, selection, options = {}, deps = {}) {
|
|
164
|
+
const env = deps.env ?? process.env;
|
|
165
|
+
const now = deps.now ?? Date.now;
|
|
166
|
+
const connect = deps.connect ?? ((s) => EngineApiClient.connect(s));
|
|
167
|
+
const log = deps.log ?? (() => { });
|
|
168
|
+
// One deadline for the whole call: install wait, launch, --help probe and connect.
|
|
169
|
+
const deadline = Date.now() + (deps.waitMs ?? SELF_START_WAIT_MS);
|
|
170
|
+
const left = (reserve = 0) => Math.max(0, deadline - Date.now() - reserve);
|
|
171
|
+
const reason = options.notRunning?.reason ?? "no editor has the project open";
|
|
172
|
+
const failure = (message, recovery) => new EngineNotRunningError(message, reason, recovery);
|
|
173
|
+
if (hasNoDisplay(env))
|
|
174
|
+
throw failure("Summer Engine cannot open a window here.", SELF_START_RECOVERY.noDisplay);
|
|
175
|
+
const open = await otherOpenProject(root);
|
|
176
|
+
if (open)
|
|
177
|
+
throw failure("Summer Engine has another project open.", SELF_START_RECOVERY.otherProject(open, root));
|
|
178
|
+
const binary = resolveRunBinary(undefined, env);
|
|
179
|
+
if (binary.binary === null) {
|
|
180
|
+
// A build named in SUMMER_BIN that is missing is not "not installed": installing would not change it.
|
|
181
|
+
if (env[ENGINE_BIN_ENV]?.trim())
|
|
182
|
+
throw failure("Summer Engine could not start.", binary.error);
|
|
183
|
+
if (!options.install && !install)
|
|
184
|
+
throw failure("Summer Engine is not installed.", SELF_START_RECOVERY.notInstalledHere);
|
|
185
|
+
log("Summer Engine is not installed: installing it");
|
|
186
|
+
if (!(await installWithin(deps, left(5_000)))) {
|
|
187
|
+
throw failure("Summer Engine is installing.", SELF_START_RECOVERY.installing(install?.line ?? "starting"));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const since = launched.get(root);
|
|
191
|
+
if (since && now() - since.at < LAUNCH_GRACE_MS && (since.pid === undefined || processIsAlive(since.pid))) {
|
|
192
|
+
log(`waiting for the editor launched ${Math.round((now() - since.at) / 1000)} s ago for ${root}`);
|
|
193
|
+
const client = await connectWithin(selection, connect, left());
|
|
194
|
+
if (client)
|
|
195
|
+
return client;
|
|
196
|
+
throw failure("Summer Engine is starting.", SELF_START_RECOVERY.starting(root));
|
|
197
|
+
}
|
|
198
|
+
launched.delete(root);
|
|
199
|
+
log(`no editor has ${root} open: launching it in the background`);
|
|
200
|
+
let result;
|
|
201
|
+
try {
|
|
202
|
+
result = await (deps.launch ?? launchEditor)({
|
|
203
|
+
projectPath: root,
|
|
204
|
+
posture: "background",
|
|
205
|
+
// The lock wait, the --help probe and the API wait end by then; the connect after it fits in the rest.
|
|
206
|
+
deadline: deadline - 5_000,
|
|
207
|
+
out: { log, error: log },
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
// Another launch holds the lock: it may be for another project.
|
|
212
|
+
if (error instanceof LaunchLockBusyError)
|
|
213
|
+
throw failure("Another Summer launch is in progress.", SELF_START_RECOVERY.launchBusy);
|
|
214
|
+
throw failure("Summer Engine could not start.", SELF_START_RECOVERY.launchFailed(error.message));
|
|
215
|
+
}
|
|
216
|
+
switch (result.status) {
|
|
217
|
+
case "running": {
|
|
218
|
+
if (result.launched)
|
|
219
|
+
launched.set(root, { at: now(), pid: result.pid });
|
|
220
|
+
// The editor answers health a moment before it registers for discovery.
|
|
221
|
+
const client = await connectWithin(selection, connect, Math.min(5_000, left()));
|
|
222
|
+
if (client)
|
|
223
|
+
return client;
|
|
224
|
+
throw failure("Summer Engine is starting.", SELF_START_RECOVERY.starting(root));
|
|
225
|
+
}
|
|
226
|
+
case "starting":
|
|
227
|
+
launched.set(root, { at: now(), pid: result.pid });
|
|
228
|
+
throw failure("Summer Engine is starting.", SELF_START_RECOVERY.starting(root));
|
|
229
|
+
case "other_project":
|
|
230
|
+
throw failure("Summer Engine has another project open.", SELF_START_RECOVERY.otherProject(result.project, root));
|
|
231
|
+
case "no_binary":
|
|
232
|
+
throw failure("Summer Engine is not installed.", SELF_START_RECOVERY.notInstalledHere);
|
|
233
|
+
case "spawn_failed":
|
|
234
|
+
if (result.code === CLOSED_EARLY)
|
|
235
|
+
throw failure("Summer Engine closed before the project opened.", SELF_START_RECOVERY.closedEarly(root));
|
|
236
|
+
throw failure("Summer Engine could not start.", SELF_START_RECOVERY.launchFailed(result.code));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Open the session's project and return a connected client, or throw an
|
|
241
|
+
* EngineNotRunningError whose recovery is the one next step for this case.
|
|
242
|
+
* Never installs: that needs the person's yes (projects.open_local).
|
|
243
|
+
*/
|
|
244
|
+
export async function selfStartEngine(selection, notRunning, deps = {}) {
|
|
245
|
+
const now = deps.now ?? Date.now;
|
|
246
|
+
if (recent && now() - recent.at < RECENT_MS)
|
|
247
|
+
throw recent.error;
|
|
248
|
+
try {
|
|
249
|
+
return await openSessionProject(selection, notRunning, deps);
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
if (error instanceof EngineNotRunningError)
|
|
253
|
+
recent = { at: now(), error };
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function openSessionProject(selection, notRunning, deps) {
|
|
258
|
+
const env = deps.env ?? process.env;
|
|
259
|
+
const failure = (message, recovery) => new EngineNotRunningError(message, notRunning.reason, recovery);
|
|
260
|
+
// A registered editor that does not answer is busy (an import, a blocking
|
|
261
|
+
// script): it is there, so never start a second one.
|
|
262
|
+
if (/instance \S+ is not responding/.test(notRunning.reason))
|
|
263
|
+
throw failure("Summer Engine is not answering.", SELF_START_RECOVERY.busy);
|
|
264
|
+
// The person already said yes to an install this process runs: say so instead of asking again.
|
|
265
|
+
if (install && !install.done)
|
|
266
|
+
throw failure("Summer Engine is installing.", SELF_START_RECOVERY.installing(install.line));
|
|
267
|
+
const root = await sessionProject(selection);
|
|
268
|
+
if (!root) {
|
|
269
|
+
if (resolveRunBinary(undefined, env).binary === null)
|
|
270
|
+
throw failure("Summer Engine is not installed.", SELF_START_RECOVERY.notInstalled);
|
|
271
|
+
throw failure("No Summer project for this session.", SELF_START_RECOVERY.noProject);
|
|
272
|
+
}
|
|
273
|
+
if (env[AUTO_LAUNCH_ENV] === "0") {
|
|
274
|
+
const open = await otherOpenProject(root);
|
|
275
|
+
if (open)
|
|
276
|
+
throw failure("Summer Engine has another project open.", SELF_START_RECOVERY.otherProject(open, root));
|
|
277
|
+
if (resolveRunBinary(undefined, env).binary === null)
|
|
278
|
+
throw failure("Summer Engine is not installed.", SELF_START_RECOVERY.notInstalledHere);
|
|
279
|
+
throw failure(notRunning.message, SELF_START_RECOVERY.autoLaunchOff(root));
|
|
280
|
+
}
|
|
281
|
+
return openProjectInEditor(root, selection, { notRunning }, deps);
|
|
282
|
+
}
|
package/dist/core/engine.d.ts
CHANGED
|
@@ -48,7 +48,13 @@ export interface EngineConnection {
|
|
|
48
48
|
export declare function getApiToken(summerDir?: string): Promise<string | null>;
|
|
49
49
|
export declare function getApiPort(summerDir?: string): Promise<number>;
|
|
50
50
|
export declare function findProjectRoot(startPath: string): Promise<string | null>;
|
|
51
|
-
export declare function listEngineInstances(nowMs?: number, summerDir?: string
|
|
51
|
+
export declare function listEngineInstances(nowMs?: number, summerDir?: string,
|
|
52
|
+
/** Keep entries whose heartbeat is old (up to 30 min): an editor that is
|
|
53
|
+
* alive but busy (a long import) stops beating; the launcher must still
|
|
54
|
+
* not start a second one. */
|
|
55
|
+
options?: {
|
|
56
|
+
anyHeartbeat?: boolean;
|
|
57
|
+
}): Promise<EngineInstance[]>;
|
|
52
58
|
export declare function resolveEngineConnection(selection?: EngineSelection, options?: EngineDiscoveryOptions): Promise<EngineConnection>;
|
|
53
59
|
/**
|
|
54
60
|
* The registry half of no-selection discovery (the CLI face; see
|
package/dist/core/engine.js
CHANGED
|
@@ -7,6 +7,7 @@ import { parseEngineCapabilities, } from "./capability-skew.js";
|
|
|
7
7
|
const DEFAULT_PORT = 6550;
|
|
8
8
|
const INSTANCE_SCHEMA_VERSION = 1;
|
|
9
9
|
const INSTANCE_STALE_MS = 180_000;
|
|
10
|
+
const BUSY_INSTANCE_STALE_MS = 30 * 60_000;
|
|
10
11
|
const MAX_INSTANCE_FILE_BYTES = 64 * 1024;
|
|
11
12
|
/** Env names the MCP server already honours for an explicit selection
|
|
12
13
|
* (src/mcp/server.ts startMcpServer). The CLI face reads the same two so a
|
|
@@ -116,7 +117,11 @@ function parseEngineInstance(value) {
|
|
|
116
117
|
engineVersion: stringFromUnknown(record.engineVersion),
|
|
117
118
|
};
|
|
118
119
|
}
|
|
119
|
-
export async function listEngineInstances(nowMs = Date.now(), summerDir = getSummerDir()
|
|
120
|
+
export async function listEngineInstances(nowMs = Date.now(), summerDir = getSummerDir(),
|
|
121
|
+
/** Keep entries whose heartbeat is old (up to 30 min): an editor that is
|
|
122
|
+
* alive but busy (a long import) stops beating; the launcher must still
|
|
123
|
+
* not start a second one. */
|
|
124
|
+
options = {}) {
|
|
120
125
|
const instancesDir = join(summerDir, "instances");
|
|
121
126
|
try {
|
|
122
127
|
const dirInfo = await lstat(instancesDir);
|
|
@@ -142,7 +147,9 @@ export async function listEngineInstances(nowMs = Date.now(), summerDir = getSum
|
|
|
142
147
|
const parsed = parseEngineInstance(JSON.parse(await readFile(path, "utf-8")));
|
|
143
148
|
if (!parsed || !processIsAlive(parsed.pid))
|
|
144
149
|
continue;
|
|
145
|
-
|
|
150
|
+
// A busy editor's heartbeat may lag; one silent this long is gone (its pid reused).
|
|
151
|
+
const staleAfter = options.anyHeartbeat ? BUSY_INSTANCE_STALE_MS : INSTANCE_STALE_MS;
|
|
152
|
+
if (nowMs - parsed.heartbeatAt * 1000 > staleAfter)
|
|
146
153
|
continue;
|
|
147
154
|
parsed.resourceRoot = await canonicalPath(parsed.resourceRoot);
|
|
148
155
|
instances.push(parsed);
|
|
@@ -51,8 +51,11 @@ export declare function networkDetails(error: unknown): {
|
|
|
51
51
|
* registry paths) stays in the logs, not in the line the agent repeats. */
|
|
52
52
|
export declare class EngineNotRunningError extends Error {
|
|
53
53
|
readonly reason: string;
|
|
54
|
+
readonly recovery: string;
|
|
54
55
|
readonly code: "engine_not_running";
|
|
55
|
-
|
|
56
|
+
/** `recovery`: the one next step for this case (not installed, no project,
|
|
57
|
+
* another project open, still starting); the general step by default. */
|
|
58
|
+
constructor(message: string, reason: string, recovery?: string);
|
|
56
59
|
}
|
|
57
60
|
/** A connect error from core/engine.ts → EngineNotRunningError when it means
|
|
58
61
|
* no editor is reachable; null for the others (several editors open, …),
|
package/dist/core/error-codes.js
CHANGED
|
@@ -71,10 +71,14 @@ export function networkDetails(error) {
|
|
|
71
71
|
* registry paths) stays in the logs, not in the line the agent repeats. */
|
|
72
72
|
export class EngineNotRunningError extends Error {
|
|
73
73
|
reason;
|
|
74
|
+
recovery;
|
|
74
75
|
code = "engine_not_running";
|
|
75
|
-
|
|
76
|
+
/** `recovery`: the one next step for this case (not installed, no project,
|
|
77
|
+
* another project open, still starting); the general step by default. */
|
|
78
|
+
constructor(message, reason, recovery = RECOVERY.engine_not_running) {
|
|
76
79
|
super(message);
|
|
77
80
|
this.reason = reason;
|
|
81
|
+
this.recovery = recovery;
|
|
78
82
|
this.name = "EngineNotRunningError";
|
|
79
83
|
}
|
|
80
84
|
}
|
|
@@ -93,5 +97,5 @@ export function engineNotRunningFromConnectError(reason) {
|
|
|
93
97
|
return null;
|
|
94
98
|
}
|
|
95
99
|
/** Engine-free tools keep working while the engine is down. */
|
|
96
|
-
export const ENGINE_FREE_NOTE = "Only tools that touch the local project need the engine.
|
|
97
|
-
"(
|
|
100
|
+
export const ENGINE_FREE_NOTE = "Only tools that touch the local project need the engine. Docs, generation and asset tools " +
|
|
101
|
+
"(summer_docs_search, summer_docs_read, summer_assets_generate_*, summer_assets_search, summer_assets_get, summer_jobs_get) work now.";
|
|
@@ -21,10 +21,10 @@ export declare const readLibraryInputSchema: z.ZodObject<{
|
|
|
21
21
|
part: z.ZodOptional<z.ZodEnum<["skill", "resource", "all"]>>;
|
|
22
22
|
}, "strict", z.ZodTypeAny, {
|
|
23
23
|
id: string;
|
|
24
|
-
part?: "resource" | "
|
|
24
|
+
part?: "resource" | "skill" | "all" | undefined;
|
|
25
25
|
}, {
|
|
26
26
|
id: string;
|
|
27
|
-
part?: "resource" | "
|
|
27
|
+
part?: "resource" | "skill" | "all" | undefined;
|
|
28
28
|
}>;
|
|
29
29
|
export type ReadLibraryArgs = z.infer<typeof readLibraryInputSchema>;
|
|
30
30
|
export interface LibraryReadDeps extends LibrarySearchDeps {
|
|
@@ -98,12 +98,12 @@ export declare const searchLibraryInputSchema: z.ZodObject<{
|
|
|
98
98
|
include_preview: z.ZodOptional<z.ZodBoolean>;
|
|
99
99
|
}, "strict", z.ZodTypeAny, {
|
|
100
100
|
query: string;
|
|
101
|
-
kinds?: ("
|
|
101
|
+
kinds?: ("skill" | "tool" | "template" | "reference" | "example" | "collection")[] | undefined;
|
|
102
102
|
limit?: number | undefined;
|
|
103
103
|
include_preview?: boolean | undefined;
|
|
104
104
|
}, {
|
|
105
105
|
query: string;
|
|
106
|
-
kinds?: ("
|
|
106
|
+
kinds?: ("skill" | "tool" | "template" | "reference" | "example" | "collection")[] | undefined;
|
|
107
107
|
limit?: number | undefined;
|
|
108
108
|
include_preview?: boolean | undefined;
|
|
109
109
|
}>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type TemplateEntry } from "./templates.js";
|
|
2
|
+
import { type InstructionFileResult } from "../project-memory/agent-instructions.js";
|
|
3
|
+
/**
|
|
4
|
+
* Built-in templates are generated in-process — no download, no pin. Their
|
|
5
|
+
* library manifests (`library/templates/<slug>/resource.yaml`) declare
|
|
6
|
+
* `builtin: true`; this map supplies the generator for each such slug.
|
|
7
|
+
*/
|
|
8
|
+
type BuiltinGenerator = (dir: string, projectName: string) => void;
|
|
9
|
+
export declare const BUILTIN_GENERATORS: Readonly<Record<string, BuiltinGenerator>>;
|
|
10
|
+
/**
|
|
11
|
+
* Copy the autopilot verification scaffold into a fresh project.
|
|
12
|
+
*
|
|
13
|
+
* Every project gets a working example of how to prove its own gameplay: a probe
|
|
14
|
+
* that drives the player with real input, saves rendered frames, asserts, and
|
|
15
|
+
* exits. Without it every agent reinvents verification from scratch, and most
|
|
16
|
+
* settle for asking the user to play the game instead.
|
|
17
|
+
*
|
|
18
|
+
* Never overwrites — an existing tests/autopilot/ is the user's, not ours.
|
|
19
|
+
*/
|
|
20
|
+
export declare function scaffoldAutopilot(projectDir: string): boolean;
|
|
21
|
+
/** A folder `summer dev` may create a project in: missing, empty, or only
|
|
22
|
+
* dotfiles (.git, .claude, .vscode, ...). */
|
|
23
|
+
export declare function isEmptyProjectFolder(dir: string): boolean;
|
|
24
|
+
/** The home folder or a filesystem root: never a project folder by default. */
|
|
25
|
+
export declare function isHomeOrRoot(dir: string, home?: string): boolean;
|
|
26
|
+
/** The project that holds `dir` (project.godot here or above, never above the home folder), or null. */
|
|
27
|
+
export declare function projectRootAbove(dir: string, home?: string): string | null;
|
|
28
|
+
export interface CreatedProject {
|
|
29
|
+
scaffolded: boolean;
|
|
30
|
+
instructions: InstructionFileResult[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Write a project from a template into `fullPath`: generate a built-in, or
|
|
34
|
+
* fetch a pinned template (verified tree digest), then the autopilot
|
|
35
|
+
* scaffold, `.summer/project.json` and the agent instruction files.
|
|
36
|
+
* `fullPath` may already exist when isEmptyProjectFolder says so; files that
|
|
37
|
+
* are already there are kept, never replaced.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createProject(entry: TemplateEntry, fullPath: string, options: {
|
|
40
|
+
name: string;
|
|
41
|
+
keepGit?: boolean;
|
|
42
|
+
log: (line: string) => void;
|
|
43
|
+
}): Promise<CreatedProject>;
|
|
44
|
+
/**
|
|
45
|
+
* project.godot uses C-style double-quoted strings; a project name containing
|
|
46
|
+
* `"` or `\` must be escaped or the file does not parse.
|
|
47
|
+
*/
|
|
48
|
+
export declare function escapeGodotString(value: string): string;
|
|
49
|
+
export declare function renderProjectSettings(name: string, mainScene: string): string;
|
|
50
|
+
export {};
|