@vgai/sdk 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/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.20",
5
+ "version": "0.5.21",
6
6
  "type": "module",
7
7
  "repository": {
8
8
  "type": "git",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@modelcontextprotocol/sdk": "^1.30.0",
33
- "@vgai/engine": "0.5.20",
33
+ "@vgai/engine": "0.5.21",
34
34
  "playwright": "^1.58.2",
35
35
  "zod": "^4.3.6"
36
36
  }
@@ -179,6 +179,15 @@ export interface EditorSessionInfo {
179
179
  pid: number | null;
180
180
  /** Exact explicitly targeted editor origin/base URL, including protocol and host. */
181
181
  url?: string;
182
+ /**
183
+ * Why this server cannot DESCRIBE the `project` it is serving — its
184
+ * manifest's own parse/validation failure, naming the failing key. `null`
185
+ * when the project reads fine, and `undefined` from a probe that did not ask
186
+ * (or a server too old to say). See {@link servedProjectPath}: a session in
187
+ * this state is still THIS project's session, and callers must report the
188
+ * reason rather than treat it as no session at all.
189
+ */
190
+ manifestError?: string | null;
182
191
  }
183
192
 
184
193
  export interface EditorCommandResult {
@@ -414,6 +423,28 @@ function readRegisteredSessions(): RegistrySessionEntry[] {
414
423
  }
415
424
  }
416
425
 
426
+ /**
427
+ * WHICH PROJECT a `/__editor/project` body says its server is serving.
428
+ *
429
+ * `serving` is that server's own statement of "I AM serving this project, I
430
+ * just cannot describe it" (its manifest is unparseable or fails strict
431
+ * validation) — added to the route precisely because a bare `{ project: null }`
432
+ * is indistinguishable from "no project open". Reading only `project.path`
433
+ * collapses the two, and the cost is that every project-matched command loses
434
+ * a live session the moment a save breaks its manifest, reporting it as
435
+ * belonging to no project rather than as this project's degraded session.
436
+ */
437
+ function servedProjectPath(body: unknown): { path: string | null; manifestError: string | null } {
438
+ const b = body as {
439
+ project?: { path?: string } | null;
440
+ serving?: { path?: string; error?: string } | null;
441
+ };
442
+ return {
443
+ path: b.project?.path ?? b.serving?.path ?? null,
444
+ manifestError: b.project ? null : (b.serving?.error ?? null),
445
+ };
446
+ }
447
+
417
448
  async function fetchJson(url: string, timeoutMs: number): Promise<unknown | undefined> {
418
449
  try {
419
450
  const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
@@ -463,9 +494,15 @@ export class HttpEditorTransport implements EditorTransport {
463
494
  const base = editorUrl.replace(/\/+$/, '');
464
495
  const body = await fetchJson(`${base}/__editor/project`, timeoutMs);
465
496
  if (body === undefined) return undefined;
466
- const project = (body as { project?: { path?: string } | null }).project?.path ?? null;
497
+ const served = servedProjectPath(body);
467
498
  const port = url.port ? Number(url.port) : url.protocol === 'https:' ? 443 : 80;
468
- return { port, project, pid: null, url: base };
499
+ return {
500
+ port,
501
+ project: served.path,
502
+ pid: null,
503
+ url: base,
504
+ manifestError: served.manifestError,
505
+ };
469
506
  }
470
507
 
471
508
  async listSessions(timeoutMs: number): Promise<EditorSessionInfo[]> {
@@ -478,11 +515,12 @@ export class HttpEditorTransport implements EditorTransport {
478
515
  perProbeTimeout,
479
516
  );
480
517
  if (body === undefined) return undefined;
481
- const project = (body as { project?: { path?: string } | null }).project?.path ?? null;
518
+ const served = servedProjectPath(body);
482
519
  const info: EditorSessionInfo = {
483
520
  port: s.port,
484
- project,
521
+ project: served.path,
485
522
  pid: s.pid,
523
+ manifestError: served.manifestError,
486
524
  };
487
525
  return info;
488
526
  }),
@@ -426,15 +426,33 @@ function readRegisteredSessions(): RegistrySessionEntry[] {
426
426
  try {
427
427
  const raw: unknown = JSON.parse(readFileSync(registryFile, 'utf8'));
428
428
  return Array.isArray(raw)
429
- ? raw
430
- .filter(isRegistrySessionEntry)
431
- .filter((s) => pidAlive(s.pid))
429
+ ? raw.filter(isRegistrySessionEntry).filter((s) => pidAlive(s.pid))
432
430
  : [];
433
431
  } catch {
434
432
  return [];
435
433
  }
436
434
  }
437
435
 
436
+ /**
437
+ * WHICH PROJECT a `/__editor/project` body says its server is serving — the
438
+ * play lane's copy of `editor/transport.ts`'s `servedProjectPath` (deliberate
439
+ * light duplicate of the read half, same as `readRegisteredSessions` above).
440
+ *
441
+ * `serving` is that server's own statement of "I AM serving this project, I
442
+ * just cannot describe it" (its manifest is unparseable or fails strict
443
+ * validation) — added to the route precisely because a bare `{ project: null }`
444
+ * is indistinguishable from "no project open". Reading only `project.path`
445
+ * collapses the two, and the cost is that every project-matched command loses
446
+ * a live session the moment a save breaks its manifest.
447
+ */
448
+ function servedProjectPath(body: unknown): string | null {
449
+ const b = body as {
450
+ project?: { path?: string } | null;
451
+ serving?: { path?: string } | null;
452
+ };
453
+ return b.project?.path ?? b.serving?.path ?? null;
454
+ }
455
+
438
456
  async function fetchJson(url: string, timeoutMs: number): Promise<unknown | undefined> {
439
457
  try {
440
458
  const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
@@ -548,7 +566,7 @@ export class HttpPlayTransport implements PlayTransport {
548
566
  const base = editorUrl.replace(/\/+$/, '');
549
567
  const body = await fetchJson(`${base}/__editor/project`, timeoutMs);
550
568
  if (body === undefined) return undefined;
551
- const project = (body as { project?: { path?: string } | null }).project?.path ?? null;
569
+ const project = servedProjectPath(body);
552
570
  const port = url.port ? Number(url.port) : url.protocol === 'https:' ? 443 : 80;
553
571
  return { port, project, pid: null, url: base };
554
572
  }
@@ -563,7 +581,7 @@ export class HttpPlayTransport implements PlayTransport {
563
581
  perProbeTimeout,
564
582
  );
565
583
  if (body === undefined) return undefined;
566
- const project = (body as { project?: { path?: string } | null }).project?.path ?? null;
584
+ const project = servedProjectPath(body);
567
585
  const info: PlaySessionInfo = {
568
586
  port: s.port,
569
587
  project,