summer-engine 4.5.0 → 4.8.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 -2
- package/.codex-plugin/plugin.json +5 -4
- package/.cursor-plugin/plugin.json +4 -3
- package/.factory-plugin/plugin.json +4 -3
- package/CHANGELOG.md +50 -0
- package/README.md +3 -3
- package/commands/start.md +1 -1
- package/dist/cli/commands/api.d.ts +43 -0
- package/dist/cli/commands/api.js +191 -0
- package/dist/cli/commands/login.d.ts +2 -5
- package/dist/cli/commands/login.js +10 -81
- package/dist/cli/commands/setup.js +175 -32
- package/dist/cli/commands/token.d.ts +17 -0
- package/dist/cli/commands/token.js +25 -0
- package/dist/cli/index.js +4 -0
- package/dist/core/api-client.d.ts +5 -1
- package/dist/core/api-client.js +21 -0
- package/dist/core/capabilities/doctor.d.ts +1 -1
- package/dist/core/capabilities/doctor.js +1 -1
- package/dist/core/capabilities/game-task-plan.d.ts +2 -2
- package/dist/core/capabilities/navigation/open.d.ts +3 -0
- package/dist/core/capabilities/navigation/open.js +53 -0
- package/dist/core/capabilities/projects-local.js +2 -1
- package/dist/core/capabilities/runtime-control.d.ts +2 -2
- package/dist/core/capabilities/seeing/args.d.ts +4 -4
- package/dist/core/capabilities/tool-dispatch.js +37 -0
- package/dist/core/capabilities/ui-control.d.ts +2 -2
- package/dist/core/capabilities/view-set.d.ts +11 -0
- package/dist/core/capabilities/view-set.js +15 -0
- package/dist/core/capability-skew.d.ts +8 -0
- package/dist/core/capability-skew.js +18 -0
- package/dist/core/cli-session.d.ts +25 -0
- package/dist/core/cli-session.js +97 -0
- package/dist/core/engine-self-start.d.ts +20 -0
- package/dist/core/engine-self-start.js +108 -5
- package/dist/core/oauth.d.ts +15 -2
- package/dist/core/oauth.js +26 -3
- package/dist/core/ops/areas.d.ts +1 -1
- package/dist/core/ops/areas.js +5 -3
- package/dist/core/ops/engine-answer.js +5 -2
- package/dist/core/ops/engine.js +6 -0
- package/dist/core/ops/save-files.d.ts +2 -0
- package/dist/core/ops/save-files.js +13 -0
- package/dist/core/ops/studio-page.d.ts +22 -0
- package/dist/core/ops/studio-page.js +63 -0
- package/dist/core/sign-in-link.d.ts +38 -0
- package/dist/core/sign-in-link.js +106 -0
- package/dist/core/store.js +19 -1
- package/dist/core/summer-api.d.ts +114 -0
- package/dist/core/summer-api.js +281 -0
- package/dist/installer/agent-config.d.ts +29 -0
- package/dist/installer/agent-config.js +75 -11
- package/dist/installer/agent-detect.d.ts +44 -0
- package/dist/installer/agent-detect.js +166 -0
- package/dist/installer/agent-table.d.ts +19 -0
- package/dist/installer/agent-table.js +130 -0
- package/dist/installer/setup-error.d.ts +19 -0
- package/dist/installer/setup-error.js +34 -0
- package/dist/installer/version-check.d.ts +5 -0
- package/dist/installer/version-check.js +21 -0
- package/dist/mcp/ops-surface.d.ts +10 -0
- package/dist/mcp/ops-surface.js +18 -1
- package/dist/mcp/server.js +16 -4
- package/dist/mcp/tool-results.d.ts +9 -0
- package/dist/mcp/tool-results.js +64 -0
- package/dist/mcp/tool-surface.js +7 -1
- package/dist/mcp/tools/api-tools.d.ts +50 -0
- package/dist/mcp/tools/api-tools.js +65 -0
- package/dist/mcp/tools/local-project-tools.d.ts +14 -0
- package/dist/mcp/tools/local-project-tools.js +13 -2
- package/dist/mcp/tools/navigation-tools.js +1 -0
- package/dist/mcp/tools/scene-tools.js +7 -0
- package/dist/project-memory/agent-instructions.js +11 -3
- package/gemini-extension.json +3 -2
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +15 -2
- package/library/references/mcp-tools-reference/resource.yaml +1 -1
- package/library/skills/README.md +6 -5
- package/library/skills/design-npc/SKILL.md +1 -1
- package/library/skills/design-npc/resource.yaml +2 -1
- package/library/skills/make-game/SKILL.md +4 -4
- package/library/skills/navigate-summer/SKILL.md +23 -2
- package/library/skills/navigate-summer/resource.yaml +5 -3
- package/library/skills/summer-api/SKILL.md +198 -0
- package/library/skills/summer-api/resource.yaml +38 -0
- package/library/skills/summer-assets/SKILL.md +1 -1
- package/library/skills/summer-assets/resource.yaml +3 -2
- package/library/skills/summer-router/SKILL.md +251 -0
- package/library/skills/summer-router/resource.yaml +35 -0
- package/library/tools/api-batch/resource.yaml +82 -0
- package/library/tools/api-estimate/resource.yaml +52 -0
- package/library/tools/api-job/resource.yaml +58 -0
- package/library/tools/api-models/resource.yaml +57 -0
- package/library/tools/api-run/resource.yaml +70 -0
- package/library/tools/view-set/GUIDE.md +3 -0
- package/library/tools/view-set/resource.yaml +78 -0
- package/package.json +3 -3
- package/registry/generated/aliases.json +7 -0
- package/registry/generated/counts.json +3 -3
- package/registry/generated/gemini-extension.json +3 -2
- package/registry/generated/index.json +317 -52
- package/registry/generated/marketplace.claude.json +1 -1
- package/registry/generated/plugin.claude-directory.json +4 -3
- package/registry/generated/plugin.claude.json +3 -2
- package/registry/generated/plugin.codex.json +5 -4
- package/registry/generated/plugin.cursor.json +4 -3
- package/registry/generated/plugin.factory.json +4 -3
- package/registry/generated/server.json +2 -2
- package/registry/generated/skills-index.md +6 -5
- package/registry/generated/skills-registry.json +29 -15
- package/registry/operations/legacy-names.json +30 -0
- package/registry/operations/public-catalog.json +9169 -4926
- package/library/skills/ai-characters-in-games/SKILL.md +0 -235
- package/library/skills/ai-characters-in-games/resource.yaml +0 -30
|
@@ -87,6 +87,50 @@ export function resPathProblem(value) {
|
|
|
87
87
|
return "must not contain control characters";
|
|
88
88
|
return null;
|
|
89
89
|
}
|
|
90
|
+
/** A summerengine.com Studio path (`/studio`, `/studio/games`, `/studio?tab=assets`), the
|
|
91
|
+
* only paths the engine's `studio` Navigate row opens. */
|
|
92
|
+
export function isStudioPath(path) {
|
|
93
|
+
return /^\/studio(?:[/?#]|$)/.test(path);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A Studio path opens in the Summer app's own Studio tab when the running
|
|
97
|
+
* engine advertises the `studio` Navigate destination (it loads the tab when
|
|
98
|
+
* the person has not opened it yet). Otherwise, the engine off, an older
|
|
99
|
+
* engine or a refused Navigate, null: the caller opens the browser as before.
|
|
100
|
+
*/
|
|
101
|
+
async function openInAppStudio(deps, path, url, extra) {
|
|
102
|
+
if (!isStudioPath(path))
|
|
103
|
+
return null;
|
|
104
|
+
let client;
|
|
105
|
+
try {
|
|
106
|
+
client = await deps.engine();
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
if (!advertisedNavigation(client)?.has("studio"))
|
|
112
|
+
return null;
|
|
113
|
+
const op = { op: "Navigate", target: "studio", path };
|
|
114
|
+
let receipt;
|
|
115
|
+
try {
|
|
116
|
+
receipt = await client.executeOps([op]);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
if (extractOpError(receipt))
|
|
122
|
+
return null;
|
|
123
|
+
return {
|
|
124
|
+
ok: true,
|
|
125
|
+
action: "opened",
|
|
126
|
+
url,
|
|
127
|
+
...extra,
|
|
128
|
+
op,
|
|
129
|
+
engine: { running: true, version: client.getEngineVersion?.(), navigation: true },
|
|
130
|
+
receipt,
|
|
131
|
+
hint: "Opened in the Studio tab of the Summer app, not a browser.",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
90
134
|
/** Launch the browser; a failed launch is a result, not a crash. */
|
|
91
135
|
async function launch(deps, url) {
|
|
92
136
|
try {
|
|
@@ -444,6 +488,9 @@ export async function runOpen(args, deps) {
|
|
|
444
488
|
};
|
|
445
489
|
if (print)
|
|
446
490
|
return base;
|
|
491
|
+
const inApp = await openInAppStudio(deps, resolution.url, url, { unmapped: true });
|
|
492
|
+
if (inApp)
|
|
493
|
+
return inApp;
|
|
447
494
|
const failure = await launch(deps, url);
|
|
448
495
|
if (failure)
|
|
449
496
|
return openFailed(url, failure, { unmapped: true });
|
|
@@ -479,6 +526,12 @@ export async function runOpen(args, deps) {
|
|
|
479
526
|
};
|
|
480
527
|
if (print)
|
|
481
528
|
return result;
|
|
529
|
+
if (target.web.origin !== "docs") {
|
|
530
|
+
// The app's Studio tab is signed in with the app: no login detour.
|
|
531
|
+
const inApp = await openInAppStudio(deps, path, url, { target: summary });
|
|
532
|
+
if (inApp)
|
|
533
|
+
return inApp;
|
|
534
|
+
}
|
|
482
535
|
const toOpen = needsLogin && loggedIn === false ? loginUrl : url;
|
|
483
536
|
const failure = await launch(deps, toOpen);
|
|
484
537
|
if (failure)
|
|
@@ -143,7 +143,8 @@ sessionFolder = process.cwd()) {
|
|
|
143
143
|
}
|
|
144
144
|
let client;
|
|
145
145
|
try {
|
|
146
|
-
|
|
146
|
+
// The agent asked for this game: an editor with another one switches (J16.1).
|
|
147
|
+
client = await openProjectInEditor(root, { cwd: root }, { install: input.install === true, switchOpen: true }, deps);
|
|
147
148
|
}
|
|
148
149
|
catch (error) {
|
|
149
150
|
if (error instanceof EngineInstallError)
|
|
@@ -141,18 +141,18 @@ export declare const runtimeSpawnArgsSchema: z.ZodObject<{
|
|
|
141
141
|
action: "free" | "spawn";
|
|
142
142
|
mode?: "free" | "queue_free" | undefined;
|
|
143
143
|
name?: string | undefined;
|
|
144
|
+
path?: string | undefined;
|
|
144
145
|
instance?: string | undefined;
|
|
145
146
|
scene?: string | undefined;
|
|
146
|
-
path?: string | undefined;
|
|
147
147
|
parent?: string | undefined;
|
|
148
148
|
props?: Record<string, string | number | boolean> | undefined;
|
|
149
149
|
}, {
|
|
150
150
|
action: "free" | "spawn";
|
|
151
151
|
mode?: "free" | "queue_free" | undefined;
|
|
152
152
|
name?: string | undefined;
|
|
153
|
+
path?: string | undefined;
|
|
153
154
|
instance?: string | undefined;
|
|
154
155
|
scene?: string | undefined;
|
|
155
|
-
path?: string | undefined;
|
|
156
156
|
parent?: string | undefined;
|
|
157
157
|
props?: Record<string, string | number | boolean> | undefined;
|
|
158
158
|
}>;
|
|
@@ -177,10 +177,10 @@ export declare const shotSheetArgsSchema: z.ZodObject<{
|
|
|
177
177
|
bookmark_name?: string | undefined;
|
|
178
178
|
}[];
|
|
179
179
|
scenePath?: string | undefined;
|
|
180
|
+
view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
|
|
180
181
|
compare_previous?: boolean | undefined;
|
|
181
182
|
max_size?: number | undefined;
|
|
182
183
|
save_to?: string | undefined;
|
|
183
|
-
view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
|
|
184
184
|
update_previous?: boolean | undefined;
|
|
185
185
|
aspect?: number | undefined;
|
|
186
186
|
}, {
|
|
@@ -192,10 +192,10 @@ export declare const shotSheetArgsSchema: z.ZodObject<{
|
|
|
192
192
|
bookmark_name?: string | undefined;
|
|
193
193
|
}[];
|
|
194
194
|
scenePath?: string | undefined;
|
|
195
|
+
view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
|
|
195
196
|
compare_previous?: boolean | undefined;
|
|
196
197
|
max_size?: number | undefined;
|
|
197
198
|
save_to?: string | undefined;
|
|
198
|
-
view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
|
|
199
199
|
update_previous?: boolean | undefined;
|
|
200
200
|
aspect?: number | undefined;
|
|
201
201
|
}>;
|
|
@@ -251,11 +251,11 @@ export declare const zoomArgsSchema: z.ZodObject<{
|
|
|
251
251
|
camera_position?: string | undefined;
|
|
252
252
|
camera_look_at?: string | undefined;
|
|
253
253
|
fov?: number | undefined;
|
|
254
|
+
view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
|
|
254
255
|
bookmark_name?: string | undefined;
|
|
255
256
|
region?: number[] | undefined;
|
|
256
257
|
max_size?: number | undefined;
|
|
257
258
|
save_to?: string | undefined;
|
|
258
|
-
view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
|
|
259
259
|
mark?: number | undefined;
|
|
260
260
|
reference_size?: number[] | undefined;
|
|
261
261
|
pad?: number | undefined;
|
|
@@ -264,11 +264,11 @@ export declare const zoomArgsSchema: z.ZodObject<{
|
|
|
264
264
|
camera_position?: string | undefined;
|
|
265
265
|
camera_look_at?: string | undefined;
|
|
266
266
|
fov?: number | undefined;
|
|
267
|
+
view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
|
|
267
268
|
bookmark_name?: string | undefined;
|
|
268
269
|
region?: number[] | undefined;
|
|
269
270
|
max_size?: number | undefined;
|
|
270
271
|
save_to?: string | undefined;
|
|
271
|
-
view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
|
|
272
272
|
mark?: number | undefined;
|
|
273
273
|
reference_size?: number[] | undefined;
|
|
274
274
|
pad?: number | undefined;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* as the single per-tool dispatch table so the handler bodies stop being two
|
|
17
17
|
* mirrors as well.
|
|
18
18
|
*/
|
|
19
|
+
import { viewSetOp } from "./view-set.js";
|
|
19
20
|
import { tmpdir } from "node:os";
|
|
20
21
|
import { join } from "node:path";
|
|
21
22
|
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
@@ -79,6 +80,7 @@ import { captureGame, captureScene, captureViewport } from "./capture.js";
|
|
|
79
80
|
// the scripting ones come with their op builders from ./scene-script.js.
|
|
80
81
|
import { ALIGN_DISTRIBUTE_FALLBACK, GREP_FALLBACK, NAVIGATION_PROBE_FALLBACK, RUNTIME_NODE_FALLBACK, RUNTIME_TREE_FALLBACK, SNAP_TO_SURFACE_FALLBACK, SNAPSHOT_DIFF_FALLBACK, STARCAST_FALLBACK, TEST_PLACEMENT_FALLBACK, WORLD_SNAPSHOT_FALLBACK, } from "./engine-fallbacks.js";
|
|
81
82
|
import { sendLibraryFeedback, } from "../feedback/client.js";
|
|
83
|
+
import { batchToolResult, cancelJob, checkBatch, createBatch, estimateJob, getBatch, getJob, jobToolResult, listModels, runJob, summerApiToolAnswer } from "../summer-api.js";
|
|
82
84
|
// One copy of the batch op classification and the instantiate/batch engine
|
|
83
85
|
// path, shared with the MCP face.
|
|
84
86
|
import { PLACEMENT_FIELDS, SCENE_MUTATION_OPS, SCENE_QUERY_OPS, instantiateScene, runBatch, } from "./placement-batch.js";
|
|
@@ -1296,6 +1298,11 @@ export const TOOL_DISPATCH = [
|
|
|
1296
1298
|
op.scenePath = args.scenePath;
|
|
1297
1299
|
return requireEngineSuccess(await (await ctx.engine()).executeOps([op]));
|
|
1298
1300
|
}),
|
|
1301
|
+
entry("summer_view_set", "Show the person a view, rail and focus in the editor", true, async (args, ctx) => {
|
|
1302
|
+
const frame = Array.isArray(args.frame) ? args.frame.filter((entry) => typeof entry === "string") : undefined;
|
|
1303
|
+
const op = viewSetOp({ view: str(args, "view"), rail: optStr(args, "rail"), focus: optStr(args, "focus"), frame });
|
|
1304
|
+
return requireEngineSuccess(await (await ctx.engine()).executeOps([op]));
|
|
1305
|
+
}),
|
|
1299
1306
|
entry("summer_replace_node", "Replace a node with a different scene or type and verify the saved scene", true, async (args, ctx) => {
|
|
1300
1307
|
// ONE implementation for both faces (core/capabilities/replace-node.ts):
|
|
1301
1308
|
// persisted ops + read-back of the saved .tscn.
|
|
@@ -1767,6 +1774,36 @@ export const TOOL_DISPATCH = [
|
|
|
1767
1774
|
throw new ToolResultError(result, result.hint);
|
|
1768
1775
|
return result;
|
|
1769
1776
|
}),
|
|
1777
|
+
// --- account ---
|
|
1778
|
+
entry("summer_api_models", "The Summer API's models with input and price", false, async (args) => summerApiToolAnswer(() => listModels({ ...(optStr(args, "model") ? { model: optStr(args, "model") } : {}), ...(optStr(args, "capability") ? { capability: optStr(args, "capability") } : {}) }))),
|
|
1779
|
+
entry("summer_api_estimate", "The exact price of one Summer API job; charges nothing", false, async (args) => summerApiToolAnswer(() => estimateJob(str(args, "model"), args.input ?? {}))),
|
|
1780
|
+
entry("summer_api_run", "Run one Summer API job with the Summer sign-in on this computer", false, async (args) => summerApiToolAnswer(async () => {
|
|
1781
|
+
const out = optStr(args, "out");
|
|
1782
|
+
const follow = args.wait === true || Boolean(out);
|
|
1783
|
+
const job = await runJob(str(args, "model"), args.input ?? {}, {
|
|
1784
|
+
...(optStr(args, "quoteId") ? { quoteId: optStr(args, "quoteId") } : {}),
|
|
1785
|
+
...(optStr(args, "idempotencyKey") ? { idempotencyKey: optStr(args, "idempotencyKey") } : {}),
|
|
1786
|
+
...(follow ? { waitSeconds: 30 } : {}),
|
|
1787
|
+
});
|
|
1788
|
+
return jobToolResult(job, { wait: args.wait === true, ...(out ? { out } : {}) });
|
|
1789
|
+
})),
|
|
1790
|
+
entry("summer_api_batch", "Run up to 500 Summer API jobs at once (all or none), or read a batch", false, async (args) => summerApiToolAnswer(async () => {
|
|
1791
|
+
const out = optStr(args, "out");
|
|
1792
|
+
const follow = { wait: args.wait === true, ...(out ? { out } : {}) };
|
|
1793
|
+
const batchId = optStr(args, "batchId");
|
|
1794
|
+
if (batchId)
|
|
1795
|
+
return batchToolResult(await getBatch(batchId), follow);
|
|
1796
|
+
const key = optStr(args, "idempotencyKey");
|
|
1797
|
+
const batch = await createBatch(checkBatch(Array.isArray(args.jobs) ? args.jobs : []), key ? { idempotencyKey: key } : {});
|
|
1798
|
+
return batchToolResult(batch, follow);
|
|
1799
|
+
})),
|
|
1800
|
+
entry("summer_api_job", "Read, follow, download or cancel a Summer API job", false, async (args) => summerApiToolAnswer(async () => {
|
|
1801
|
+
const jobId = str(args, "jobId");
|
|
1802
|
+
if (args.cancel === true)
|
|
1803
|
+
return { job: await cancelJob(jobId) };
|
|
1804
|
+
const out = optStr(args, "out");
|
|
1805
|
+
return jobToolResult(await getJob(jobId), { wait: args.wait === true, ...(out ? { out } : {}) });
|
|
1806
|
+
})),
|
|
1770
1807
|
// --- feedback ---
|
|
1771
1808
|
entry("summer_library_feedback", "Report library entry outcomes so Summer can fix and re-rank them", false, async (args) => {
|
|
1772
1809
|
if (!Array.isArray(args.reports) || args.reports.length === 0) {
|
|
@@ -88,16 +88,16 @@ export declare const uiActivateArgsSchema: z.ZodObject<{
|
|
|
88
88
|
button: z.ZodOptional<z.ZodString>;
|
|
89
89
|
}, "strip", z.ZodTypeAny, {
|
|
90
90
|
value?: string | number | undefined;
|
|
91
|
-
title?: string | undefined;
|
|
92
91
|
path?: string | undefined;
|
|
92
|
+
title?: string | undefined;
|
|
93
93
|
action?: "focus" | "press" | "select_tab" | "toggle" | "set_text" | "set_value" | "dismiss_dialog" | undefined;
|
|
94
94
|
button?: string | undefined;
|
|
95
95
|
index?: number | undefined;
|
|
96
96
|
submit?: boolean | undefined;
|
|
97
97
|
}, {
|
|
98
98
|
value?: string | number | undefined;
|
|
99
|
-
title?: string | undefined;
|
|
100
99
|
path?: string | undefined;
|
|
100
|
+
title?: string | undefined;
|
|
101
101
|
action?: "focus" | "press" | "select_tab" | "toggle" | "set_text" | "set_value" | "dismiss_dialog" | undefined;
|
|
102
102
|
button?: string | undefined;
|
|
103
103
|
index?: number | undefined;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* summer_view_set / engine.view_set (PRODUCT-SPEC J17): an outside agent shows
|
|
3
|
+
* the person what it works on, through the engine's SetView op: the view, the
|
|
4
|
+
* right rail, what to focus, and the nodes to frame the camera on.
|
|
5
|
+
*/
|
|
6
|
+
export declare function viewSetOp(args: {
|
|
7
|
+
view: string;
|
|
8
|
+
rail?: string;
|
|
9
|
+
focus?: string;
|
|
10
|
+
frame?: string[];
|
|
11
|
+
}): Record<string, unknown>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* summer_view_set / engine.view_set (PRODUCT-SPEC J17): an outside agent shows
|
|
3
|
+
* the person what it works on, through the engine's SetView op: the view, the
|
|
4
|
+
* right rail, what to focus, and the nodes to frame the camera on.
|
|
5
|
+
*/
|
|
6
|
+
export function viewSetOp(args) {
|
|
7
|
+
const op = { op: "SetView", view: args.view };
|
|
8
|
+
if (args.rail)
|
|
9
|
+
op.rail = args.rail;
|
|
10
|
+
if (args.focus)
|
|
11
|
+
op.focus = args.focus;
|
|
12
|
+
if (args.frame?.length)
|
|
13
|
+
op.frame = args.frame;
|
|
14
|
+
return op;
|
|
15
|
+
}
|
|
@@ -81,6 +81,9 @@ export interface EngineCapabilities {
|
|
|
81
81
|
* destination ids this build can open. Absent = engine predates the op;
|
|
82
82
|
* summer_open then falls back to the legacy per-surface ops it can map. */
|
|
83
83
|
navigation?: EngineNavigationCapabilities;
|
|
84
|
+
/** The Studio page ops this app answers itself through its own Studio tab
|
|
85
|
+
* (POST /api/studio). Absent = send them to Summer's server. */
|
|
86
|
+
studioPage?: EngineStudioPageCapabilities;
|
|
84
87
|
/** Launch postures this build ENFORCES: "focus" (always), "background"
|
|
85
88
|
* (`--summer-background`), "offscreen" (`--summer-offscreen` /
|
|
86
89
|
* `--summer-verify`). Non-macOS builds advertise ["focus"] only. Absent =
|
|
@@ -89,6 +92,11 @@ export interface EngineCapabilities {
|
|
|
89
92
|
* snake_case spelling too. */
|
|
90
93
|
launchPostures?: string[];
|
|
91
94
|
}
|
|
95
|
+
export interface EngineStudioPageCapabilities {
|
|
96
|
+
version?: number;
|
|
97
|
+
path: string;
|
|
98
|
+
ops: string[];
|
|
99
|
+
}
|
|
92
100
|
export interface EngineNavigationCapabilities {
|
|
93
101
|
version?: number;
|
|
94
102
|
targets: EngineNavigationTarget[];
|
|
@@ -49,6 +49,8 @@ export const CLI_KNOWN_OP_NEEDS = [
|
|
|
49
49
|
// engine's one-table op; the other three are the legacy
|
|
50
50
|
// fallbacks on engines that predate it.
|
|
51
51
|
"Navigate", "OpenResource", "FocusDock", "RevealInFileSystem",
|
|
52
|
+
// engine.view_set: the frame's view, rail, focus and frame (core/capabilities/view-set.ts)
|
|
53
|
+
"SetView",
|
|
52
54
|
// Summer Apps editor docks (summer_app_dock: core/capabilities/apps.ts)
|
|
53
55
|
"OpenAppDock", "CloseAppDock", "ListAppDocks",
|
|
54
56
|
// summer_enable_plugin (core/capabilities/enable-plugin.ts)
|
|
@@ -188,11 +190,27 @@ export function parseEngineCapabilities(raw) {
|
|
|
188
190
|
const navigation = parseNavigation(record.navigation);
|
|
189
191
|
if (navigation)
|
|
190
192
|
out.navigation = navigation;
|
|
193
|
+
const studioPage = parseStudioPage(record.studioPage);
|
|
194
|
+
if (studioPage)
|
|
195
|
+
out.studioPage = studioPage;
|
|
191
196
|
const launchPostures = stringList(record.launchPostures) ?? stringList(record.launch_postures);
|
|
192
197
|
if (launchPostures)
|
|
193
198
|
out.launchPostures = launchPostures;
|
|
194
199
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
195
200
|
}
|
|
201
|
+
function parseStudioPage(raw) {
|
|
202
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
203
|
+
return undefined;
|
|
204
|
+
const record = raw;
|
|
205
|
+
const ops = stringList(record.ops);
|
|
206
|
+
if (!ops?.length)
|
|
207
|
+
return undefined;
|
|
208
|
+
return {
|
|
209
|
+
...(typeof record.version === "number" ? { version: record.version } : {}),
|
|
210
|
+
path: typeof record.path === "string" && record.path.startsWith("/api/") ? record.path : "/api/studio",
|
|
211
|
+
ops,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
196
214
|
function parseNavigation(raw) {
|
|
197
215
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
198
216
|
return undefined;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser sign-in for the Summer account (the `cli_session` flow): the
|
|
3
|
+
* person opens `{gateway}/login?cli_session=<id>`, signs in, and approves;
|
|
4
|
+
* this polls `{gateway}/api/auth/cli-login?session=<id>` until the gateway
|
|
5
|
+
* hands over the CLI token, then saves it to ~/.summer (the files the desktop
|
|
6
|
+
* engine and every tool read). `summer login` and the MCP's sign-in link
|
|
7
|
+
* (core/sign-in-link.ts) share it.
|
|
8
|
+
*/
|
|
9
|
+
import { type LoginSession } from "./auth.js";
|
|
10
|
+
export declare const POLL_TIMEOUT_MS = 900000;
|
|
11
|
+
export interface CliSessionPollDependencies {
|
|
12
|
+
fetch: typeof fetch;
|
|
13
|
+
sleep: (ms: number) => Promise<void>;
|
|
14
|
+
now: () => number;
|
|
15
|
+
log: (message: string) => void;
|
|
16
|
+
/** Stop waiting (another sign-in already saved a token, the server is closing). */
|
|
17
|
+
cancelled?: () => boolean | Promise<boolean>;
|
|
18
|
+
}
|
|
19
|
+
/** The page the person opens to sign in. */
|
|
20
|
+
export declare function cliSessionLoginUrl(gatewayUrl: string, sessionId: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Wait for the browser sign-in of `sessionId`, save it, and return it.
|
|
23
|
+
* Throws on a rejected request, an invalid token, a timeout, or `cancelled`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function pollCliSession(gatewayUrl: string, sessionId: string, deps: CliSessionPollDependencies): Promise<LoginSession>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser sign-in for the Summer account (the `cli_session` flow): the
|
|
3
|
+
* person opens `{gateway}/login?cli_session=<id>`, signs in, and approves;
|
|
4
|
+
* this polls `{gateway}/api/auth/cli-login?session=<id>` until the gateway
|
|
5
|
+
* hands over the CLI token, then saves it to ~/.summer (the files the desktop
|
|
6
|
+
* engine and every tool read). `summer login` and the MCP's sign-in link
|
|
7
|
+
* (core/sign-in-link.ts) share it.
|
|
8
|
+
*/
|
|
9
|
+
import { saveLoginSession } from "./auth.js";
|
|
10
|
+
import { summerRequestHeaders } from "./agent-host.js";
|
|
11
|
+
const POLL_INTERVAL_MS = 2000;
|
|
12
|
+
// One generous window on ONE session id. First-time users may need to create an
|
|
13
|
+
// account and confirm an email before they can approve the CLI. The gateway
|
|
14
|
+
// never expires a pending session (the completed payload waits under this id
|
|
15
|
+
// for pickup), so the worst mistake would be rotating ids mid-wait — the user
|
|
16
|
+
// would approve the original browser tab while we poll a different session.
|
|
17
|
+
export const POLL_TIMEOUT_MS = 900000;
|
|
18
|
+
const HEARTBEAT_MS = 30000;
|
|
19
|
+
/** The gateway answered with a terminal 4xx — retrying the same URL cannot help. */
|
|
20
|
+
class LoginRejectedError extends Error {
|
|
21
|
+
}
|
|
22
|
+
/** The page the person opens to sign in. */
|
|
23
|
+
export function cliSessionLoginUrl(gatewayUrl, sessionId) {
|
|
24
|
+
return `${gatewayUrl}/login?cli_session=${encodeURIComponent(sessionId)}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Wait for the browser sign-in of `sessionId`, save it, and return it.
|
|
28
|
+
* Throws on a rejected request, an invalid token, a timeout, or `cancelled`.
|
|
29
|
+
*/
|
|
30
|
+
export async function pollCliSession(gatewayUrl, sessionId, deps) {
|
|
31
|
+
const loginUrl = cliSessionLoginUrl(gatewayUrl, sessionId);
|
|
32
|
+
const pollUrl = `${gatewayUrl}/api/auth/cli-login?session=${encodeURIComponent(sessionId)}`;
|
|
33
|
+
const startTime = deps.now();
|
|
34
|
+
let lastHeartbeat = startTime;
|
|
35
|
+
let lastError = null;
|
|
36
|
+
while (deps.now() - startTime < POLL_TIMEOUT_MS) {
|
|
37
|
+
await deps.sleep(POLL_INTERVAL_MS);
|
|
38
|
+
if (deps.cancelled && (await deps.cancelled())) {
|
|
39
|
+
throw new Error("The sign-in wait was cancelled.");
|
|
40
|
+
}
|
|
41
|
+
if (deps.now() - lastHeartbeat >= HEARTBEAT_MS) {
|
|
42
|
+
lastHeartbeat = deps.now();
|
|
43
|
+
deps.log('Still waiting — finish signing in (or creating your account) in the browser, then click "Yes, Sign In". Same link: ' +
|
|
44
|
+
loginUrl +
|
|
45
|
+
(lastError ? ` (last error: ${lastError})` : ""));
|
|
46
|
+
}
|
|
47
|
+
// Only the network round-trip is retried. A completed session is handled
|
|
48
|
+
// OUTSIDE this try: saveLoginSession rejecting the token (mismatched sub,
|
|
49
|
+
// non-CLI token type, already expired) is terminal — the gateway will
|
|
50
|
+
// return the same payload forever, so polling on would only bury the
|
|
51
|
+
// recovery message under a 15-minute timeout.
|
|
52
|
+
let completed = null;
|
|
53
|
+
try {
|
|
54
|
+
const res = await deps.fetch(pollUrl, {
|
|
55
|
+
headers: summerRequestHeaders({ surface: "cli" }),
|
|
56
|
+
signal: AbortSignal.timeout(5000),
|
|
57
|
+
});
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
if (res.status === 503) {
|
|
60
|
+
lastError =
|
|
61
|
+
"The login service is unavailable (Redis/auth is not configured).";
|
|
62
|
+
}
|
|
63
|
+
else if (res.status >= 500) {
|
|
64
|
+
lastError = `The login service failed (${res.status}).`;
|
|
65
|
+
}
|
|
66
|
+
else if (res.status === 429 || res.status === 408) {
|
|
67
|
+
lastError = `The login service asked us to slow down (${res.status}).`;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// 401/403/404 and the other 4xx are terminal: this URL will not
|
|
71
|
+
// start succeeding on its own.
|
|
72
|
+
throw new LoginRejectedError(`The login request was rejected (${res.status}). Recovery: check that gateway.url (${gatewayUrl}) is the Summer gateway you meant, then run "summer login" again.`);
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const data = (await res.json());
|
|
77
|
+
if (data.status === "pending")
|
|
78
|
+
continue;
|
|
79
|
+
if (data.status === "complete" && data.token) {
|
|
80
|
+
completed = { token: data.token, user: data.user, scopes: data.scopes };
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
lastError = "The login service returned an incomplete authentication result.";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
if (err instanceof LoginRejectedError)
|
|
88
|
+
throw err;
|
|
89
|
+
lastError = err instanceof Error ? err.message : "Network error";
|
|
90
|
+
}
|
|
91
|
+
if (completed) {
|
|
92
|
+
await saveLoginSession(completed);
|
|
93
|
+
return completed;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`Login timed out after ${Math.round(POLL_TIMEOUT_MS / 60000)} minutes.${lastError ? ` Last error: ${lastError}` : ""} Recovery: run "summer login" again and complete "Yes, Sign In" in the browser tab it opens.`);
|
|
97
|
+
}
|
|
@@ -20,7 +20,20 @@ export declare const SELF_START_RECOVERY: {
|
|
|
20
20
|
closedEarly: (root: string) => string;
|
|
21
21
|
launchFailed: (code: string) => string;
|
|
22
22
|
autoLaunchOff: (root: string) => string;
|
|
23
|
+
switchUnsupported: (open: string, wanted: string) => string;
|
|
24
|
+
switchFailed: (wanted: string, reason: string) => string;
|
|
23
25
|
};
|
|
26
|
+
/** The engine operation that makes a running editor open another game in
|
|
27
|
+
* the same window (the editor's own switch, J16.1). */
|
|
28
|
+
export declare const SWITCH_PROJECT_OP = "SwitchProject";
|
|
29
|
+
/** How long the new editor has to register after a switch: it boots first
|
|
30
|
+
* and registers about 5 s after the operation returns locally, about 14 s
|
|
31
|
+
* on CI (J17), so the next tool never meets a half-open game. */
|
|
32
|
+
export declare const SWITCH_REGISTER_MS = 30000;
|
|
33
|
+
/** While a game opens, the agent hears "Opening <game>…" this often. */
|
|
34
|
+
export declare const OPEN_PROGRESS_EVERY_MS = 3000;
|
|
35
|
+
/** Says `message` now and every OPEN_PROGRESS_EVERY_MS until the returned stop is called. */
|
|
36
|
+
export declare function keepSaying(progress: SelfStartDeps["progress"], message: string): () => void;
|
|
24
37
|
export interface SelfStartDeps {
|
|
25
38
|
launch?: typeof launchEditor;
|
|
26
39
|
connect?: (selection: EngineSelection | undefined) => Promise<EngineApiClient>;
|
|
@@ -29,6 +42,10 @@ export interface SelfStartDeps {
|
|
|
29
42
|
waitMs?: number;
|
|
30
43
|
env?: NodeJS.ProcessEnv;
|
|
31
44
|
now?: () => number;
|
|
45
|
+
/** A progress line for the agent while it waits (MCP notifications/progress). */
|
|
46
|
+
progress?: (message: string) => void;
|
|
47
|
+
/** Tests: how long a switch waits for the new editor (default SWITCH_REGISTER_MS). */
|
|
48
|
+
switchWaitMs?: number;
|
|
32
49
|
}
|
|
33
50
|
/** Test-only: forget the last answer only (withEngine's immediate retry window). */
|
|
34
51
|
export declare function _resetRecentForTests(): void;
|
|
@@ -48,6 +65,9 @@ export interface OpenProjectOptions {
|
|
|
48
65
|
install?: boolean;
|
|
49
66
|
/** The error to build answers from (a failed connect), or the op's own. */
|
|
50
67
|
notRunning?: EngineNotRunningError;
|
|
68
|
+
/** An editor with another game open switches to this one in the same
|
|
69
|
+
* window instead of refusing (projects.open_local: the agent asked). */
|
|
70
|
+
switchOpen?: boolean;
|
|
51
71
|
}
|
|
52
72
|
/**
|
|
53
73
|
* Open `root` in the editor and return a connected client: launch it unless
|