summer-engine 2.3.0 → 2.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 +20 -5
- package/.codex-plugin/plugin.json +15 -5
- package/.cursor-plugin/plugin.json +15 -5
- package/.factory-plugin/plugin.json +2 -2
- package/.opencode/INSTALL.md +1 -1
- package/CHANGELOG.md +27 -6
- package/CLAUDE.md +2 -2
- package/GEMINI.md +2 -3
- package/README.md +51 -30
- package/_persona/summer/SKILL.md +5 -6
- package/commands/gameskill.md +103 -0
- package/dist/bin/summer.js +12 -4
- package/dist/commands/create.js +2 -0
- package/dist/commands/doctor.js +71 -1
- package/dist/commands/install.d.ts +17 -0
- package/dist/commands/install.js +63 -4
- package/dist/commands/memory.d.ts +14 -0
- package/dist/commands/memory.js +196 -0
- package/dist/commands/setup.js +8 -2
- package/dist/commands/skills.js +90 -6
- package/dist/commands/status.js +3 -0
- package/dist/lib/agent-config.d.ts +2 -2
- package/dist/lib/agent-config.js +134 -7
- package/dist/lib/api-client.d.ts +13 -0
- package/dist/lib/api-client.js +63 -26
- package/dist/lib/async-op-lifecycle.d.ts +60 -0
- package/dist/lib/async-op-lifecycle.js +83 -0
- package/dist/lib/project-memory.d.ts +33 -0
- package/dist/lib/project-memory.js +220 -0
- package/dist/lib/skills-registry.d.ts +134 -61
- package/dist/lib/skills-registry.js +102 -4
- package/dist/lib/version-check.d.ts +106 -0
- package/dist/lib/version-check.js +338 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +81 -0
- package/dist/mcp/tools/debug-tools.js +32 -2
- package/dist/mcp/tools/project-tools.js +18 -6
- package/dist/mcp/tools/with-engine.d.ts +14 -0
- package/dist/mcp/tools/with-engine.js +52 -7
- package/gemini-extension.json +2 -2
- package/hooks/pre-commit-doctor.sh +0 -0
- package/hooks/run-hook.cmd +0 -0
- package/hooks/session-start.sh +0 -0
- package/package.json +6 -4
- package/skills/2d-assets/character-portrait/SKILL.md +1 -1
- package/skills/2d-assets/concept-art/SKILL.md +1 -1
- package/skills/2d-assets/create-asset-sheet/SKILL.md +163 -0
- package/skills/2d-assets/instantiate-asset-pack/SKILL.md +245 -0
- package/skills/2d-assets/pixel-art/SKILL.md +1 -1
- package/skills/2d-assets/skybox-panorama/SKILL.md +1 -1
- package/skills/2d-assets/sprite-sheet/SKILL.md +1 -1
- package/skills/2d-assets/tileable-texture/SKILL.md +1 -1
- package/skills/2d-assets/ui-graphics/SKILL.md +1 -1
- package/skills/2d-assets/use-widget-asset/SKILL.md +217 -0
- package/skills/3d-assets/character-model/SKILL.md +1 -1
- package/skills/3d-assets/organic-model/SKILL.md +1 -1
- package/skills/3d-assets/prop-model/SKILL.md +1 -1
- package/skills/3d-assets/vehicle-model/SKILL.md +1 -1
- package/skills/README.md +37 -30
- package/skills/ai-and-npcs/design-npc/SKILL.md +6 -6
- package/skills/animation/animation-tree/SKILL.md +2 -2
- package/skills/animation/facial-and-lipsync/SKILL.md +1 -1
- package/skills/animation/generate-motion/SKILL.md +1 -1
- package/skills/animation/procedural-animation/SKILL.md +2 -2
- package/skills/audio/adaptive-music/SKILL.md +1 -1
- package/skills/audio/audio-direction/SKILL.md +5 -5
- package/skills/audio/sound-effect/SKILL.md +2 -2
- package/skills/audio/voice-line/SKILL.md +25 -3
- package/skills/character-controllers/fps-controller/SKILL.md +5 -5
- package/skills/debugging/debug/SKILL.md +39 -1
- package/skills/deployment/export-and-ship/SKILL.md +4 -4
- package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +170 -0
- package/skills/gameplay-mechanics/design-mechanic/SKILL.md +8 -8
- package/skills/level-design/design-level/SKILL.md +5 -5
- package/skills/level-design/scene-to-level/SKILL.md +125 -0
- package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +4 -4
- package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +4 -4
- package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +7 -7
- package/skills/performance/tune-performance/SKILL.md +5 -5
- package/skills/rendering-and-lighting/3d-lighting/SKILL.md +1 -1
- package/skills/rendering-and-lighting/art-direction/SKILL.md +5 -5
- package/skills/scene-and-project/brainstorm-game/SKILL.md +4 -4
- package/skills/scene-and-project/play/SKILL.md +1 -1
- package/skills/scene-and-project/scene-composition/SKILL.md +30 -1
- package/skills/scripting-patterns/gdscript-patterns/SKILL.md +53 -0
- package/skills/ui-and-ux/ui-basics/SKILL.md +1 -1
- package/skills/video/animated-loop/SKILL.md +1 -1
- package/skills/video/cinematic-cutscene/SKILL.md +1 -1
- package/skills/video/trailer-shot/SKILL.md +1 -1
- package/skills/visual-effects/game-feel/SKILL.md +6 -6
- package/skills/workflow/brainstorming/SKILL.md +164 -0
- package/skills/workflow/brainstorming/scripts/frame-template.html +214 -0
- package/skills/workflow/brainstorming/scripts/helper.js +88 -0
- package/skills/workflow/brainstorming/scripts/server.cjs +354 -0
- package/skills/workflow/brainstorming/scripts/start-server.sh +148 -0
- package/skills/workflow/brainstorming/scripts/stop-server.sh +56 -0
- package/skills/workflow/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/skills/workflow/brainstorming/visual-companion.md +287 -0
- package/skills/workflow/debugging-game-feel/SKILL.md +174 -0
- package/skills/workflow/diagnosing-perf-regressions/SKILL.md +190 -0
- package/skills/workflow/dispatching-parallel-agents/SKILL.md +182 -0
- package/skills/workflow/gameskill/SKILL.md +111 -0
- package/skills/workflow/investigating-bugs/CREATION-LOG.md +119 -0
- package/skills/workflow/investigating-bugs/SKILL.md +296 -0
- package/skills/workflow/investigating-bugs/condition-based-waiting-example.ts +158 -0
- package/skills/workflow/investigating-bugs/condition-based-waiting.md +115 -0
- package/skills/workflow/investigating-bugs/defense-in-depth.md +122 -0
- package/skills/workflow/investigating-bugs/find-polluter.sh +63 -0
- package/skills/workflow/investigating-bugs/root-cause-tracing.md +169 -0
- package/skills/workflow/investigating-bugs/test-academic.md +14 -0
- package/skills/workflow/investigating-bugs/test-pressure-1.md +58 -0
- package/skills/workflow/investigating-bugs/test-pressure-2.md +68 -0
- package/skills/workflow/investigating-bugs/test-pressure-3.md +69 -0
- package/skills/workflow/playtesting-a-feature/SKILL.md +173 -0
- package/skills/{_meta → workflow}/skill-create/SKILL.md +32 -22
- package/skills/{_meta → workflow}/skill-improve/SKILL.md +9 -10
- package/skills/{_meta → workflow}/skill-test/SKILL.md +12 -12
- package/skills/{_meta → workflow}/using-summer/SKILL.md +5 -2
- package/skills/workflow/verification-before-completion/SKILL.md +139 -0
- package/skills/workflow/writing-plans/SKILL.md +152 -0
- package/skills/workflow/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/skills/workflow/writing-skills/SKILL.md +655 -0
- package/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -0
- package/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/skills/workflow/writing-skills/graphviz-conventions.dot +172 -0
- package/skills/workflow/writing-skills/persuasion-principles.md +187 -0
- package/skills/workflow/writing-skills/render-graphs.js +168 -0
- package/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -0
- package/dist/commands/assets.d.ts +0 -2
- package/dist/commands/assets.js +0 -189
- package/dist/commands/debug.d.ts +0 -2
- package/dist/commands/debug.js +0 -127
- package/dist/commands/doctor.test.d.ts +0 -1
- package/dist/commands/doctor.test.js +0 -29
- package/dist/commands/project.d.ts +0 -2
- package/dist/commands/project.js +0 -170
- package/dist/commands/scene.d.ts +0 -2
- package/dist/commands/scene.js +0 -290
- package/dist/lib/agent-config.test.d.ts +0 -1
- package/dist/lib/agent-config.test.js +0 -80
- package/dist/lib/format.test.d.ts +0 -1
- package/dist/lib/format.test.js +0 -36
- package/dist/lib/plugin-manifests.test.d.ts +0 -1
- package/dist/lib/plugin-manifests.test.js +0 -134
- package/dist/lib/with-engine.d.ts +0 -14
- package/dist/lib/with-engine.js +0 -109
- package/skills/_shared/collaborative-protocol.md +0 -50
- package/skills/_shared/gd-style.md +0 -121
- package/skills/_shared/godot-version.md +0 -32
- package/skills/_shared/mcp-tools-reference.md +0 -133
- package/skills/_shared/summer-folder.md +0 -44
- package/skills/_shared/template-registry.md +0 -40
- package/skills/_tests/runner.md +0 -90
- package/skills/_tests/specs/3d-lighting.md +0 -19
- package/skills/_tests/specs/art-direction.md +0 -115
- package/skills/_tests/specs/asset-strategy.md +0 -19
- package/skills/_tests/specs/audio-direction.md +0 -123
- package/skills/_tests/specs/brainstorm-game.md +0 -99
- package/skills/_tests/specs/debug.md +0 -94
- package/skills/_tests/specs/design-level.md +0 -113
- package/skills/_tests/specs/design-mechanic.md +0 -112
- package/skills/_tests/specs/design-npc.md +0 -143
- package/skills/_tests/specs/export-and-ship.md +0 -114
- package/skills/_tests/specs/fps-controller.md +0 -113
- package/skills/_tests/specs/gdscript-patterns.md +0 -19
- package/skills/_tests/specs/host-authoritative-state.md +0 -104
- package/skills/_tests/specs/make-game.md +0 -19
- package/skills/_tests/specs/peer-to-peer-multiplayer.md +0 -99
- package/skills/_tests/specs/play.md +0 -88
- package/skills/_tests/specs/scene-composition.md +0 -19
- package/skills/_tests/specs/setup-multiplayer.md +0 -106
- package/skills/_tests/specs/tune-performance.md +0 -104
- package/skills/_tests/specs/ui-basics.md +0 -19
- package/skills/_tests/specs/vfx.md +0 -116
- package/skills/catalog.yaml +0 -991
package/dist/commands/skills.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, cpSync, writeFileSync, } from "fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, cpSync, rmSync, writeFileSync, readdirSync, copyFileSync, } from "fs";
|
|
3
3
|
import { join, dirname } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import { homedir, platform } from "os";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
6
7
|
import { AGENT_CLIENTS, SKILL_REGISTRY, } from "../lib/skills-registry.js";
|
|
7
8
|
import { tildeify } from "../lib/format.js";
|
|
9
|
+
import { writeSkillMarker } from "../lib/version-check.js";
|
|
10
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
11
|
+
const { version: cliVersion } = requireFromHere("../../package.json");
|
|
8
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
13
|
// Resolve skills dir: from dist/commands/skills.js -> ../../skills
|
|
10
14
|
const skillsDir = join(__dirname, "..", "..", "skills");
|
|
15
|
+
// Resolve commands dir: same parent. Used by Claude Code installs to also copy
|
|
16
|
+
// slash commands (e.g. /gameskill) to ~/.claude/commands/. Other agents don't
|
|
17
|
+
// have an equivalent today, so the copy is gated to claude-code.
|
|
18
|
+
const commandsDir = join(__dirname, "..", "..", "commands");
|
|
11
19
|
const SKILL_SCOPES = ["user", "project"];
|
|
12
20
|
function getBuiltinSkills() {
|
|
13
21
|
const skills = [];
|
|
@@ -126,6 +134,10 @@ function agentLabel(agent) {
|
|
|
126
134
|
return "Roo Code";
|
|
127
135
|
case "gemini":
|
|
128
136
|
return "Gemini CLI";
|
|
137
|
+
case "github-copilot":
|
|
138
|
+
return "GitHub Copilot CLI";
|
|
139
|
+
case "vscode-copilot":
|
|
140
|
+
return "GitHub Copilot in VS Code";
|
|
129
141
|
case "opencode":
|
|
130
142
|
return "OpenCode";
|
|
131
143
|
case "summer":
|
|
@@ -180,6 +192,14 @@ function resolveInstallLocation(agent, scope) {
|
|
|
180
192
|
kind: "gemini-skill-dir",
|
|
181
193
|
path: join(homedir(), ".gemini", "extensions", "summer-engine", "skills"),
|
|
182
194
|
};
|
|
195
|
+
case "github-copilot":
|
|
196
|
+
case "vscode-copilot":
|
|
197
|
+
return {
|
|
198
|
+
kind: "skill-dir",
|
|
199
|
+
path: scope === "user"
|
|
200
|
+
? join(homedir(), ".copilot", "skills")
|
|
201
|
+
: join(process.cwd(), ".github", "skills"),
|
|
202
|
+
};
|
|
183
203
|
case "opencode":
|
|
184
204
|
return {
|
|
185
205
|
kind: "opencode-skill-dir",
|
|
@@ -244,13 +264,13 @@ function printAvailableSkillNames() {
|
|
|
244
264
|
console.log(` ${skill.name}`);
|
|
245
265
|
}
|
|
246
266
|
}
|
|
247
|
-
function installSkill(skill, agent, location) {
|
|
267
|
+
function installSkill(skill, agent, location, options) {
|
|
248
268
|
if (!skill.clients.includes(agent)) {
|
|
249
269
|
die(`${skill.name} does not support ${agentLabel(agent)}.`);
|
|
250
270
|
}
|
|
251
271
|
switch (location.kind) {
|
|
252
272
|
case "skill-dir":
|
|
253
|
-
return copySkillDirectory(skill, location.path);
|
|
273
|
+
return copySkillDirectory(skill, location.path, options);
|
|
254
274
|
case "cursor-rule-dir":
|
|
255
275
|
return writeCursorRule(skill, location.path);
|
|
256
276
|
case "windsurf-rule-file":
|
|
@@ -263,13 +283,18 @@ function installSkill(skill, agent, location) {
|
|
|
263
283
|
return writeOpencodeSkill(skill, location.path);
|
|
264
284
|
}
|
|
265
285
|
}
|
|
266
|
-
function copySkillDirectory(skill, targetDir) {
|
|
286
|
+
function copySkillDirectory(skill, targetDir, options) {
|
|
267
287
|
const src = getSkillPath(skill.name);
|
|
268
288
|
if (!src)
|
|
269
289
|
die(`Skill files missing: ${skill.name}`);
|
|
270
290
|
mkdirSync(targetDir, { recursive: true });
|
|
271
291
|
const dest = join(targetDir, skill.name);
|
|
272
|
-
|
|
292
|
+
if (options.force && existsSync(dest)) {
|
|
293
|
+
// Wipe stale skill content so re-installs overwrite cleanly even if files
|
|
294
|
+
// were renamed or removed upstream.
|
|
295
|
+
rmSync(dest, { recursive: true, force: true });
|
|
296
|
+
}
|
|
297
|
+
cpSync(src, dest, { recursive: true, force: true });
|
|
273
298
|
return { action: "Installed", path: dest };
|
|
274
299
|
}
|
|
275
300
|
function writeCursorRule(skill, rulesDir) {
|
|
@@ -400,6 +425,7 @@ skillsCommand
|
|
|
400
425
|
.option("--scope <scope>", "Install scope: user or project")
|
|
401
426
|
.option("--as-claude-skill", "Legacy alias for --agent claude-code")
|
|
402
427
|
.option("--as-cursor-skill", "Legacy alias for --agent cursor")
|
|
428
|
+
.option("--force", "Overwrite existing skill files (wipe stale skill dirs before copying)")
|
|
403
429
|
.action((name, opts) => {
|
|
404
430
|
const agent = resolveAgent(opts);
|
|
405
431
|
const scope = resolveScope(agent, opts);
|
|
@@ -410,11 +436,69 @@ skillsCommand
|
|
|
410
436
|
return;
|
|
411
437
|
}
|
|
412
438
|
for (const skill of skills) {
|
|
413
|
-
const result = installSkill(skill, agent, location
|
|
439
|
+
const result = installSkill(skill, agent, location, {
|
|
440
|
+
force: Boolean(opts.force),
|
|
441
|
+
});
|
|
414
442
|
console.log(` ${result.action} ${skill.name} -> ${result.path}`);
|
|
415
443
|
}
|
|
444
|
+
// Claude Code: also install slash commands (`tools/summer-cli/commands/*.md`)
|
|
445
|
+
// into `~/.claude/commands/`. Other agents don't have an equivalent today.
|
|
446
|
+
if (agent === "claude-code") {
|
|
447
|
+
const commandResults = installClaudeCommands(scope, Boolean(opts.force));
|
|
448
|
+
for (const r of commandResults) {
|
|
449
|
+
console.log(` ${r.action} command ${r.name} -> ${r.path}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
writeMarkerForLocation(location);
|
|
416
453
|
printInstallSummary(skills.length, agent, scope, location);
|
|
417
454
|
});
|
|
455
|
+
/**
|
|
456
|
+
* Copy every `.md` file in `tools/summer-cli/commands/` to the user's
|
|
457
|
+
* `~/.claude/commands/` directory so the slash commands (e.g. `/gameskill`)
|
|
458
|
+
* are available on every machine that runs `summer skills install`.
|
|
459
|
+
*
|
|
460
|
+
* Project-scope installs go to `<cwd>/.claude/commands/`. Returns one result
|
|
461
|
+
* per command file copied. Silent no-op if no commands directory exists in
|
|
462
|
+
* the package (older summer-cli releases didn't ship one).
|
|
463
|
+
*/
|
|
464
|
+
function installClaudeCommands(scope, force) {
|
|
465
|
+
if (!existsSync(commandsDir))
|
|
466
|
+
return [];
|
|
467
|
+
const root = scope === "user" ? homedir() : process.cwd();
|
|
468
|
+
const targetDir = join(root, ".claude", "commands");
|
|
469
|
+
mkdirSync(targetDir, { recursive: true });
|
|
470
|
+
const results = [];
|
|
471
|
+
for (const entry of readdirSync(commandsDir)) {
|
|
472
|
+
if (!entry.endsWith(".md"))
|
|
473
|
+
continue;
|
|
474
|
+
const src = join(commandsDir, entry);
|
|
475
|
+
const dest = join(targetDir, entry);
|
|
476
|
+
if (existsSync(dest) && !force) {
|
|
477
|
+
results.push({ action: "Kept", name: entry, path: dest });
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
copyFileSync(src, dest);
|
|
481
|
+
results.push({ action: "Installed", name: entry, path: dest });
|
|
482
|
+
}
|
|
483
|
+
return results;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Drop a `.summer-version` marker into the install dir so doctor's
|
|
487
|
+
* `skills-version-stale` check can detect drift later. Windsurf writes a
|
|
488
|
+
* single rules file with no surrounding dir, so we skip it there.
|
|
489
|
+
*/
|
|
490
|
+
function writeMarkerForLocation(location) {
|
|
491
|
+
if (location.kind === "windsurf-rule-file")
|
|
492
|
+
return;
|
|
493
|
+
try {
|
|
494
|
+
mkdirSync(location.path, { recursive: true });
|
|
495
|
+
writeSkillMarker(location.path, cliVersion);
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
// Marker is purely informational. If the FS rejects it (e.g. permission
|
|
499
|
+
// surprises on locked-down corp machines) we don't want to fail the install.
|
|
500
|
+
}
|
|
501
|
+
}
|
|
418
502
|
skillsCommand
|
|
419
503
|
.command("info <name>")
|
|
420
504
|
.description("Show skill description and preview")
|
package/dist/commands/status.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { getAuthToken, getUserInfo } from "../lib/auth.js";
|
|
3
3
|
import { getApiToken, getApiPort, checkEngineHealth } from "../lib/engine.js";
|
|
4
|
+
import { getProjectMemorySummary } from "../lib/project-memory.js";
|
|
5
|
+
import { formatStatusMemoryLine } from "./memory.js";
|
|
4
6
|
export const statusCommand = new Command("status")
|
|
5
7
|
.description("Check Summer Engine status, connection, and auth state")
|
|
6
8
|
.action(async () => {
|
|
@@ -36,6 +38,7 @@ export const statusCommand = new Command("status")
|
|
|
36
38
|
}
|
|
37
39
|
if (health.project_path) {
|
|
38
40
|
console.log(` Path: ${health.project_path}`);
|
|
41
|
+
console.log(` ${formatStatusMemoryLine(getProjectMemorySummary(health.project_path))}`);
|
|
39
42
|
}
|
|
40
43
|
if (health.scene) {
|
|
41
44
|
console.log(` Scene: ${health.scene}`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const SUMMER_MCP_SERVER_NAME = "summer-engine";
|
|
2
|
-
export declare const supportedAgents: readonly ["codex", "claude-code", "cursor", "windsurf", "cline", "roo-code", "gemini", "opencode"];
|
|
2
|
+
export declare const supportedAgents: readonly ["codex", "claude-code", "cursor", "windsurf", "cline", "roo-code", "gemini", "github-copilot", "vscode-copilot", "opencode"];
|
|
3
3
|
export type SupportedAgent = (typeof supportedAgents)[number];
|
|
4
4
|
export type ConfigScope = "user" | "project";
|
|
5
5
|
export interface StdioMcpServerConfig {
|
|
@@ -22,7 +22,7 @@ export interface AgentConfigResult {
|
|
|
22
22
|
path: string;
|
|
23
23
|
serverName: string;
|
|
24
24
|
server: StdioMcpServerConfig;
|
|
25
|
-
format: "json" | "toml" | "json-opencode";
|
|
25
|
+
format: "json" | "toml" | "json-opencode" | "json-copilot" | "json-vscode";
|
|
26
26
|
snippet: string;
|
|
27
27
|
changed: boolean;
|
|
28
28
|
wrote: boolean;
|
package/dist/lib/agent-config.js
CHANGED
|
@@ -12,6 +12,8 @@ export const supportedAgents = [
|
|
|
12
12
|
"cline",
|
|
13
13
|
"roo-code",
|
|
14
14
|
"gemini",
|
|
15
|
+
"github-copilot",
|
|
16
|
+
"vscode-copilot",
|
|
15
17
|
"opencode",
|
|
16
18
|
];
|
|
17
19
|
const agentAliases = {
|
|
@@ -26,6 +28,15 @@ const agentAliases = {
|
|
|
26
28
|
roocode: "roo-code",
|
|
27
29
|
gemini: "gemini",
|
|
28
30
|
"gemini-cli": "gemini",
|
|
31
|
+
copilot: "github-copilot",
|
|
32
|
+
"copilot-cli": "github-copilot",
|
|
33
|
+
"github-copilot": "github-copilot",
|
|
34
|
+
"github-copilot-cli": "github-copilot",
|
|
35
|
+
vscode: "vscode-copilot",
|
|
36
|
+
"vs-code": "vscode-copilot",
|
|
37
|
+
"vscode-copilot": "vscode-copilot",
|
|
38
|
+
"vs-code-copilot": "vscode-copilot",
|
|
39
|
+
"github-copilot-vscode": "vscode-copilot",
|
|
29
40
|
opencode: "opencode",
|
|
30
41
|
"open-code": "opencode",
|
|
31
42
|
};
|
|
@@ -57,9 +68,13 @@ export async function configureAgentMcp(options) {
|
|
|
57
68
|
? await upsertCodexConfig(target.path, server, shouldWrite)
|
|
58
69
|
: target.format === "json-opencode"
|
|
59
70
|
? await upsertOpencodeConfig(target.path, server, shouldWrite)
|
|
60
|
-
:
|
|
61
|
-
? await
|
|
62
|
-
:
|
|
71
|
+
: target.format === "json-copilot"
|
|
72
|
+
? await upsertCopilotConfig(target.path, server, shouldWrite)
|
|
73
|
+
: target.format === "json-vscode"
|
|
74
|
+
? await upsertVsCodeMcpConfig(target.path, server, shouldWrite)
|
|
75
|
+
: options.agent === "gemini"
|
|
76
|
+
? await upsertGeminiExtension(target.path, server, shouldWrite)
|
|
77
|
+
: await upsertJsonMcpConfig(target.path, server, shouldWrite);
|
|
63
78
|
return {
|
|
64
79
|
agent: options.agent,
|
|
65
80
|
scope: options.scope,
|
|
@@ -101,6 +116,20 @@ export function renderConfigSnippet(agent, server) {
|
|
|
101
116
|
},
|
|
102
117
|
}, null, 2) + "\n");
|
|
103
118
|
}
|
|
119
|
+
if (agent === "github-copilot") {
|
|
120
|
+
return (JSON.stringify({
|
|
121
|
+
mcpServers: {
|
|
122
|
+
[SUMMER_MCP_SERVER_NAME]: copilotServerEntry(server),
|
|
123
|
+
},
|
|
124
|
+
}, null, 2) + "\n");
|
|
125
|
+
}
|
|
126
|
+
if (agent === "vscode-copilot") {
|
|
127
|
+
return (JSON.stringify({
|
|
128
|
+
servers: {
|
|
129
|
+
[SUMMER_MCP_SERVER_NAME]: vsCodeServerEntry(server),
|
|
130
|
+
},
|
|
131
|
+
}, null, 2) + "\n");
|
|
132
|
+
}
|
|
104
133
|
if (agent === "gemini") {
|
|
105
134
|
return (JSON.stringify(geminiExtensionManifest(server), null, 2) + "\n");
|
|
106
135
|
}
|
|
@@ -128,7 +157,11 @@ function resolveConfigTarget(agent, scope, cwd, env) {
|
|
|
128
157
|
? "toml"
|
|
129
158
|
: agent === "opencode"
|
|
130
159
|
? "json-opencode"
|
|
131
|
-
: "
|
|
160
|
+
: agent === "github-copilot"
|
|
161
|
+
? "json-copilot"
|
|
162
|
+
: agent === "vscode-copilot"
|
|
163
|
+
? "json-vscode"
|
|
164
|
+
: "json",
|
|
132
165
|
warnings,
|
|
133
166
|
};
|
|
134
167
|
}
|
|
@@ -189,6 +222,24 @@ function resolveConfigTarget(agent, scope, cwd, env) {
|
|
|
189
222
|
warnings,
|
|
190
223
|
};
|
|
191
224
|
}
|
|
225
|
+
if (agent === "github-copilot") {
|
|
226
|
+
return {
|
|
227
|
+
path: scope === "user"
|
|
228
|
+
? join(homedir(), ".copilot", "mcp-config.json")
|
|
229
|
+
: join(cwd, ".mcp.json"),
|
|
230
|
+
format: "json-copilot",
|
|
231
|
+
warnings,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (agent === "vscode-copilot") {
|
|
235
|
+
return {
|
|
236
|
+
path: scope === "user"
|
|
237
|
+
? vsCodeUserMcpPath(env)
|
|
238
|
+
: join(cwd, ".vscode", "mcp.json"),
|
|
239
|
+
format: "json-vscode",
|
|
240
|
+
warnings,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
192
243
|
if (agent === "opencode") {
|
|
193
244
|
return {
|
|
194
245
|
path: scope === "user"
|
|
@@ -231,6 +282,18 @@ function opencodeUserConfigPath(env) {
|
|
|
231
282
|
const xdg = env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
232
283
|
return join(xdg, "opencode", "opencode.json");
|
|
233
284
|
}
|
|
285
|
+
function vsCodeUserMcpPath(env) {
|
|
286
|
+
const os = platform();
|
|
287
|
+
if (os === "win32") {
|
|
288
|
+
const appData = env.APPDATA ?? join(homedir(), "AppData", "Roaming");
|
|
289
|
+
return join(appData, "Code", "User", "mcp.json");
|
|
290
|
+
}
|
|
291
|
+
if (os === "darwin") {
|
|
292
|
+
return join(homedir(), "Library", "Application Support", "Code", "User", "mcp.json");
|
|
293
|
+
}
|
|
294
|
+
const xdg = env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
295
|
+
return join(xdg, "Code", "User", "mcp.json");
|
|
296
|
+
}
|
|
234
297
|
function getConfigPathOverride(agent, env) {
|
|
235
298
|
if (agent === "codex")
|
|
236
299
|
return env.SUMMER_CODEX_CONFIG_FILE;
|
|
@@ -244,6 +307,10 @@ function getConfigPathOverride(agent, env) {
|
|
|
244
307
|
return env.SUMMER_ROO_CODE_CONFIG_FILE;
|
|
245
308
|
if (agent === "gemini")
|
|
246
309
|
return env.SUMMER_GEMINI_CONFIG_FILE;
|
|
310
|
+
if (agent === "github-copilot")
|
|
311
|
+
return env.SUMMER_GITHUB_COPILOT_CONFIG_FILE;
|
|
312
|
+
if (agent === "vscode-copilot")
|
|
313
|
+
return env.SUMMER_VSCODE_COPILOT_CONFIG_FILE;
|
|
247
314
|
if (agent === "opencode")
|
|
248
315
|
return env.SUMMER_OPENCODE_CONFIG_FILE;
|
|
249
316
|
return env.SUMMER_WINDSURF_MCP_CONFIG_FILE;
|
|
@@ -321,6 +388,30 @@ async function upsertOpencodeConfig(path, server, write) {
|
|
|
321
388
|
}
|
|
322
389
|
return { changed };
|
|
323
390
|
}
|
|
391
|
+
async function upsertCopilotConfig(path, server, write) {
|
|
392
|
+
const current = await readJsonConfig(path);
|
|
393
|
+
const next = copyJsonObject(current);
|
|
394
|
+
next.mcpServers = mergeNamedObject(next.mcpServers, SUMMER_MCP_SERVER_NAME, copilotServerEntry(server), "mcpServers");
|
|
395
|
+
const currentRendered = renderJsonFile(current);
|
|
396
|
+
const nextRendered = renderJsonFile(next);
|
|
397
|
+
const changed = currentRendered !== nextRendered;
|
|
398
|
+
if (write && changed) {
|
|
399
|
+
await writeTextFile(path, nextRendered);
|
|
400
|
+
}
|
|
401
|
+
return { changed };
|
|
402
|
+
}
|
|
403
|
+
async function upsertVsCodeMcpConfig(path, server, write) {
|
|
404
|
+
const current = await readJsonConfig(path);
|
|
405
|
+
const next = copyJsonObject(current);
|
|
406
|
+
next.servers = mergeNamedObject(next.servers, SUMMER_MCP_SERVER_NAME, vsCodeServerEntry(server), "servers");
|
|
407
|
+
const currentRendered = renderJsonFile(current);
|
|
408
|
+
const nextRendered = renderJsonFile(next);
|
|
409
|
+
const changed = currentRendered !== nextRendered;
|
|
410
|
+
if (write && changed) {
|
|
411
|
+
await writeTextFile(path, nextRendered);
|
|
412
|
+
}
|
|
413
|
+
return { changed };
|
|
414
|
+
}
|
|
324
415
|
async function upsertGeminiExtension(path, server, write) {
|
|
325
416
|
const current = await readJsonConfig(path);
|
|
326
417
|
const next = geminiExtensionManifest(server, current);
|
|
@@ -342,6 +433,29 @@ function opencodeServerEntry(server) {
|
|
|
342
433
|
}
|
|
343
434
|
return entry;
|
|
344
435
|
}
|
|
436
|
+
function copilotServerEntry(server) {
|
|
437
|
+
const entry = {
|
|
438
|
+
type: "local",
|
|
439
|
+
command: server.command,
|
|
440
|
+
args: server.args,
|
|
441
|
+
tools: ["*"],
|
|
442
|
+
};
|
|
443
|
+
if (server.env && Object.keys(server.env).length > 0) {
|
|
444
|
+
entry.env = { ...server.env };
|
|
445
|
+
}
|
|
446
|
+
return entry;
|
|
447
|
+
}
|
|
448
|
+
function vsCodeServerEntry(server) {
|
|
449
|
+
const entry = {
|
|
450
|
+
type: "stdio",
|
|
451
|
+
command: server.command,
|
|
452
|
+
args: server.args,
|
|
453
|
+
};
|
|
454
|
+
if (server.env && Object.keys(server.env).length > 0) {
|
|
455
|
+
entry.env = { ...server.env };
|
|
456
|
+
}
|
|
457
|
+
return entry;
|
|
458
|
+
}
|
|
345
459
|
function geminiExtensionManifest(server, base = {}) {
|
|
346
460
|
const manifest = { ...base };
|
|
347
461
|
manifest.name = "summer";
|
|
@@ -414,6 +528,15 @@ function renderJsonFile(value) {
|
|
|
414
528
|
function copyJsonObject(value) {
|
|
415
529
|
return JSON.parse(JSON.stringify(value));
|
|
416
530
|
}
|
|
531
|
+
function mergeNamedObject(value, key, entry, label) {
|
|
532
|
+
if (value !== undefined && !isJsonObject(value)) {
|
|
533
|
+
throw new Error(`Existing ${label} value must be a JSON object.`);
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
...(isJsonObject(value) ? value : {}),
|
|
537
|
+
[key]: entry,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
417
540
|
function isJsonObject(value) {
|
|
418
541
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
419
542
|
}
|
|
@@ -437,9 +560,13 @@ function createNextSteps(agent, scope, path) {
|
|
|
437
560
|
? "Restart VS Code so Roo Code reloads its MCP config."
|
|
438
561
|
: agent === "gemini"
|
|
439
562
|
? "Run `gemini extensions enable summer-engine` (if not already enabled), then restart Gemini CLI."
|
|
440
|
-
: agent === "
|
|
441
|
-
? "Restart
|
|
442
|
-
:
|
|
563
|
+
: agent === "github-copilot"
|
|
564
|
+
? "Restart Copilot CLI, or run /mcp reload and /skills reload in the active session."
|
|
565
|
+
: agent === "vscode-copilot"
|
|
566
|
+
? "Restart VS Code or run MCP: List Servers, then start summer-engine in Copilot Agent mode."
|
|
567
|
+
: agent === "opencode"
|
|
568
|
+
? "Restart OpenCode so it reloads opencode.json."
|
|
569
|
+
: "Restart Windsurf and refresh MCP servers from Cascade settings.";
|
|
443
570
|
const projectTrust = scope === "project" && agent === "codex"
|
|
444
571
|
? "Codex only loads project .codex/config.toml from trusted projects."
|
|
445
572
|
: null;
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -19,6 +19,19 @@ export declare class EngineApiClient {
|
|
|
19
19
|
constructor(port: number, token: string);
|
|
20
20
|
static connect(): Promise<EngineApiClient>;
|
|
21
21
|
private request;
|
|
22
|
+
/** Raw fetch with auth + timeout — returns the Response so callers can inspect
|
|
23
|
+
* the status (202 queued vs 200 legacy). */
|
|
24
|
+
private _fetchRaw;
|
|
25
|
+
/** One long-poll of GET /api/ops/result (up to waitMs). */
|
|
26
|
+
private _pollResult;
|
|
27
|
+
/**
|
|
28
|
+
* Block E: send a mutating request, then resolve EITHER the legacy synchronous
|
|
29
|
+
* result (HTTP 200 — dormant/older engine) OR the async lifecycle (202
|
|
30
|
+
* {requestId} -> long-poll /api/ops/result until terminal). Compatible with
|
|
31
|
+
* both — inspects the HTTP status, not a flag. `timeoutMs` is the poll loop's
|
|
32
|
+
* total budget; a long op that keeps advancing is not falsely timed out.
|
|
33
|
+
*/
|
|
34
|
+
private _requestQueued;
|
|
22
35
|
health(): Promise<unknown>;
|
|
23
36
|
executeOps(ops: Record<string, unknown>[], options?: Record<string, unknown>): Promise<unknown>;
|
|
24
37
|
getSceneState(): Promise<unknown>;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -2,6 +2,7 @@ import { mkdir, writeFile } from "fs/promises";
|
|
|
2
2
|
import { tmpdir } from "os";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { getApiToken, getApiPort, checkEngineHealth } from "./engine.js";
|
|
5
|
+
import { classifyOpsResponse, pollOpToTerminal, } from "./async-op-lifecycle.js";
|
|
5
6
|
function asRecord(value) {
|
|
6
7
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
7
8
|
? value
|
|
@@ -96,11 +97,60 @@ export class EngineApiClient {
|
|
|
96
97
|
}
|
|
97
98
|
return res.json();
|
|
98
99
|
}
|
|
100
|
+
/** Raw fetch with auth + timeout — returns the Response so callers can inspect
|
|
101
|
+
* the status (202 queued vs 200 legacy). */
|
|
102
|
+
async _fetchRaw(method, path, body, timeoutMs) {
|
|
103
|
+
return fetch(`http://127.0.0.1:${this.port}${path}`, {
|
|
104
|
+
method,
|
|
105
|
+
headers: {
|
|
106
|
+
Authorization: `Bearer ${this.token}`,
|
|
107
|
+
"Content-Type": "application/json",
|
|
108
|
+
},
|
|
109
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
110
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** One long-poll of GET /api/ops/result (up to waitMs). */
|
|
114
|
+
async _pollResult(requestId, waitMs) {
|
|
115
|
+
const res = await this._fetchRaw("GET", `/api/ops/result?requestId=${encodeURIComponent(requestId)}&wait=${waitMs}`, undefined, waitMs + 5000);
|
|
116
|
+
if (!res.ok) {
|
|
117
|
+
const text = await res.text().catch(() => "");
|
|
118
|
+
throw new Error(`Engine API error ${res.status}: ${text.slice(0, 200)}`);
|
|
119
|
+
}
|
|
120
|
+
return (await res.json());
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Block E: send a mutating request, then resolve EITHER the legacy synchronous
|
|
124
|
+
* result (HTTP 200 — dormant/older engine) OR the async lifecycle (202
|
|
125
|
+
* {requestId} -> long-poll /api/ops/result until terminal). Compatible with
|
|
126
|
+
* both — inspects the HTTP status, not a flag. `timeoutMs` is the poll loop's
|
|
127
|
+
* total budget; a long op that keeps advancing is not falsely timed out.
|
|
128
|
+
*/
|
|
129
|
+
async _requestQueued(method, path, body, timeoutMs) {
|
|
130
|
+
const res = await this._fetchRaw(method, path, body, timeoutMs);
|
|
131
|
+
// 202 (queued) + 429 (backpressure, errorClass in body) are handled below;
|
|
132
|
+
// any other non-2xx is a real transport error.
|
|
133
|
+
if (!res.ok && res.status !== 202 && res.status !== 429) {
|
|
134
|
+
const text = await res.text().catch(() => "");
|
|
135
|
+
throw new Error(`Engine API error ${res.status}: ${text.slice(0, 200)}`);
|
|
136
|
+
}
|
|
137
|
+
const respBody = await res.json().catch(() => ({}));
|
|
138
|
+
const classified = classifyOpsResponse(res.status, respBody);
|
|
139
|
+
if (classified.mode === "legacy") {
|
|
140
|
+
return respBody;
|
|
141
|
+
}
|
|
142
|
+
return pollOpToTerminal((waitMs) => this._pollResult(classified.requestId, waitMs), {
|
|
143
|
+
totalTimeoutMs: timeoutMs,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
99
146
|
async health() {
|
|
100
147
|
return this.request("GET", "/api/health");
|
|
101
148
|
}
|
|
102
149
|
async executeOps(ops, options) {
|
|
103
|
-
|
|
150
|
+
// Ops may include long-running work (ImportFromUrlBatch, GitCommit); the
|
|
151
|
+
// engine keeps it "running" and we poll. 120s budget. Resolves legacy-200 or
|
|
152
|
+
// async-202 (Block E).
|
|
153
|
+
return this._requestQueued("POST", "/api/ops", { ops, options }, 120_000);
|
|
104
154
|
}
|
|
105
155
|
async getSceneState() {
|
|
106
156
|
return this.request("GET", "/api/state/scene");
|
|
@@ -121,10 +171,11 @@ export class EngineApiClient {
|
|
|
121
171
|
return this.request("GET", `/api/state/script-errors?path=${encodeURIComponent(path)}`);
|
|
122
172
|
}
|
|
123
173
|
async play(scene) {
|
|
124
|
-
|
|
174
|
+
// Cold-load on large projects can take 25-40s. 60s budget.
|
|
175
|
+
return this._requestQueued("POST", "/api/play", scene ? { scene } : {}, 60_000);
|
|
125
176
|
}
|
|
126
177
|
async stop() {
|
|
127
|
-
return this.
|
|
178
|
+
return this._requestQueued("POST", "/api/stop", undefined, 15_000);
|
|
128
179
|
}
|
|
129
180
|
async readFile(path, maxBytes) {
|
|
130
181
|
const params = new URLSearchParams({ path });
|
|
@@ -153,31 +204,17 @@ export class EngineApiClient {
|
|
|
153
204
|
return localPath;
|
|
154
205
|
}
|
|
155
206
|
async snapshot(kind) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
throw new Error(`Snapshot failed ${res.status}: ${text.slice(0, 200)}`);
|
|
207
|
+
// Block E: /api/snapshot/* is queued (GET -> 202 -> poll /api/ops/result).
|
|
208
|
+
// The terminal result is the apply dict; the image rides as base64 inside it
|
|
209
|
+
// (no raw-binary channel). Legacy/dormant engines answer 200 synchronously
|
|
210
|
+
// with the same payload shape — _requestQueued resolves both.
|
|
211
|
+
let response;
|
|
212
|
+
try {
|
|
213
|
+
response = await this._requestQueued("GET", `/api/snapshot/${kind}`, undefined, 60_000);
|
|
164
214
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const { format, mime, ext } = snapshotFormat({ mime: contentType });
|
|
168
|
-
const buffer = Buffer.from(await res.arrayBuffer());
|
|
169
|
-
const localPath = await this.writeSnapshotFile(kind, buffer, ext);
|
|
170
|
-
return {
|
|
171
|
-
ok: true,
|
|
172
|
-
localPath,
|
|
173
|
-
path: localPath,
|
|
174
|
-
format,
|
|
175
|
-
mime,
|
|
176
|
-
context: kind === "viewport" ? "scene" : "game",
|
|
177
|
-
bytes: buffer.byteLength,
|
|
178
|
-
};
|
|
215
|
+
catch (err) {
|
|
216
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
179
217
|
}
|
|
180
|
-
const response = await res.json();
|
|
181
218
|
const payload = findSnapshotPayload(response);
|
|
182
219
|
if (!payload) {
|
|
183
220
|
return {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.5.34 Block E — async tool-lifecycle consumer for the CLI/MCP.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the web orchestrator (publicsummerengine
|
|
5
|
+
* src/lib/bridge/async-op-lifecycle.ts) so the CLI speaks the SAME contract as
|
|
6
|
+
* the engine's responsive tool server (Block E):
|
|
7
|
+
* - NEW path: `202 {requestId, status:"queued"}` -> long-poll
|
|
8
|
+
* GET /api/ops/result?requestId=&wait=ms until terminal (done|failed|canceled).
|
|
9
|
+
* - LEGACY path (dormant/older engine): a synchronous `200` with the full apply
|
|
10
|
+
* result, exactly as before.
|
|
11
|
+
*
|
|
12
|
+
* It inspects the RESPONSE (status/body), not a flag, so it stays compatible with
|
|
13
|
+
* both an async and a synchronous engine. Pure + dependency-injected (now/sleep/
|
|
14
|
+
* pollOnce) so it is unit-tested without HTTP.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isTerminalStatus(status: string | undefined): boolean;
|
|
17
|
+
/** The envelope returned by GET /api/ops/result. */
|
|
18
|
+
export interface OpResultEnvelope {
|
|
19
|
+
requestId?: string;
|
|
20
|
+
status: string;
|
|
21
|
+
terminalState?: string;
|
|
22
|
+
errorClass?: string;
|
|
23
|
+
result?: Record<string, unknown>;
|
|
24
|
+
progress?: unknown;
|
|
25
|
+
appliedSeq?: number;
|
|
26
|
+
}
|
|
27
|
+
export type OpsResponseClassification = {
|
|
28
|
+
mode: "queued";
|
|
29
|
+
requestId: string;
|
|
30
|
+
} | {
|
|
31
|
+
mode: "legacy";
|
|
32
|
+
legacyResult: unknown;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Decide whether a POST /api/ops (or GET /api/snapshot/*) response is the new
|
|
36
|
+
* async ack (`202`) or the legacy synchronous result (`200`). Inspecting the HTTP
|
|
37
|
+
* status (not a flag) is what keeps this compatible with both engines.
|
|
38
|
+
*/
|
|
39
|
+
export declare function classifyOpsResponse(httpStatus: number, body: unknown): OpsResponseClassification;
|
|
40
|
+
export interface PollOpts {
|
|
41
|
+
/** Hard wall-clock cap on the whole poll loop (ms). The per-tool budget. */
|
|
42
|
+
totalTimeoutMs: number;
|
|
43
|
+
/** Give up if the op hasn't advanced for this long (ms). Default = totalTimeoutMs. */
|
|
44
|
+
noProgressTimeoutMs?: number;
|
|
45
|
+
/** Server long-poll wait per GET (ms). Default 5000 — the engine holds the
|
|
46
|
+
* connection up to this long, which paces the loop in production. */
|
|
47
|
+
pollWaitMs?: number;
|
|
48
|
+
/** Pacing sleep between non-terminal polls (ms). Negligible in production
|
|
49
|
+
* (the long-poll dominates); keeps the loop from hot-spinning. Default 250. */
|
|
50
|
+
pacingMs?: number;
|
|
51
|
+
now?: () => number;
|
|
52
|
+
sleep?: (ms: number) => Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Drive an async op to a terminal result by polling. `pollOnce(waitMs)` performs
|
|
56
|
+
* one GET /api/ops/result (long-poll up to waitMs). Returns the engine's apply
|
|
57
|
+
* dict with terminalState/errorClass/appliedSeq merged on top, or a synthetic
|
|
58
|
+
* `timed_out` result if the budget is exhausted.
|
|
59
|
+
*/
|
|
60
|
+
export declare function pollOpToTerminal(pollOnce: (waitMs: number) => Promise<OpResultEnvelope>, opts: PollOpts): Promise<Record<string, unknown>>;
|