@vgai/sdk 0.5.40 → 0.5.42
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/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@vgai/sdk",
|
|
3
3
|
"author": "Volter AI, Inc.",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.42",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
34
|
-
"@vgai/engine": "0.5.
|
|
34
|
+
"@vgai/engine": "0.5.42",
|
|
35
35
|
"playwright": "^1.58.2",
|
|
36
36
|
"zod": "^4.3.6"
|
|
37
37
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Node half of the play-log format: the header fast-read over a file
|
|
3
|
+
* descriptor. Split from `log-format.ts` so the browser tier (which writes
|
|
4
|
+
* and parses the same format through the StorageBackend) can import the
|
|
5
|
+
* format without `node:fs` — the same split `game-globals-prelude.ts`
|
|
6
|
+
* established for its two programs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { closeSync, openSync, readSync } from 'node:fs';
|
|
10
|
+
import { asPlayLogHeader, type PlayLogHeader } from './log-format.js';
|
|
11
|
+
|
|
12
|
+
const HEADER_READ_BYTES = 4096;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The header of the log file at `path`, read WITHOUT loading the file — a
|
|
16
|
+
* listing walks every log in `logs/`, and those files are unbounded (a long
|
|
17
|
+
* run writes megabytes). `null` for an unreadable file or one with no header.
|
|
18
|
+
*/
|
|
19
|
+
export function readPlayLogHeaderFile(path: string): PlayLogHeader | null {
|
|
20
|
+
let fd: number | null = null;
|
|
21
|
+
try {
|
|
22
|
+
fd = openSync(path, 'r');
|
|
23
|
+
const buffer = Buffer.alloc(HEADER_READ_BYTES);
|
|
24
|
+
const bytes = readSync(fd, buffer, 0, HEADER_READ_BYTES, 0);
|
|
25
|
+
const head = buffer.subarray(0, bytes).toString('utf-8');
|
|
26
|
+
const newline = head.indexOf('\n');
|
|
27
|
+
const line = newline === -1 ? head : head.slice(0, newline);
|
|
28
|
+
if (line.trim() === '') return null;
|
|
29
|
+
return asPlayLogHeader(JSON.parse(line));
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
} finally {
|
|
33
|
+
if (fd !== null) closeSync(fd);
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/play/log-format.ts
CHANGED
|
@@ -26,8 +26,6 @@
|
|
|
26
26
|
* "no header" means "this run's identity was never recorded", never an error.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
import { closeSync, openSync, readSync } from 'node:fs';
|
|
30
|
-
|
|
31
29
|
/** `kind` value marking a play log's header line. */
|
|
32
30
|
export const PLAY_LOG_HEADER_KIND = 'session';
|
|
33
31
|
|
|
@@ -76,30 +74,3 @@ export function asPlayLogHeader(record: unknown): PlayLogHeader | null {
|
|
|
76
74
|
startedAt: typeof rec['startedAt'] === 'number' ? rec['startedAt'] : 0,
|
|
77
75
|
};
|
|
78
76
|
}
|
|
79
|
-
|
|
80
|
-
/** How much of a log file's start is read to find its header. A header line is
|
|
81
|
-
* a few hundred bytes; anything longer is not one. */
|
|
82
|
-
const HEADER_READ_BYTES = 4096;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* The header of the log file at `path`, read WITHOUT loading the file — a
|
|
86
|
-
* listing walks every log in `logs/`, and those files are unbounded (a long
|
|
87
|
-
* run writes megabytes). `null` for an unreadable file or one with no header.
|
|
88
|
-
*/
|
|
89
|
-
export function readPlayLogHeaderFile(path: string): PlayLogHeader | null {
|
|
90
|
-
let fd: number | null = null;
|
|
91
|
-
try {
|
|
92
|
-
fd = openSync(path, 'r');
|
|
93
|
-
const buffer = Buffer.alloc(HEADER_READ_BYTES);
|
|
94
|
-
const bytes = readSync(fd, buffer, 0, HEADER_READ_BYTES, 0);
|
|
95
|
-
const head = buffer.subarray(0, bytes).toString('utf-8');
|
|
96
|
-
const newline = head.indexOf('\n');
|
|
97
|
-
const line = newline === -1 ? head : head.slice(0, newline);
|
|
98
|
-
if (line.trim() === '') return null;
|
|
99
|
-
return asPlayLogHeader(JSON.parse(line));
|
|
100
|
-
} catch {
|
|
101
|
-
return null;
|
|
102
|
-
} finally {
|
|
103
|
-
if (fd !== null) closeSync(fd);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
@@ -45,7 +45,8 @@ import {
|
|
|
45
45
|
resolveProjectPath,
|
|
46
46
|
} from '../project/shared.js';
|
|
47
47
|
import { defineTool, type ToolRegistry } from '../registry.js';
|
|
48
|
-
import { asPlayLogHeader, type PlayLogHeader
|
|
48
|
+
import { asPlayLogHeader, type PlayLogHeader } from './log-format.js';
|
|
49
|
+
import { readPlayLogHeaderFile } from './log-format-node.js';
|
|
49
50
|
import {
|
|
50
51
|
getPlayTransport,
|
|
51
52
|
PLAY_READ_TIMEOUT_MS,
|
|
@@ -210,6 +210,23 @@ export type SessionJournalEvent =
|
|
|
210
210
|
}
|
|
211
211
|
/** The server granted a socket the duplex control capability. */
|
|
212
212
|
| { readonly kind: 'duplex-granted'; readonly clientId8: string }
|
|
213
|
+
/** A page proved it is speaking the exact generation the server granted. */
|
|
214
|
+
| {
|
|
215
|
+
readonly kind: 'control-lifecycle-confirmed';
|
|
216
|
+
readonly clientId8: string;
|
|
217
|
+
readonly connectionGeneration8: string;
|
|
218
|
+
}
|
|
219
|
+
/** A stale or contradictory control frame was refused. */
|
|
220
|
+
| {
|
|
221
|
+
readonly kind: 'control-lifecycle-rejected';
|
|
222
|
+
readonly clientId8: string;
|
|
223
|
+
readonly mismatch:
|
|
224
|
+
| 'serverGeneration'
|
|
225
|
+
| 'connectionGeneration'
|
|
226
|
+
| 'clientId'
|
|
227
|
+
| 'tabId'
|
|
228
|
+
| 'pageGeneration';
|
|
229
|
+
}
|
|
213
230
|
/** A command left the relay for one tab's command channel. */
|
|
214
231
|
| {
|
|
215
232
|
readonly kind: 'command-relayed';
|
|
@@ -588,6 +605,10 @@ export function formatJournalLine(line: SessionJournalLine): string {
|
|
|
588
605
|
return `journal: ${at} client-disconnected ${line.clientId8} code ${line.code ?? '-'} settled ${line.commandsSettled}`;
|
|
589
606
|
case 'duplex-granted':
|
|
590
607
|
return `journal: ${at} duplex-granted ${line.clientId8}`;
|
|
608
|
+
case 'control-lifecycle-confirmed':
|
|
609
|
+
return `journal: ${at} control-lifecycle-confirmed ${line.clientId8} connection ${line.connectionGeneration8}`;
|
|
610
|
+
case 'control-lifecycle-rejected':
|
|
611
|
+
return `journal: ${at} control-lifecycle-rejected ${line.clientId8} stale ${line.mismatch}`;
|
|
591
612
|
case 'command-relayed':
|
|
592
613
|
return `journal: ${at} command-relayed ${line.command} ${line.requestId8} -> tab ${line.tabId8 ?? 'none'}`;
|
|
593
614
|
case 'command-receipt':
|
|
@@ -68,7 +68,9 @@ export interface ProjectToolLoadError {
|
|
|
68
68
|
* is a FACT about the project; a host that cannot ask has not learned it.
|
|
69
69
|
*/
|
|
70
70
|
export const NO_PROJECT_MODULE_HOST_MESSAGE =
|
|
71
|
-
'This editor host cannot
|
|
71
|
+
'This editor host cannot RUN registered project tools (package.json#vgai.tools): callables ' +
|
|
72
|
+
'execute on the Node side. Contribution panels still load from project source; to run the ' +
|
|
73
|
+
'tools themselves, open the project in the Vite-backed dev or packaged editor.';
|
|
72
74
|
|
|
73
75
|
/** The catalog row that states {@link NO_PROJECT_MODULE_HOST_MESSAGE}. */
|
|
74
76
|
export function noProjectModuleHostError(): ProjectToolLoadError {
|