@vgai/live 0.5.13 → 0.5.15
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/dist/.tsbuildinfo +1 -1
- package/dist/editor.d.ts +16 -18
- package/dist/editor.js +16 -14
- package/dist/game-client/bridge-transport.d.ts +19 -0
- package/dist/game-client/capture-notes.d.ts +8 -7
- package/dist/game-client/capture-notes.js +10 -9
- package/dist/game-client/client.d.ts +16 -1
- package/dist/game-client/client.js +21 -2
- package/dist/game-client/failure-block.d.ts +3 -3
- package/dist/game-client/failure-block.js +17 -8
- package/dist/game-client/relay-transport.d.ts +40 -0
- package/dist/game-client/relay-transport.js +118 -6
- package/dist/game-client/types.d.ts +6 -6
- package/dist/index.d.ts +19 -3
- package/dist/index.js +3 -1
- package/dist/recording.d.ts +28 -0
- package/dist/recording.js +52 -0
- package/package.json +3 -3
- package/src/editor.ts +20 -15
- package/src/game-client/bridge-transport.ts +19 -0
- package/src/game-client/capture-notes.ts +13 -12
- package/src/game-client/client.ts +23 -2
- package/src/game-client/failure-block.ts +21 -12
- package/src/game-client/relay-transport.ts +130 -6
- package/src/game-client/types.ts +6 -6
- package/src/index.ts +19 -4
- package/src/recording.ts +70 -0
package/src/index.ts
CHANGED
|
@@ -71,6 +71,8 @@ export type {
|
|
|
71
71
|
export { LiveEditorDocument } from './editor-document.js';
|
|
72
72
|
export { createGameClient, createLiveGame, type LiveGame } from './game.js';
|
|
73
73
|
export * from './game-client/index.js';
|
|
74
|
+
export type { GameplayRecordingResult } from './recording.js';
|
|
75
|
+
export { LiveGameplayRecording } from './recording.js';
|
|
74
76
|
export type {
|
|
75
77
|
ProjectSessionHint,
|
|
76
78
|
ResolvedSession,
|
|
@@ -82,8 +84,18 @@ export type { LazySession } from './singleton.js';
|
|
|
82
84
|
export { createLazySession } from './singleton.js';
|
|
83
85
|
export { LiveTools } from './tools.js';
|
|
84
86
|
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
+
/**
|
|
88
|
+
* The `page` binding: a `game.page(step)`-shaped CALL (see this module's doc
|
|
89
|
+
* comment for the closure-capture limitation) that also carries the one page
|
|
90
|
+
* verb no step can express — `page.reload()`.
|
|
91
|
+
*
|
|
92
|
+
* `reload` is a property rather than a step because a step that navigates
|
|
93
|
+
* destroys the channel its own result would return on: `page(p =>
|
|
94
|
+
* p.evaluate(() => location.reload()))` was the only spelling available, and
|
|
95
|
+
* it resolves on a send while failing outright outside play mode. See
|
|
96
|
+
* `GameClient.reloadPage`.
|
|
97
|
+
*/
|
|
98
|
+
export type PageStep = GameClient['page'] & { reload: GameClient['reloadPage'] };
|
|
87
99
|
|
|
88
100
|
export interface LiveSession extends LiveBindings {
|
|
89
101
|
/** The resolved session this is bound to — useful for logging/debugging which port/project a script attached to. */
|
|
@@ -97,7 +109,9 @@ export interface LiveBindings {
|
|
|
97
109
|
* several mounted instances (multiplayer authoring). A bare `game` call
|
|
98
110
|
* targets the sole instance and refuses when several are live. */
|
|
99
111
|
game: LiveGame;
|
|
100
|
-
/** `GameClient.page` bound to `game` — see this
|
|
112
|
+
/** `GameClient.page` bound to `game`, plus `page.reload()` — see this
|
|
113
|
+
* module's own doc comment for the wire limitation, and {@link PageStep}
|
|
114
|
+
* for why reload is a property rather than a step. */
|
|
101
115
|
page: PageStep;
|
|
102
116
|
/** Registered project callables: enumerate, inspect, and invoke. */
|
|
103
117
|
tools: LiveTools;
|
|
@@ -112,7 +126,8 @@ function bindTo(port: number, projectRoot: string): LiveBindings {
|
|
|
112
126
|
const client = new EditorClient({ url: `http://127.0.0.1:${port}` });
|
|
113
127
|
const editor = new LiveEditor(client);
|
|
114
128
|
const game = createLiveGame(port, join(projectRoot, '.vgai', 'last-run'));
|
|
115
|
-
const
|
|
129
|
+
const step: GameClient['page'] = (fn) => game.page(fn);
|
|
130
|
+
const page: PageStep = Object.assign(step, { reload: () => game.reloadPage() });
|
|
116
131
|
const tools = new LiveTools(client);
|
|
117
132
|
return { editor, game, page, tools };
|
|
118
133
|
}
|
package/src/recording.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `editor.recording` — real-time gameplay evidence through the existing live
|
|
3
|
+
* editor session. The browser owns MediaRecorder and the game pixels/audio;
|
|
4
|
+
* the project server streams the standard WebM to disk while this namespace
|
|
5
|
+
* exposes the start/stop controls and optional final copy destination.
|
|
6
|
+
*
|
|
7
|
+
* Recording deliberately is not a CLI workflow of its own. Agents start and
|
|
8
|
+
* stop it through the one general door (`vgai eval`) and use ordinary ffmpeg,
|
|
9
|
+
* ffprobe, and filesystem tools to review or trim the result.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { copyFile, mkdir } from 'node:fs/promises';
|
|
13
|
+
import { dirname, extname, isAbsolute, resolve } from 'node:path';
|
|
14
|
+
import type {
|
|
15
|
+
EditorClient,
|
|
16
|
+
GameplayRecordingOptions,
|
|
17
|
+
GameplayRecordingStarted,
|
|
18
|
+
} from '@vgai/editor-sdk';
|
|
19
|
+
|
|
20
|
+
export interface GameplayRecordingResult extends GameplayRecordingStarted {
|
|
21
|
+
/** Absolute path to the standard WebM written on stop. */
|
|
22
|
+
path: string;
|
|
23
|
+
durationMs: number;
|
|
24
|
+
droppedFrames: number;
|
|
25
|
+
frameErrors: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function recordingPath(destination: string): string {
|
|
29
|
+
if (destination.trim() === '') throw new Error('recording destination must not be empty');
|
|
30
|
+
const withExtension = extname(destination) === '' ? `${destination}.webm` : destination;
|
|
31
|
+
return isAbsolute(withExtension) ? withExtension : resolve(process.cwd(), withExtension);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class LiveGameplayRecording {
|
|
35
|
+
readonly #client: EditorClient;
|
|
36
|
+
|
|
37
|
+
constructor(client: EditorClient) {
|
|
38
|
+
this.#client = client;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Start one clean game recording. The page owns it until `stop()`, even
|
|
42
|
+
* when start and stop are issued by separate `vgai eval` processes. */
|
|
43
|
+
async start(options: GameplayRecordingOptions = {}): Promise<GameplayRecordingStarted> {
|
|
44
|
+
return this.#client.startGameplayRecording(options);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Finalize the active recording, write it to disk, and return honest media
|
|
48
|
+
* metadata without echoing the large base64 transport payload to stdout. */
|
|
49
|
+
async stop(destination?: string): Promise<GameplayRecordingResult> {
|
|
50
|
+
const capture = await this.#client.stopGameplayRecording();
|
|
51
|
+
const path = destination === undefined ? capture.path : recordingPath(destination);
|
|
52
|
+
if (path !== capture.path) {
|
|
53
|
+
await mkdir(dirname(path), { recursive: true });
|
|
54
|
+
await copyFile(capture.path, path);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
path,
|
|
58
|
+
startedAt: capture.startedAt,
|
|
59
|
+
mimeType: capture.mimeType,
|
|
60
|
+
width: capture.width,
|
|
61
|
+
height: capture.height,
|
|
62
|
+
fps: capture.fps,
|
|
63
|
+
audio: capture.audio,
|
|
64
|
+
durationMs: capture.durationMs,
|
|
65
|
+
droppedFrames: capture.droppedFrames,
|
|
66
|
+
frameErrors: capture.frameErrors,
|
|
67
|
+
layers: capture.layers,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|