@jami-studio/core 0.92.11 → 0.92.13

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.
@@ -1898,6 +1898,7 @@ function ssrStubPlugin(packages: string[]): Plugin | null {
1898
1898
  "useCurrentEditor",
1899
1899
  "useEditor",
1900
1900
  "useLocalRuntime",
1901
+ "useMessagePartReasoning",
1901
1902
  "useMessagePartText",
1902
1903
  "useMessageRuntime",
1903
1904
  "useThread",
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { spawn, type ChildProcess } from "node:child_process";
3
+ import fs from "node:fs";
3
4
  import http from "node:http";
4
5
  import { type WorkspaceAppAudience } from "../shared/workspace-app-audience.js";
5
6
  export interface WorkspaceApp {
@@ -25,6 +26,23 @@ export interface WorkspaceApp {
25
26
  outputTail?: string;
26
27
  installing?: boolean;
27
28
  installAttempted?: boolean;
29
+ /**
30
+ * Built-mode lifecycle. `devMode` marks an app that has been promoted to
31
+ * the vite dev server for the rest of the session (source changed, build
32
+ * failed, or no build could be produced). `building` marks a production
33
+ * build in flight as the app's prep child. `buildAttempted` prevents
34
+ * rebuild loops; `buildChecked` caches the once-per-session staleness scan.
35
+ */
36
+ devMode?: boolean;
37
+ building?: boolean;
38
+ buildAttempted?: boolean;
39
+ buildChecked?: boolean;
40
+ /**
41
+ * Set before an intentional kill (promotion to dev mode) so the exit
42
+ * handler respawns immediately instead of treating it as a crash.
43
+ */
44
+ expectedExit?: boolean;
45
+ promoteWatcher?: fs.FSWatcher;
28
46
  /**
29
47
  * Set true once we've successfully connected to the upstream. After that we
30
48
  * skip the readiness probe on every request; the child server stays
@@ -55,6 +73,31 @@ export interface WorkspaceDevHandle {
55
73
  }
56
74
  export declare function isWorkspaceWatcherLimitError(err: Pick<NodeJS.ErrnoException, "code">): boolean;
57
75
  export declare function shouldEagerStartWorkspaceApps(args?: string[], env?: NodeJS.ProcessEnv): boolean;
76
+ /**
77
+ * Mixed-mode gateway: when enabled (--built / WORKSPACE_BUILT=1), each app
78
+ * boots from its prebuilt production server (`.output/server/index.mjs`,
79
+ * instant, prod-shaped) instead of a vite dev server. The gateway builds an
80
+ * app first when no fresh build exists, and automatically promotes an app to
81
+ * the vite dev server the moment one of its source files changes — so the
82
+ * app being edited gets hot reload while every other app stays on built
83
+ * output. Nothing is manual: editing is the switch.
84
+ */
85
+ export declare function shouldRunBuiltWorkspaceApps(args?: string[], env?: NodeJS.ProcessEnv): boolean;
86
+ /**
87
+ * Whether a path relative to an app dir counts as "source" for built-mode
88
+ * staleness checks and dev-server promotion. Excludes build outputs, data,
89
+ * dependency trees, and every dot-directory/dot-file (`.output`,
90
+ * `.deploy-tmp`, `.react-router`, `.env`, ...) — an `.env` edit needs a
91
+ * restart, not a permanent promotion to vite.
92
+ */
93
+ export declare function isBuiltModeSourcePath(relPath: string): boolean;
94
+ export declare function builtWorkspaceAppServerEntry(appDir: string): string;
95
+ /**
96
+ * Newest mtime across an app's source files (build outputs, data, and
97
+ * node_modules excluded). Used once per session per app to decide whether
98
+ * the prebuilt server is stale and needs a rebuild before serving.
99
+ */
100
+ export declare function newestBuiltModeSourceMtimeMs(dir: string, relBase?: string): number;
58
101
  /**
59
102
  * Whether the gateway should spawn the rest of the apps in the background
60
103
  * after the default app boots. Lazy spawn already covers correctness — this
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-dev.d.ts","sourceRoot":"","sources":["../../src/cli/workspace-dev.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workspace-dev.d.ts","sourceRoot":"","sources":["../../src/cli/workspace-dev.ts"],"names":[],"mappings":"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"}
@@ -40,6 +40,95 @@ export function shouldEagerStartWorkspaceApps(args = [], env = process.env) {
40
40
  env.WORKSPACE_EAGER === "1" ||
41
41
  env.WORKSPACE_EAGER === "true");
42
42
  }
43
+ /**
44
+ * Mixed-mode gateway: when enabled (--built / WORKSPACE_BUILT=1), each app
45
+ * boots from its prebuilt production server (`.output/server/index.mjs`,
46
+ * instant, prod-shaped) instead of a vite dev server. The gateway builds an
47
+ * app first when no fresh build exists, and automatically promotes an app to
48
+ * the vite dev server the moment one of its source files changes — so the
49
+ * app being edited gets hot reload while every other app stays on built
50
+ * output. Nothing is manual: editing is the switch.
51
+ */
52
+ export function shouldRunBuiltWorkspaceApps(args = [], env = process.env) {
53
+ if (args.includes("--no-built"))
54
+ return false;
55
+ if (env.WORKSPACE_BUILT === "0" || env.WORKSPACE_BUILT === "false") {
56
+ return false;
57
+ }
58
+ return (args.includes("--built") ||
59
+ env.WORKSPACE_BUILT === "1" ||
60
+ env.WORKSPACE_BUILT === "true");
61
+ }
62
+ const BUILT_MODE_IGNORED_SEGMENTS = new Set([
63
+ "node_modules",
64
+ "build",
65
+ "dist",
66
+ "data",
67
+ "media",
68
+ "coverage",
69
+ "test-results",
70
+ "playwright-report",
71
+ ]);
72
+ const BUILT_MODE_IGNORED_FILE_PATTERN = /\.(log|tmp|db|db-wal|db-shm|sqlite|sqlite-wal|sqlite-shm)$/i;
73
+ /**
74
+ * Whether a path relative to an app dir counts as "source" for built-mode
75
+ * staleness checks and dev-server promotion. Excludes build outputs, data,
76
+ * dependency trees, and every dot-directory/dot-file (`.output`,
77
+ * `.deploy-tmp`, `.react-router`, `.env`, ...) — an `.env` edit needs a
78
+ * restart, not a permanent promotion to vite.
79
+ */
80
+ export function isBuiltModeSourcePath(relPath) {
81
+ if (!relPath)
82
+ return false;
83
+ const segments = relPath.split(/[\\/]/).filter(Boolean);
84
+ if (segments.length === 0)
85
+ return false;
86
+ for (const segment of segments) {
87
+ if (segment.startsWith("."))
88
+ return false;
89
+ if (BUILT_MODE_IGNORED_SEGMENTS.has(segment))
90
+ return false;
91
+ }
92
+ return !BUILT_MODE_IGNORED_FILE_PATTERN.test(relPath);
93
+ }
94
+ export function builtWorkspaceAppServerEntry(appDir) {
95
+ return path.join(appDir, ".output", "server", "index.mjs");
96
+ }
97
+ /**
98
+ * Newest mtime across an app's source files (build outputs, data, and
99
+ * node_modules excluded). Used once per session per app to decide whether
100
+ * the prebuilt server is stale and needs a rebuild before serving.
101
+ */
102
+ export function newestBuiltModeSourceMtimeMs(dir, relBase = "") {
103
+ let entries;
104
+ try {
105
+ entries = fs.readdirSync(dir, { withFileTypes: true });
106
+ }
107
+ catch {
108
+ return 0;
109
+ }
110
+ let newest = 0;
111
+ for (const entry of entries) {
112
+ const rel = relBase ? `${relBase}/${entry.name}` : entry.name;
113
+ if (entry.isDirectory()) {
114
+ if (entry.name.startsWith(".") ||
115
+ BUILT_MODE_IGNORED_SEGMENTS.has(entry.name)) {
116
+ continue;
117
+ }
118
+ newest = Math.max(newest, newestBuiltModeSourceMtimeMs(path.join(dir, entry.name), rel));
119
+ continue;
120
+ }
121
+ if (!entry.isFile() || !isBuiltModeSourcePath(rel))
122
+ continue;
123
+ try {
124
+ newest = Math.max(newest, fs.statSync(path.join(dir, entry.name)).mtimeMs);
125
+ }
126
+ catch {
127
+ // File vanished mid-scan — ignore.
128
+ }
129
+ }
130
+ return newest;
131
+ }
43
132
  /**
44
133
  * Whether the gateway should spawn the rest of the apps in the background
45
134
  * after the default app boots. Lazy spawn already covers correctness — this
@@ -413,76 +502,78 @@ function renderStartingApp(app) {
413
502
  ? `The workspace gateway will retry in ${Math.max(1, Math.ceil((failure.nextRetryAt - Date.now()) / 1_000))}s. Fix the error below or stop the server with Ctrl+C.`
414
503
  : app.installing
415
504
  ? "The workspace gateway is installing this app's dependencies before starting it."
416
- : "The workspace gateway is waking this app's dev server.";
505
+ : app.building
506
+ ? "The workspace gateway is building this app's production server before serving it."
507
+ : "The workspace gateway is waking this app's dev server.";
417
508
  const failureOutput = failure?.output.trim();
418
- return `<!doctype html>
419
- <html>
420
- <head>
421
- <meta charset="utf-8" />
422
- <meta name="viewport" content="width=device-width, initial-scale=1" />
423
- <meta http-equiv="refresh" content="${refreshSeconds}" />
424
- <title>${title}</title>
425
- <meta name="color-scheme" content="light dark" />
426
- <style>
427
- :root { --bg: #fafafa; --fg: #171717; --muted: #737373; --bar-bg: #e5e5e5; --bar-fill: #171717; --danger: #dc2626; --code-bg: #171717; --code-fg: #fafafa; }
428
- @media (prefers-color-scheme: dark) { :root { --bg: #0a0a0a; --fg: #fafafa; --muted: #a3a3a3; --bar-bg: #262626; --bar-fill: #fafafa; --danger: #f87171; --code-bg: #171717; --code-fg: #f5f5f5; } }
429
- body { min-height: 100vh; margin: 0; display: grid; place-items: center; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--fg); }
430
- main { width: min(680px, calc(100vw - 48px)); }
431
- .bar { height: 3px; overflow: hidden; border-radius: 999px; background: var(--bar-bg); }
432
- .bar::before { content: ""; display: block; height: 100%; width: 42%; border-radius: inherit; background: var(--bar-fill); animation: load 1s ease-in-out infinite; }
433
- main.failed .bar::before { width: 100%; background: var(--danger); animation: none; }
434
- p { color: var(--muted); }
435
- pre { max-height: min(46vh, 360px); overflow: auto; margin-top: 20px; padding: 14px 16px; border-radius: 8px; background: var(--code-bg); color: var(--code-fg); font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; white-space: pre-wrap; word-break: break-word; }
436
- @keyframes load { 0% { transform: translateX(-105%); } 100% { transform: translateX(245%); } }
437
- </style>
438
- <script>
439
- (() => {
440
- const reload = () => window.location.reload();
441
- setTimeout(reload, ${JSON.stringify(refreshScriptDelay)});
442
- setInterval(reload, ${JSON.stringify(Math.max(refreshScriptDelay, 3_000))});
443
- })();
444
- </script>
445
- </head>
446
- <body>
447
- <main class="${failure ? "failed" : ""}">
448
- <div class="bar"></div>
449
- <h1>${title}</h1>
450
- <p>${escapeHtml(message)}</p>
451
- ${failureOutput ? `<pre>${escapeHtml(failureOutput)}</pre>` : ""}
452
- </main>
453
- </body>
509
+ return `<!doctype html>
510
+ <html>
511
+ <head>
512
+ <meta charset="utf-8" />
513
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
514
+ <meta http-equiv="refresh" content="${refreshSeconds}" />
515
+ <title>${title}</title>
516
+ <meta name="color-scheme" content="light dark" />
517
+ <style>
518
+ :root { --bg: #fafafa; --fg: #171717; --muted: #737373; --bar-bg: #e5e5e5; --bar-fill: #171717; --danger: #dc2626; --code-bg: #171717; --code-fg: #fafafa; }
519
+ @media (prefers-color-scheme: dark) { :root { --bg: #0a0a0a; --fg: #fafafa; --muted: #a3a3a3; --bar-bg: #262626; --bar-fill: #fafafa; --danger: #f87171; --code-bg: #171717; --code-fg: #f5f5f5; } }
520
+ body { min-height: 100vh; margin: 0; display: grid; place-items: center; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--fg); }
521
+ main { width: min(680px, calc(100vw - 48px)); }
522
+ .bar { height: 3px; overflow: hidden; border-radius: 999px; background: var(--bar-bg); }
523
+ .bar::before { content: ""; display: block; height: 100%; width: 42%; border-radius: inherit; background: var(--bar-fill); animation: load 1s ease-in-out infinite; }
524
+ main.failed .bar::before { width: 100%; background: var(--danger); animation: none; }
525
+ p { color: var(--muted); }
526
+ pre { max-height: min(46vh, 360px); overflow: auto; margin-top: 20px; padding: 14px 16px; border-radius: 8px; background: var(--code-bg); color: var(--code-fg); font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; white-space: pre-wrap; word-break: break-word; }
527
+ @keyframes load { 0% { transform: translateX(-105%); } 100% { transform: translateX(245%); } }
528
+ </style>
529
+ <script>
530
+ (() => {
531
+ const reload = () => window.location.reload();
532
+ setTimeout(reload, ${JSON.stringify(refreshScriptDelay)});
533
+ setInterval(reload, ${JSON.stringify(Math.max(refreshScriptDelay, 3_000))});
534
+ })();
535
+ </script>
536
+ </head>
537
+ <body>
538
+ <main class="${failure ? "failed" : ""}">
539
+ <div class="bar"></div>
540
+ <h1>${title}</h1>
541
+ <p>${escapeHtml(message)}</p>
542
+ ${failureOutput ? `<pre>${escapeHtml(failureOutput)}</pre>` : ""}
543
+ </main>
544
+ </body>
454
545
  </html>`;
455
546
  }
456
547
  function renderIndex(apps) {
457
- return `<!doctype html>
458
- <html>
459
- <head>
460
- <meta charset="utf-8" />
461
- <meta name="viewport" content="width=device-width, initial-scale=1" />
462
- <title>Agent-Native Workspace</title>
463
- <meta name="color-scheme" content="light dark" />
464
- <style>
465
- :root { --bg: #fafafa; --fg: #171717; --muted: #737373; --card-bg: #ffffff; --card-border: #d4d4d4; }
466
- @media (prefers-color-scheme: dark) { :root { --bg: #0a0a0a; --fg: #fafafa; --muted: #a3a3a3; --card-bg: #171717; --card-border: #262626; } }
467
- body { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 0; padding: 32px; background: var(--bg); color: var(--fg); }
468
- main { max-width: 760px; margin: 0 auto; }
469
- a { color: inherit; text-decoration: none; }
470
- .grid { display: grid; gap: 12px; margin-top: 20px; }
471
- .card { display: flex; justify-content: space-between; border: 1px solid var(--card-border); border-radius: 8px; padding: 14px 16px; background: var(--card-bg); }
472
- .muted { color: var(--muted); }
473
- </style>
474
- </head>
475
- <body>
476
- <main>
477
- <h1>Agent-Native Workspace</h1>
478
- <p class="muted">Open an app below. Dispatch is the workspace control plane when installed.</p>
479
- <div class="grid">
548
+ return `<!doctype html>
549
+ <html>
550
+ <head>
551
+ <meta charset="utf-8" />
552
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
553
+ <title>Agent-Native Workspace</title>
554
+ <meta name="color-scheme" content="light dark" />
555
+ <style>
556
+ :root { --bg: #fafafa; --fg: #171717; --muted: #737373; --card-bg: #ffffff; --card-border: #d4d4d4; }
557
+ @media (prefers-color-scheme: dark) { :root { --bg: #0a0a0a; --fg: #fafafa; --muted: #a3a3a3; --card-bg: #171717; --card-border: #262626; } }
558
+ body { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 0; padding: 32px; background: var(--bg); color: var(--fg); }
559
+ main { max-width: 760px; margin: 0 auto; }
560
+ a { color: inherit; text-decoration: none; }
561
+ .grid { display: grid; gap: 12px; margin-top: 20px; }
562
+ .card { display: flex; justify-content: space-between; border: 1px solid var(--card-border); border-radius: 8px; padding: 14px 16px; background: var(--card-bg); }
563
+ .muted { color: var(--muted); }
564
+ </style>
565
+ </head>
566
+ <body>
567
+ <main>
568
+ <h1>Agent-Native Workspace</h1>
569
+ <p class="muted">Open an app below. Dispatch is the workspace control plane when installed.</p>
570
+ <div class="grid">
480
571
  ${apps
481
572
  .map((app) => `<a class="card" href="/${app.id}"><strong>${escapeHtml(app.name)}</strong><span class="muted">/${escapeHtml(app.id)}</span></a>`)
482
- .join("")}
483
- </div>
484
- </main>
485
- </body>
573
+ .join("")}
574
+ </div>
575
+ </main>
576
+ </body>
486
577
  </html>`;
487
578
  }
488
579
  function hasLocalBin(dir, command) {
@@ -505,6 +596,7 @@ export async function runWorkspaceDev(options = {}) {
505
596
  const appPortStart = Number(env.WORKSPACE_APP_PORT_START || DEFAULT_APP_PORT_START);
506
597
  const forceVite = env.WORKSPACE_VITE_FORCE === "1";
507
598
  const eager = shouldEagerStartWorkspaceApps(args, env);
599
+ const builtMode = shouldRunBuiltWorkspaceApps(args, env);
508
600
  const pollingMode = pollingFileWatcherMode(env, root);
509
601
  const usePollingFileWatcher = pollingMode === "enable";
510
602
  const proxyReadyTimeoutMs = Number(env.WORKSPACE_PROXY_READY_TIMEOUT_MS ?? 30_000);
@@ -642,9 +734,61 @@ export async function runWorkspaceDev(options = {}) {
642
734
  app.outputTail = undefined;
643
735
  const basePath = `/${app.id}`;
644
736
  const shouldInstall = !app.installAttempted && !hasLocalBin(app.dir, "vite");
645
- const childArgs = shouldInstall
646
- ? ["--dir", root, "install", "--no-frozen-lockfile", "--prefer-offline"]
647
- : [
737
+ const builtEntry = builtWorkspaceAppServerEntry(app.dir);
738
+ let shouldBuild = false;
739
+ let runBuilt = false;
740
+ if (builtMode && !shouldInstall && !app.devMode) {
741
+ let hasFreshBuild = false;
742
+ if (fs.existsSync(builtEntry)) {
743
+ if (app.buildChecked) {
744
+ hasFreshBuild = true;
745
+ }
746
+ else {
747
+ app.buildChecked = true;
748
+ try {
749
+ hasFreshBuild =
750
+ newestBuiltModeSourceMtimeMs(app.dir) <=
751
+ fs.statSync(builtEntry).mtimeMs;
752
+ }
753
+ catch {
754
+ hasFreshBuild = false;
755
+ }
756
+ if (!hasFreshBuild) {
757
+ stdout.write(`[workspace] /${app.id}: production build is stale \u2014 rebuilding before serving\n`);
758
+ }
759
+ }
760
+ }
761
+ if (hasFreshBuild) {
762
+ runBuilt = true;
763
+ }
764
+ else if (!app.buildAttempted) {
765
+ shouldBuild = true;
766
+ }
767
+ else {
768
+ app.devMode = true;
769
+ stdout.write(`[workspace] /${app.id}: no usable production build \u2014 falling back to the vite dev server\n`);
770
+ }
771
+ }
772
+ let spawnCommand = "pnpm";
773
+ let childArgs;
774
+ if (shouldInstall) {
775
+ childArgs = [
776
+ "--dir",
777
+ root,
778
+ "install",
779
+ "--no-frozen-lockfile",
780
+ "--prefer-offline",
781
+ ];
782
+ }
783
+ else if (shouldBuild) {
784
+ childArgs = ["--dir", app.dir, "run", "build"];
785
+ }
786
+ else if (runBuilt) {
787
+ spawnCommand = process.execPath;
788
+ childArgs = [builtEntry];
789
+ }
790
+ else {
791
+ childArgs = [
648
792
  "--dir",
649
793
  app.dir,
650
794
  "exec",
@@ -656,10 +800,17 @@ export async function runWorkspaceDev(options = {}) {
656
800
  "--strictPort",
657
801
  ...(forceVite ? ["--force"] : []),
658
802
  ];
803
+ }
659
804
  if (shouldInstall) {
660
805
  stdout.write(`[workspace] Installing dependencies before starting /${app.id}\n`);
661
806
  }
662
- const child = spawnProcess("pnpm", childArgs, {
807
+ else if (shouldBuild) {
808
+ stdout.write(`[workspace] Building /${app.id} for built-mode serving (one-time; subsequent boots are instant)\n`);
809
+ }
810
+ else if (runBuilt) {
811
+ stdout.write(`[workspace] /${app.id}: serving prebuilt server\n`);
812
+ }
813
+ const child = spawnProcess(spawnCommand, childArgs, {
663
814
  cwd: root,
664
815
  stdio: ["ignore", "pipe", "pipe"],
665
816
  env: devWatcherEnv({
@@ -683,10 +834,14 @@ export async function runWorkspaceDev(options = {}) {
683
834
  VITE_WORKSPACE_GATEWAY_URL: gatewayUrl,
684
835
  PORT: String(app.port),
685
836
  WORKSPACE_GATEWAY_URL: gatewayUrl,
837
+ ...(runBuilt ? { HOST: "127.0.0.1" } : {}),
686
838
  }, pollingMode),
687
839
  });
688
840
  app.process = child;
689
841
  app.installing = shouldInstall;
842
+ app.building = shouldBuild;
843
+ if (runBuilt)
844
+ attachPromoteWatcher(app);
690
845
  const prefix = `[${app.id}]`;
691
846
  const stableTimer = setTimeout(() => {
692
847
  app.restartAttempts = 0;
@@ -707,6 +862,7 @@ export async function runWorkspaceDev(options = {}) {
707
862
  const wasInstalling = app.installing;
708
863
  app.process = undefined;
709
864
  app.installing = false;
865
+ app.building = false;
710
866
  app.ready = false;
711
867
  app.readinessProbe = undefined;
712
868
  if (app.restartTimer || shuttingDown)
@@ -724,21 +880,45 @@ export async function runWorkspaceDev(options = {}) {
724
880
  child.on("exit", (code, signal) => {
725
881
  clearTimeout(stableTimer);
726
882
  const wasInstalling = app.installing;
883
+ const wasBuilding = app.building;
727
884
  app.process = undefined;
728
885
  app.installing = false;
886
+ app.building = false;
729
887
  app.ready = false;
730
888
  app.readinessProbe = undefined;
889
+ if (app.expectedExit) {
890
+ // Intentional kill (promotion to the vite dev server). Respawn
891
+ // immediately instead of treating it as a crash.
892
+ app.expectedExit = false;
893
+ if (!shuttingDown && !app.restartTimer)
894
+ startApp(app);
895
+ return;
896
+ }
731
897
  if (app.restartTimer)
732
898
  return;
733
899
  if (code === 0 || shuttingDown) {
734
- if (wasInstalling && code === 0 && !shuttingDown) {
735
- app.installAttempted = true;
736
- startApp(app);
900
+ if (code === 0 && !shuttingDown) {
901
+ if (wasInstalling) {
902
+ app.installAttempted = true;
903
+ startApp(app);
904
+ }
905
+ else if (wasBuilding) {
906
+ app.buildAttempted = true;
907
+ startApp(app);
908
+ }
737
909
  }
738
910
  return;
739
911
  }
740
912
  if (wasInstalling)
741
913
  app.installAttempted = false;
914
+ if (wasBuilding) {
915
+ // A failed production build must never brick the app — fall back to
916
+ // the vite dev server, which surfaces the error with hot reload.
917
+ app.devMode = true;
918
+ stderr.write(`[${app.id}] production build failed (exit ${code}); falling back to the vite dev server\n`);
919
+ startApp(app);
920
+ return;
921
+ }
742
922
  scheduleAppRestart(app, {
743
923
  code,
744
924
  signal,
@@ -748,6 +928,47 @@ export async function runWorkspaceDev(options = {}) {
748
928
  });
749
929
  });
750
930
  }
931
+ /**
932
+ * Built-mode promotion: the moment one of an app's source files changes,
933
+ * swap its prebuilt server for the vite dev server on the same port. The
934
+ * proxy doesn't care what listens upstream, so the swap is invisible apart
935
+ * from a brief wake page. Sticky for the rest of the session.
936
+ */
937
+ function promoteAppToDevServer(app, changedPath) {
938
+ if (app.devMode || shuttingDown)
939
+ return;
940
+ app.devMode = true;
941
+ app.promoteWatcher?.close();
942
+ app.promoteWatcher = undefined;
943
+ stdout.write(`[workspace] /${app.id}: source changed (${changedPath}) \u2014 promoting to the vite dev server with hot reload\n`);
944
+ app.ready = false;
945
+ app.readinessProbe = undefined;
946
+ if (app.process && !app.process.killed) {
947
+ app.expectedExit = true;
948
+ killAppProcessTree(app.process);
949
+ return;
950
+ }
951
+ startApp(app);
952
+ }
953
+ function attachPromoteWatcher(app) {
954
+ if (app.promoteWatcher || app.devMode)
955
+ return;
956
+ try {
957
+ const watcher = fs.watch(app.dir, { recursive: true }, (_event, filename) => {
958
+ const rel = filename ? String(filename) : "";
959
+ if (!isBuiltModeSourcePath(rel))
960
+ return;
961
+ promoteAppToDevServer(app, rel);
962
+ });
963
+ watcher.on("error", (err) => {
964
+ handleWatcherError(err);
965
+ });
966
+ app.promoteWatcher = watcher;
967
+ }
968
+ catch (err) {
969
+ handleWatcherError(err);
970
+ }
971
+ }
751
972
  function scheduleAppRestart(app, input) {
752
973
  if (shuttingDown || app.restartTimer)
753
974
  return;
@@ -772,8 +993,9 @@ export async function runWorkspaceDev(options = {}) {
772
993
  app.restartTimer.unref();
773
994
  }
774
995
  function failAppStartupTimeout(app) {
775
- if (app.installing || app.ready || app.restartTimer)
996
+ if (app.installing || app.building || app.ready || app.restartTimer) {
776
997
  return;
998
+ }
777
999
  const timeout = formatProxyReadyTimeout(proxyReadyTimeoutMs);
778
1000
  const message = `Timed out waiting ${timeout} for /${app.id} to return ` +
779
1001
  `an HTTP response on 127.0.0.1:${app.port}.`;
@@ -826,8 +1048,9 @@ export async function runWorkspaceDev(options = {}) {
826
1048
  return false;
827
1049
  }
828
1050
  function ensureReadinessProbe(app) {
829
- if (app.ready || app.readinessProbe || app.installing)
1051
+ if (app.ready || app.readinessProbe || app.installing || app.building) {
830
1052
  return;
1053
+ }
831
1054
  app.readinessProbe = waitForHttpReady(app, Date.now() + proxyReadyTimeoutMs)
832
1055
  .then((ready) => {
833
1056
  if (ready) {
@@ -1183,7 +1406,7 @@ export async function runWorkspaceDev(options = {}) {
1183
1406
  stdout.write(`[workspace] Default: ${redirectRootToDefault ? `${gatewayUrl}/${defaultApp}` : gatewayUrl}\n`);
1184
1407
  stdout.write(`[workspace] Gateway: ${gatewayUrl}\n`);
1185
1408
  const prewarming = shouldPrewarmWorkspaceApps(args, env);
1186
- stdout.write(`[workspace] Mode: ${eager ? "eager" : prewarming ? "lazy+prewarm" : "lazy"}\n`);
1409
+ stdout.write(`[workspace] Mode: ${eager ? "eager" : prewarming ? "lazy+prewarm" : "lazy"}${builtMode ? "+built (prebuilt servers; edits promote to vite)" : ""}\n`);
1187
1410
  for (const app of apps) {
1188
1411
  stdout.write(`[workspace] ${app.id}: /${app.id} -> 127.0.0.1:${app.port}\n`);
1189
1412
  }
@@ -1203,6 +1426,8 @@ export async function runWorkspaceDev(options = {}) {
1203
1426
  shuttingDown = true;
1204
1427
  server.close();
1205
1428
  for (const app of apps) {
1429
+ app.promoteWatcher?.close();
1430
+ app.promoteWatcher = undefined;
1206
1431
  killAppProcessTree(app.process);
1207
1432
  }
1208
1433
  if (syncTimer)