@vgai/live 0.5.20 → 0.5.21
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/session.js +67 -10
- package/package.json +3 -3
- package/src/session.ts +74 -17
package/dist/session.js
CHANGED
|
@@ -92,21 +92,61 @@ function readProjectSession(projectRoot) {
|
|
|
92
92
|
return null;
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
-
|
|
95
|
+
/**
|
|
96
|
+
* WHICH PROJECT a `/__editor/project` body says its server is serving, and the
|
|
97
|
+
* reason it cannot describe it.
|
|
98
|
+
*
|
|
99
|
+
* `serving` is the server's own statement of "I AM serving this project, I
|
|
100
|
+
* just cannot describe it" — its manifest is unparseable or fails strict
|
|
101
|
+
* validation. The route added it because a bare `{ project: null }` there is
|
|
102
|
+
* indistinguishable from "no project open"; reading only `project.path`
|
|
103
|
+
* reproduces that collapse on this side, and the cost is the worst kind of
|
|
104
|
+
* wrong answer: this door refused a session that WAS open on the caller's
|
|
105
|
+
* project with "no live editor session found … (N other live session(s) found,
|
|
106
|
+
* but none open this project)" — sending the operator to start an editor that
|
|
107
|
+
* was already running, with the real defect (their own manifest) never named.
|
|
108
|
+
*/
|
|
109
|
+
function servedProject(body) {
|
|
110
|
+
const b = body;
|
|
111
|
+
return {
|
|
112
|
+
path: b.project?.path ?? b.serving?.path ?? null,
|
|
113
|
+
manifestError: b.project ? null : (b.serving?.error ?? null),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* What the server on `port` says it serves — `undefined` when it did not
|
|
118
|
+
* answer at all. One probe: the manifest failure arrives with the path, so
|
|
119
|
+
* neither the hint path nor the refusal path pays a second round trip.
|
|
120
|
+
*/
|
|
121
|
+
async function probeServedProject(port) {
|
|
96
122
|
try {
|
|
97
|
-
const response = await fetch(`http://127.0.0.1:${
|
|
123
|
+
const response = await fetch(`http://127.0.0.1:${port}/__editor/project`, {
|
|
98
124
|
signal: AbortSignal.timeout(EDITOR_SESSION_DISCOVERY_TIMEOUT_MS),
|
|
99
125
|
});
|
|
100
126
|
if (!response.ok)
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
const servedProject = body.project?.path;
|
|
104
|
-
return (servedProject !== undefined && canonicalPath(servedProject) === canonicalPath(projectRoot));
|
|
127
|
+
return undefined;
|
|
128
|
+
return servedProject(await response.json());
|
|
105
129
|
}
|
|
106
130
|
catch {
|
|
107
|
-
return
|
|
131
|
+
return undefined;
|
|
108
132
|
}
|
|
109
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* The refusal for a session that IS this project's and cannot be driven,
|
|
136
|
+
* because the project's manifest does not load.
|
|
137
|
+
*
|
|
138
|
+
* A refusal, not an attach: with no readable manifest the editor page itself
|
|
139
|
+
* renders its startup-error screen, so there is no editor and no game behind
|
|
140
|
+
* this door to bind `{ editor, game, page, tools, session }` to. What changed
|
|
141
|
+
* is only that it now says the true thing — the failing file and key — instead
|
|
142
|
+
* of "no live editor session found", which sent operators to start an editor
|
|
143
|
+
* that was already running while their actual defect went unnamed.
|
|
144
|
+
*/
|
|
145
|
+
function manifestRefusal(projectRoot, manifestError) {
|
|
146
|
+
return new Error(`@vgai/live: the editor session for ${projectRoot} is live, but its vgai.project.json does ` +
|
|
147
|
+
'not load, so there is no editor or game to drive — the editor page is showing this same ' +
|
|
148
|
+
`error. Fix the manifest and retry; the session recovers on save, no restart needed.\n${manifestError}`);
|
|
149
|
+
}
|
|
110
150
|
/**
|
|
111
151
|
* Resolve `projectDir` (default `process.cwd()`) to the port of its already-
|
|
112
152
|
* running `vgai edit` session. Throws a descriptive error (never hangs
|
|
@@ -127,9 +167,24 @@ export async function resolveSession(projectDir = process.cwd(), deps = {}) {
|
|
|
127
167
|
// deadline. The live probe remains authoritative, so a stale file cannot
|
|
128
168
|
// attach us to the wrong project or port.
|
|
129
169
|
const localHint = (deps.readProjectSession ?? readProjectSession)(projectRoot);
|
|
130
|
-
if (localHint
|
|
131
|
-
|
|
132
|
-
|
|
170
|
+
if (localHint) {
|
|
171
|
+
// `deps.verifyProjectSession` is the test-only override and keeps its
|
|
172
|
+
// boolean meaning: "this hint is this project's session, and healthy".
|
|
173
|
+
// With no override we read the probe ourselves, because the same response
|
|
174
|
+
// carries WHY a served project cannot be described.
|
|
175
|
+
if (deps.verifyProjectSession) {
|
|
176
|
+
if (await deps.verifyProjectSession(localHint, projectRoot)) {
|
|
177
|
+
return { port: localHint.port, projectRoot };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
const served = await probeServedProject(localHint.port);
|
|
182
|
+
if (served?.path != null && canonicalPath(served.path) === canonicalPath(projectRoot)) {
|
|
183
|
+
if (served.manifestError !== null)
|
|
184
|
+
throw manifestRefusal(projectRoot, served.manifestError);
|
|
185
|
+
return { port: localHint.port, projectRoot };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
133
188
|
}
|
|
134
189
|
let sessions;
|
|
135
190
|
try {
|
|
@@ -150,5 +205,7 @@ export async function resolveSession(projectDir = process.cwd(), deps = {}) {
|
|
|
150
205
|
'never silently attaches to a different project.)'
|
|
151
206
|
: ''));
|
|
152
207
|
}
|
|
208
|
+
if (match.manifestError != null)
|
|
209
|
+
throw manifestRefusal(projectRoot, match.manifestError);
|
|
153
210
|
return { port: match.port, projectRoot };
|
|
154
211
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@vgai/live",
|
|
3
3
|
"author": "Volter AI, Inc.",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.21",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"prepack": "npm run build"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@vgai/editor-sdk": "0.5.
|
|
36
|
-
"@vgai/sdk": "0.5.
|
|
35
|
+
"@vgai/editor-sdk": "0.5.21",
|
|
36
|
+
"@vgai/sdk": "0.5.21"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@playwright/test": ">=1.58.2 <2"
|
package/src/session.ts
CHANGED
|
@@ -130,25 +130,69 @@ function readProjectSession(projectRoot: string): ProjectSessionHint | null {
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
/**
|
|
134
|
+
* WHICH PROJECT a `/__editor/project` body says its server is serving, and the
|
|
135
|
+
* reason it cannot describe it.
|
|
136
|
+
*
|
|
137
|
+
* `serving` is the server's own statement of "I AM serving this project, I
|
|
138
|
+
* just cannot describe it" — its manifest is unparseable or fails strict
|
|
139
|
+
* validation. The route added it because a bare `{ project: null }` there is
|
|
140
|
+
* indistinguishable from "no project open"; reading only `project.path`
|
|
141
|
+
* reproduces that collapse on this side, and the cost is the worst kind of
|
|
142
|
+
* wrong answer: this door refused a session that WAS open on the caller's
|
|
143
|
+
* project with "no live editor session found … (N other live session(s) found,
|
|
144
|
+
* but none open this project)" — sending the operator to start an editor that
|
|
145
|
+
* was already running, with the real defect (their own manifest) never named.
|
|
146
|
+
*/
|
|
147
|
+
function servedProject(body: unknown): { path: string | null; manifestError: string | null } {
|
|
148
|
+
const b = body as {
|
|
149
|
+
project?: { path?: string } | null;
|
|
150
|
+
serving?: { path?: string; error?: string } | null;
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
path: b.project?.path ?? b.serving?.path ?? null,
|
|
154
|
+
manifestError: b.project ? null : (b.serving?.error ?? null),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* What the server on `port` says it serves — `undefined` when it did not
|
|
160
|
+
* answer at all. One probe: the manifest failure arrives with the path, so
|
|
161
|
+
* neither the hint path nor the refusal path pays a second round trip.
|
|
162
|
+
*/
|
|
163
|
+
async function probeServedProject(
|
|
164
|
+
port: number,
|
|
165
|
+
): Promise<{ path: string | null; manifestError: string | null } | undefined> {
|
|
137
166
|
try {
|
|
138
|
-
const response = await fetch(`http://127.0.0.1:${
|
|
167
|
+
const response = await fetch(`http://127.0.0.1:${port}/__editor/project`, {
|
|
139
168
|
signal: AbortSignal.timeout(EDITOR_SESSION_DISCOVERY_TIMEOUT_MS),
|
|
140
169
|
});
|
|
141
|
-
if (!response.ok) return
|
|
142
|
-
|
|
143
|
-
const servedProject = body.project?.path;
|
|
144
|
-
return (
|
|
145
|
-
servedProject !== undefined && canonicalPath(servedProject) === canonicalPath(projectRoot)
|
|
146
|
-
);
|
|
170
|
+
if (!response.ok) return undefined;
|
|
171
|
+
return servedProject(await response.json());
|
|
147
172
|
} catch {
|
|
148
|
-
return
|
|
173
|
+
return undefined;
|
|
149
174
|
}
|
|
150
175
|
}
|
|
151
176
|
|
|
177
|
+
/**
|
|
178
|
+
* The refusal for a session that IS this project's and cannot be driven,
|
|
179
|
+
* because the project's manifest does not load.
|
|
180
|
+
*
|
|
181
|
+
* A refusal, not an attach: with no readable manifest the editor page itself
|
|
182
|
+
* renders its startup-error screen, so there is no editor and no game behind
|
|
183
|
+
* this door to bind `{ editor, game, page, tools, session }` to. What changed
|
|
184
|
+
* is only that it now says the true thing — the failing file and key — instead
|
|
185
|
+
* of "no live editor session found", which sent operators to start an editor
|
|
186
|
+
* that was already running while their actual defect went unnamed.
|
|
187
|
+
*/
|
|
188
|
+
function manifestRefusal(projectRoot: string, manifestError: string): Error {
|
|
189
|
+
return new Error(
|
|
190
|
+
`@vgai/live: the editor session for ${projectRoot} is live, but its vgai.project.json does ` +
|
|
191
|
+
'not load, so there is no editor or game to drive — the editor page is showing this same ' +
|
|
192
|
+
`error. Fix the manifest and retry; the session recovers on save, no restart needed.\n${manifestError}`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
152
196
|
/**
|
|
153
197
|
* Resolve `projectDir` (default `process.cwd()`) to the port of its already-
|
|
154
198
|
* running `vgai edit` session. Throws a descriptive error (never hangs
|
|
@@ -176,11 +220,22 @@ export async function resolveSession(
|
|
|
176
220
|
// deadline. The live probe remains authoritative, so a stale file cannot
|
|
177
221
|
// attach us to the wrong project or port.
|
|
178
222
|
const localHint = (deps.readProjectSession ?? readProjectSession)(projectRoot);
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
223
|
+
if (localHint) {
|
|
224
|
+
// `deps.verifyProjectSession` is the test-only override and keeps its
|
|
225
|
+
// boolean meaning: "this hint is this project's session, and healthy".
|
|
226
|
+
// With no override we read the probe ourselves, because the same response
|
|
227
|
+
// carries WHY a served project cannot be described.
|
|
228
|
+
if (deps.verifyProjectSession) {
|
|
229
|
+
if (await deps.verifyProjectSession(localHint, projectRoot)) {
|
|
230
|
+
return { port: localHint.port, projectRoot };
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
const served = await probeServedProject(localHint.port);
|
|
234
|
+
if (served?.path != null && canonicalPath(served.path) === canonicalPath(projectRoot)) {
|
|
235
|
+
if (served.manifestError !== null) throw manifestRefusal(projectRoot, served.manifestError);
|
|
236
|
+
return { port: localHint.port, projectRoot };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
184
239
|
}
|
|
185
240
|
|
|
186
241
|
let sessions: EditorSessionInfo[];
|
|
@@ -209,5 +264,7 @@ export async function resolveSession(
|
|
|
209
264
|
);
|
|
210
265
|
}
|
|
211
266
|
|
|
267
|
+
if (match.manifestError != null) throw manifestRefusal(projectRoot, match.manifestError);
|
|
268
|
+
|
|
212
269
|
return { port: match.port, projectRoot };
|
|
213
270
|
}
|