@superblocksteam/sdk 2.0.130-next.3 → 2.0.130-next.5

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.
Files changed (45) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/cli-replacement/dependency-install-classifier.d.mts.map +1 -1
  3. package/dist/cli-replacement/dependency-install-classifier.mjs +11 -0
  4. package/dist/cli-replacement/dependency-install-classifier.mjs.map +1 -1
  5. package/dist/cli-replacement/dependency-install-classifier.test.mjs +26 -0
  6. package/dist/cli-replacement/dependency-install-classifier.test.mjs.map +1 -1
  7. package/dist/cli-replacement/dev-s3-restore.test.mjs +1 -0
  8. package/dist/cli-replacement/dev-s3-restore.test.mjs.map +1 -1
  9. package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs +1 -0
  10. package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs.map +1 -1
  11. package/dist/cli-replacement/dev.d.mts.map +1 -1
  12. package/dist/cli-replacement/dev.mjs +70 -4
  13. package/dist/cli-replacement/dev.mjs.map +1 -1
  14. package/dist/cli-replacement/disk-space.d.mts +86 -0
  15. package/dist/cli-replacement/disk-space.d.mts.map +1 -0
  16. package/dist/cli-replacement/disk-space.mjs +133 -0
  17. package/dist/cli-replacement/disk-space.mjs.map +1 -0
  18. package/dist/cli-replacement/disk-space.test.d.mts +2 -0
  19. package/dist/cli-replacement/disk-space.test.d.mts.map +1 -0
  20. package/dist/cli-replacement/disk-space.test.mjs +186 -0
  21. package/dist/cli-replacement/disk-space.test.mjs.map +1 -0
  22. package/dist/dev-utils/dev-server-metrics.d.mts +11 -0
  23. package/dist/dev-utils/dev-server-metrics.d.mts.map +1 -1
  24. package/dist/dev-utils/dev-server-metrics.mjs +18 -0
  25. package/dist/dev-utils/dev-server-metrics.mjs.map +1 -1
  26. package/dist/telemetry/memory-metrics.d.ts +7 -0
  27. package/dist/telemetry/memory-metrics.d.ts.map +1 -1
  28. package/dist/telemetry/memory-metrics.js +221 -44
  29. package/dist/telemetry/memory-metrics.js.map +1 -1
  30. package/dist/telemetry/memory-metrics.test.d.ts +2 -0
  31. package/dist/telemetry/memory-metrics.test.d.ts.map +1 -0
  32. package/dist/telemetry/memory-metrics.test.js +256 -0
  33. package/dist/telemetry/memory-metrics.test.js.map +1 -0
  34. package/package.json +6 -6
  35. package/src/cli-replacement/dependency-install-classifier.mts +14 -0
  36. package/src/cli-replacement/dependency-install-classifier.test.mts +39 -0
  37. package/src/cli-replacement/dev-s3-restore.test.mts +1 -0
  38. package/src/cli-replacement/dev-startup-git-before-dbfs-order.test.mts +1 -0
  39. package/src/cli-replacement/dev.mts +90 -10
  40. package/src/cli-replacement/disk-space.mts +216 -0
  41. package/src/cli-replacement/disk-space.test.mts +246 -0
  42. package/src/dev-utils/dev-server-metrics.mts +20 -0
  43. package/src/telemetry/memory-metrics.test.ts +363 -0
  44. package/src/telemetry/memory-metrics.ts +329 -60
  45. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,256 @@
1
+ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+ import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ const h = vi.hoisted(() => {
6
+ const gauges = [];
7
+ const observableCounters = [];
8
+ const counters = [];
9
+ const histograms = [];
10
+ return {
11
+ gauges,
12
+ observableCounters,
13
+ counters,
14
+ histograms,
15
+ throwOnGaugeName: undefined,
16
+ delayMonitor: {
17
+ enable: vi.fn(),
18
+ disable: vi.fn(),
19
+ reset: vi.fn(),
20
+ mean: 2_000_000,
21
+ max: 80_000_000,
22
+ percentile: vi.fn((p) => p * 1_000_000),
23
+ },
24
+ gcCallback: undefined,
25
+ elu: vi.fn(),
26
+ addBatchObservableCallback: vi.fn((callback, callbackInstruments) => {
27
+ for (const instrument of callbackInstruments) {
28
+ const captured = [...gauges, ...observableCounters].find((g) => g.name === instrument.name);
29
+ if (captured)
30
+ captured.callback = callback;
31
+ }
32
+ }),
33
+ removeBatchObservableCallback: vi.fn(),
34
+ };
35
+ });
36
+ vi.mock("node:perf_hooks", async () => {
37
+ const actual = await vi.importActual("node:perf_hooks");
38
+ return {
39
+ ...actual,
40
+ PerformanceObserver: class {
41
+ constructor(callback) {
42
+ h.gcCallback = callback;
43
+ }
44
+ observe = vi.fn();
45
+ disconnect = vi.fn();
46
+ },
47
+ monitorEventLoopDelay: vi.fn(() => h.delayMonitor),
48
+ performance: {
49
+ ...actual.performance,
50
+ eventLoopUtilization: h.elu,
51
+ },
52
+ };
53
+ });
54
+ vi.mock("./index.js", () => ({
55
+ getMeter: () => ({
56
+ createObservableGauge: (name) => {
57
+ if (h.throwOnGaugeName === name) {
58
+ throw new Error(`createObservableGauge failed for ${name}`);
59
+ }
60
+ const gauge = { name };
61
+ h.gauges.push(gauge);
62
+ return gauge;
63
+ },
64
+ createObservableCounter: (name) => {
65
+ const counter = { name };
66
+ h.observableCounters.push(counter);
67
+ return counter;
68
+ },
69
+ createCounter: (name) => {
70
+ const counter = { name, adds: [] };
71
+ h.counters.push(counter);
72
+ return {
73
+ add: (value, attrs) => counter.adds.push({ value, attrs }),
74
+ };
75
+ },
76
+ createHistogram: (name, options) => {
77
+ const histogram = { name, options, records: [] };
78
+ h.histograms.push(histogram);
79
+ return {
80
+ record: (value, attrs) => histogram.records.push({ value, attrs }),
81
+ };
82
+ },
83
+ addBatchObservableCallback: h.addBatchObservableCallback,
84
+ removeBatchObservableCallback: h.removeBatchObservableCallback,
85
+ }),
86
+ }));
87
+ function observeAll() {
88
+ const observations = [];
89
+ const callback = h.gauges.find((g) => g.callback)?.callback;
90
+ if (!callback)
91
+ throw new Error("missing observable callback");
92
+ callback({
93
+ observe: (gauge, value, attrs) => observations.push({ name: gauge.name, value, attrs }),
94
+ });
95
+ return observations;
96
+ }
97
+ describe("memory metrics", () => {
98
+ beforeEach(async () => {
99
+ vi.resetModules();
100
+ h.gauges.length = 0;
101
+ h.observableCounters.length = 0;
102
+ h.counters.length = 0;
103
+ h.histograms.length = 0;
104
+ h.throwOnGaugeName = undefined;
105
+ h.delayMonitor.enable.mockClear();
106
+ h.delayMonitor.disable.mockClear();
107
+ h.delayMonitor.reset.mockClear();
108
+ h.delayMonitor.percentile.mockClear();
109
+ h.gcCallback = undefined;
110
+ h.elu.mockReset();
111
+ h.elu
112
+ .mockReturnValueOnce({ idle: 0, active: 0, utilization: 0 })
113
+ .mockReturnValueOnce({ idle: 25, active: 75, utilization: 0.75 })
114
+ .mockReturnValue({ idle: 100, active: 200, utilization: 0.67 });
115
+ h.addBatchObservableCallback.mockClear();
116
+ h.removeBatchObservableCallback.mockClear();
117
+ });
118
+ it("observes event-loop, V8 heap, and memory-pressure gauges", async () => {
119
+ const { startMemoryMetrics, stopMemoryMetrics } = await import("./memory-metrics.js");
120
+ startMemoryMetrics();
121
+ const observations = observeAll();
122
+ stopMemoryMetrics();
123
+ expect(h.delayMonitor.enable).toHaveBeenCalledOnce();
124
+ expect(observations.map((o) => o.name)).toEqual(expect.arrayContaining([
125
+ "nodejs.event_loop.delay.max_ms",
126
+ "nodejs.event_loop.delay.p99_ms",
127
+ "nodejs.event_loop.utilization",
128
+ "nodejs.memory.heap_limit_bytes",
129
+ "nodejs.memory.heap_used_ratio",
130
+ ]));
131
+ expect(h.observableCounters.map((counter) => counter.name)).toEqual([
132
+ "nodejs.process.cpu.user_seconds",
133
+ "nodejs.process.cpu.system_seconds",
134
+ ]);
135
+ expect(observations.find((o) => o.name === "nodejs.event_loop.delay.max_ms")
136
+ ?.value).toBe(80);
137
+ expect(observations.find((o) => o.name === "nodejs.event_loop.utilization")
138
+ ?.value).toBe(0.75);
139
+ expect(h.delayMonitor.reset).toHaveBeenCalledOnce();
140
+ expect(h.delayMonitor.disable).toHaveBeenCalledOnce();
141
+ });
142
+ it("cleans up the event-loop delay monitor when setup fails", async () => {
143
+ const { startMemoryMetrics, stopMemoryMetrics } = await import("./memory-metrics.js");
144
+ h.throwOnGaugeName = "nodejs.memory.rss_bytes";
145
+ expect(() => startMemoryMetrics()).toThrow("createObservableGauge failed for nodejs.memory.rss_bytes");
146
+ expect(h.delayMonitor.disable).toHaveBeenCalledOnce();
147
+ h.throwOnGaugeName = undefined;
148
+ startMemoryMetrics();
149
+ stopMemoryMetrics();
150
+ expect(h.delayMonitor.enable).toHaveBeenCalledOnce();
151
+ expect(h.delayMonitor.disable).toHaveBeenCalledTimes(2);
152
+ });
153
+ it("resets event-loop delay even when observation fails", async () => {
154
+ const { startMemoryMetrics, stopMemoryMetrics } = await import("./memory-metrics.js");
155
+ startMemoryMetrics();
156
+ const callback = h.gauges.find((g) => g.callback)?.callback;
157
+ if (!callback)
158
+ throw new Error("missing observable callback");
159
+ expect(() => callback({
160
+ observe: (instrument) => {
161
+ if (instrument.name === "nodejs.memory.rss_bytes") {
162
+ throw new Error("observe failed");
163
+ }
164
+ },
165
+ })).toThrow("observe failed");
166
+ stopMemoryMetrics();
167
+ expect(h.delayMonitor.reset).toHaveBeenCalledOnce();
168
+ });
169
+ it("does not register metrics twice until stopped", async () => {
170
+ const { startMemoryMetrics, stopMemoryMetrics } = await import("./memory-metrics.js");
171
+ startMemoryMetrics();
172
+ startMemoryMetrics();
173
+ stopMemoryMetrics();
174
+ startMemoryMetrics();
175
+ stopMemoryMetrics();
176
+ expect(h.delayMonitor.enable).toHaveBeenCalledTimes(2);
177
+ expect(h.addBatchObservableCallback).toHaveBeenCalledTimes(2);
178
+ expect(h.removeBatchObservableCallback).toHaveBeenCalledTimes(2);
179
+ });
180
+ it("records GC count and duration with bounded kind labels", async () => {
181
+ const { startMemoryMetrics, stopMemoryMetrics } = await import("./memory-metrics.js");
182
+ startMemoryMetrics();
183
+ h.gcCallback?.({
184
+ getEntries: () => [
185
+ { detail: { kind: 1 }, duration: 12 },
186
+ { detail: { kind: 4 }, duration: 34 },
187
+ { kind: 999, duration: 56 },
188
+ ],
189
+ });
190
+ stopMemoryMetrics();
191
+ const gcCounter = h.counters.find((c) => c.name === "nodejs.gc.count");
192
+ const gcDuration = h.histograms.find((histogram) => histogram.name === "nodejs.gc.duration_ms");
193
+ expect(gcCounter?.adds.map((a) => a.attrs)).toEqual([
194
+ { kind: "minor" },
195
+ { kind: "major" },
196
+ { kind: "unknown" },
197
+ ]);
198
+ expect(gcDuration?.records).toEqual([
199
+ { value: 12, attrs: { kind: "minor" } },
200
+ { value: 34, attrs: { kind: "major" } },
201
+ { value: 56, attrs: { kind: "unknown" } },
202
+ ]);
203
+ expect(gcDuration?.options).toEqual({
204
+ description: "Garbage collection duration by collection kind",
205
+ unit: "ms",
206
+ advice: {
207
+ explicitBucketBoundaries: [0.5, 1, 2, 5, 10, 25, 50, 100, 250, 500],
208
+ },
209
+ });
210
+ });
211
+ });
212
+ describe("readCgroupMemory", () => {
213
+ beforeEach(() => {
214
+ vi.resetModules();
215
+ });
216
+ it("reads cgroup v2 memory usage and ratio", async () => {
217
+ const root = mkdtempSync(path.join(tmpdir(), "sb-cgroup-"));
218
+ writeFileSync(path.join(root, "memory.current"), "50\n");
219
+ writeFileSync(path.join(root, "memory.max"), "200\n");
220
+ const { readCgroupMemory } = await import("./memory-metrics.js");
221
+ expect(readCgroupMemory(root)).toEqual({
222
+ currentBytes: 50,
223
+ limitBytes: 200,
224
+ usageRatio: 0.25,
225
+ });
226
+ });
227
+ it("returns undefined when cgroup files are absent or unlimited", async () => {
228
+ const root = mkdtempSync(path.join(tmpdir(), "sb-cgroup-"));
229
+ const { readCgroupMemory } = await import("./memory-metrics.js");
230
+ expect(readCgroupMemory(root)).toBeUndefined();
231
+ writeFileSync(path.join(root, "memory.current"), "50\n");
232
+ writeFileSync(path.join(root, "memory.max"), "max\n");
233
+ expect(readCgroupMemory(root)).toBeUndefined();
234
+ });
235
+ it("does not combine v2 usage with v1 limits", async () => {
236
+ const root = mkdtempSync(path.join(tmpdir(), "sb-cgroup-"));
237
+ const memoryRoot = path.join(root, "memory");
238
+ mkdirSync(memoryRoot);
239
+ writeFileSync(path.join(root, "memory.current"), "50\n");
240
+ writeFileSync(path.join(root, "memory.max"), "max\n");
241
+ writeFileSync(path.join(memoryRoot, "memory.usage_in_bytes"), "60\n");
242
+ writeFileSync(path.join(memoryRoot, "memory.limit_in_bytes"), "200\n");
243
+ const { readCgroupMemory } = await import("./memory-metrics.js");
244
+ expect(readCgroupMemory(root)).toBeUndefined();
245
+ });
246
+ it("treats cgroup v1 sentinel limits as unlimited", async () => {
247
+ const root = mkdtempSync(path.join(tmpdir(), "sb-cgroup-"));
248
+ const memoryRoot = path.join(root, "memory");
249
+ mkdirSync(memoryRoot);
250
+ writeFileSync(path.join(memoryRoot, "memory.usage_in_bytes"), "50\n");
251
+ writeFileSync(path.join(memoryRoot, "memory.limit_in_bytes"), "9223372036854771712\n");
252
+ const { readCgroupMemory } = await import("./memory-metrics.js");
253
+ expect(readCgroupMemory(root)).toBeUndefined();
254
+ });
255
+ });
256
+ //# sourceMappingURL=memory-metrics.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-metrics.test.js","sourceRoot":"","sources":["../../src/telemetry/memory-metrics.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superblocksteam/sdk",
3
- "version": "2.0.130-next.3",
3
+ "version": "2.0.130-next.5",
4
4
  "description": "Superblocks JS SDK",
5
5
  "homepage": "https://www.superblocks.com",
6
6
  "license": "Superblocks Community Software License",
@@ -48,11 +48,11 @@
48
48
  "vite-tsconfig-paths": "^6.0.4",
49
49
  "winston": "^3.17.0",
50
50
  "yaml": "^2.7.1",
51
- "@superblocksteam/library-shared": "2.0.130-next.3",
52
- "@superblocksteam/shared": "0.9591.3",
53
- "@superblocksteam/telemetry": "2.0.130-next.3",
54
- "@superblocksteam/util": "2.0.130-next.3",
55
- "@superblocksteam/vite-plugin-file-sync": "2.0.130-next.3"
51
+ "@superblocksteam/shared": "0.9592.1",
52
+ "@superblocksteam/library-shared": "2.0.130-next.5",
53
+ "@superblocksteam/telemetry": "2.0.130-next.5",
54
+ "@superblocksteam/util": "2.0.130-next.5",
55
+ "@superblocksteam/vite-plugin-file-sync": "2.0.130-next.5"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@eslint/js": "^9.39.2",
@@ -9,6 +9,8 @@ import {
9
9
  type ParseContext,
10
10
  } from "@superblocksteam/vite-plugin-file-sync/npm-registry";
11
11
 
12
+ import { isDiskFullError } from "./disk-space.mjs";
13
+
12
14
  /** Marker thrown by the INITIAL verification install so the dev.mts catch can
13
15
  * degrade by ORIGIN (never on classifiability). Carries the classified error. */
14
16
  export class InitialInstallFailed extends Error {
@@ -90,6 +92,18 @@ export function classifyInitialInstallError(
90
92
  if (blockedFromJson) return fromBlocked(blockedFromJson, rawError);
91
93
 
92
94
  const combined = `${stdout}\n${stderr}`;
95
+
96
+ if (isDiskFullError({ stdout, stderr, message: failure.message })) {
97
+ return {
98
+ type: "dev-server/dependency-install",
99
+ timestamp: new Date().toISOString(),
100
+ category: "disk_full",
101
+ npmErrorCode: "ENOSPC",
102
+ hasAnyRegistryConfigured: ctx.hasAnyRegistryConfigured,
103
+ rawError,
104
+ };
105
+ }
106
+
93
107
  const codeMatch = combined.match(DEP_CONFLICT_CODES);
94
108
  if (codeMatch) {
95
109
  return {
@@ -47,6 +47,45 @@ describe("classifyInitialInstallError", () => {
47
47
  expect(e.registryHost).toBe("reg.corp");
48
48
  });
49
49
 
50
+ it("classifies an ENOSPC --json envelope as disk_full", () => {
51
+ const ENOSPC_JSON = JSON.stringify({
52
+ error: {
53
+ code: "ENOSPC",
54
+ summary: "ENOSPC: no space left on device, write",
55
+ detail: "nospc ENOSPC: no space left on device, write",
56
+ },
57
+ });
58
+ const e = classifyInitialInstallError(
59
+ { stdout: ENOSPC_JSON, stderr: "npm error code ENOSPC", message: "x" },
60
+ { hasAnyRegistryConfigured: true, requestedPackages: [] },
61
+ );
62
+ expect(e.category).toBe("disk_full");
63
+ expect(e.npmErrorCode).toBe("ENOSPC");
64
+ });
65
+
66
+ it("classifies a bare 'no space left on device' stderr as disk_full", () => {
67
+ const e = classifyInitialInstallError(
68
+ {
69
+ stdout: "not json",
70
+ stderr:
71
+ "npm error code ENOSPC\nnpm error syscall write\nnpm error errno -28\nnpm error nospc ENOSPC: no space left on device, write",
72
+ message: "Command failed",
73
+ },
74
+ {},
75
+ );
76
+ expect(e.category).toBe("disk_full");
77
+ expect(e.npmErrorCode).toBe("ENOSPC");
78
+ });
79
+
80
+ it("classifies a bare 'errno -28' (no ENOSPC code name) as disk_full", () => {
81
+ const e = classifyInitialInstallError(
82
+ { stdout: "", stderr: "npm error errno -28", message: "Command failed" },
83
+ {},
84
+ );
85
+ expect(e.category).toBe("disk_full");
86
+ expect(e.npmErrorCode).toBe("ENOSPC");
87
+ });
88
+
50
89
  it("falls back to unknown for an unmapped code (still a DependencyInstallError)", () => {
51
90
  const e = classifyInitialInstallError(
52
91
  {
@@ -115,6 +115,7 @@ vi.mock("@superblocksteam/util", () => ({
115
115
  vi.mock("@superblocksteam/vite-plugin-file-sync/ai-service", () => ({
116
116
  AiService: class {
117
117
  initialize = vi.fn(async () => undefined);
118
+ notifyMigrationAppArtifactsReady = vi.fn();
118
119
  removeIntegrationCache = vi.fn(async () => undefined);
119
120
  chatSessionStore = { invalidateCache: vi.fn() };
120
121
  // `dev.mts` reads this at startup for two paths: `syncHomeNpmrc`
@@ -115,6 +115,7 @@ vi.mock("@superblocksteam/util", () => ({
115
115
  vi.mock("@superblocksteam/vite-plugin-file-sync/ai-service", () => ({
116
116
  AiService: class {
117
117
  initialize = vi.fn(async () => undefined);
118
+ notifyMigrationAppArtifactsReady = vi.fn();
118
119
  removeIntegrationCache = vi.fn(async () => undefined);
119
120
  chatSessionStore = { invalidateCache: vi.fn() };
120
121
  getNpmRegistryClient = vi.fn(() => undefined);
@@ -70,6 +70,7 @@ import {
70
70
  classifyInitialInstallError,
71
71
  InitialInstallFailed,
72
72
  } from "./dependency-install-classifier.mjs";
73
+ import { getDiskSpace, recoverFromDiskFull } from "./disk-space.mjs";
73
74
  import {
74
75
  ensureRemoteHasDefaultBranch,
75
76
  getGitErrorFields,
@@ -342,6 +343,12 @@ export async function installPackages(
342
343
  logger: Logger,
343
344
  npmRegistryClient?: NpmRegistryClient,
344
345
  ) {
346
+ // Hoisted so the catch's ENOSPC recovery can re-run the EXACT same install
347
+ // (same agent, args, env, --ignore-scripts policy) after reclaiming disk
348
+ // space, rather than reconstructing — and risking diverging from — it.
349
+ let runInstall:
350
+ | (() => Promise<{ stdout: string; stderr: string }>)
351
+ | undefined;
345
352
  try {
346
353
  const pm = await detect({
347
354
  strategies: [
@@ -445,25 +452,89 @@ export async function installPackages(
445
452
  // (`category: "unknown"`, `npmErrorCode: undefined` from real npm against a
446
453
  // missing `/tmp/app`).
447
454
  const exec = promisify(child_process.exec);
448
- const { stdout } = await exec(`${command} ${args.join(" ")}`, {
449
- cwd,
450
- env: buildInstallEnv(superblocksNpmrcPath(), logsDir),
451
- });
455
+ // Re-runnable so the ENOSPC recovery path below can retry the EXACT same
456
+ // install after reclaiming disk space.
457
+ runInstall = () =>
458
+ exec(`${command} ${args.join(" ")}`, {
459
+ cwd,
460
+ env: buildInstallEnv(superblocksNpmrcPath(), logsDir),
461
+ });
462
+
463
+ // Cheap `df` snapshot before the install (statfs only — no directory walk
464
+ // on the happy path). On a 3Gi live-edit PVC this is the baseline that
465
+ // makes an ENOSPC mid-install interpretable after the fact.
466
+ const diskBefore = await getDiskSpace(cwd);
467
+ if (diskBefore) {
468
+ logger.info(
469
+ `Disk usage before package install [freeBytes=${diskBefore.freeBytes} totalBytes=${diskBefore.totalBytes} percentUsed=${diskBefore.percentUsed}] cwd=${cwd}`,
470
+ { disk: diskBefore },
471
+ );
472
+ } else {
473
+ logger.warn(
474
+ `Could not read disk usage before package install (statfs failed) cwd=${cwd}`,
475
+ );
476
+ }
477
+
478
+ const { stdout } = await runInstall();
452
479
  logger.info("Package installation completed successfully");
453
480
  logger.info(stdout);
454
481
  } catch (error) {
455
- logger.error("Error during package installation", getErrorMeta(error));
456
482
  // `util.promisify(child_process.exec)` preserves `.stdout`/`.stderr` on
457
483
  // the rejected error separately; with `--json`, the structured npm error
458
484
  // envelope lands on `.stdout`. Classify it into a DependencyInstallError
459
485
  // and throw the `InitialInstallFailed` marker so the `dev()` catch can
460
486
  // degrade by origin rather than crash-looping the dev-server pod.
461
487
  const ctx = await buildInstallParseContext(cwd, npmRegistryClient);
462
- const f = error as { stdout?: string; stderr?: string; message?: string };
463
- const serverError = classifyInitialInstallError(
464
- { stdout: f.stdout, stderr: f.stderr, message: f.message },
465
- ctx,
466
- );
488
+ const toFailure = (e: unknown) => {
489
+ const f = e as { stdout?: string; stderr?: string; message?: string };
490
+ return { stdout: f.stdout, stderr: f.stderr, message: f.message };
491
+ };
492
+ let serverError = classifyInitialInstallError(toFailure(error), ctx);
493
+ // The error whose meta we log on the way out — kept in lockstep with the
494
+ // (possibly reclassified) `serverError` we throw, so operational logs and
495
+ // the user-visible error never disagree.
496
+ let errorToLog: unknown = error;
497
+
498
+ // ENOSPC: the live-edit volume filled up mid-install. Try to recover by
499
+ // reclaiming regenerable build artifacts (and, if that's not enough, the
500
+ // half-written node_modules) and retrying the install before giving up.
501
+ // The recovery logs each tier's df + freed bytes, which doubles as the
502
+ // only autopsy we can get of a PVC that `reclaimPolicy: Delete` destroys
503
+ // at teardown. `runInstall` is only set once the command was resolved; if
504
+ // the failure happened before that, there's nothing to retry.
505
+ if (serverError.category === "disk_full" && runInstall) {
506
+ let recovery: Awaited<ReturnType<typeof recoverFromDiskFull>> | undefined;
507
+ try {
508
+ recovery = await recoverFromDiskFull(cwd, runInstall, logger);
509
+ } catch (recoveryErr) {
510
+ // Recovery is best-effort; an unexpected throw must never escape as a
511
+ // raw error (that would bypass the InitialInstallFailed contract the
512
+ // dev() catch relies on). Swallow it and surface the original failure.
513
+ logger.warn("Disk recovery threw unexpectedly", {
514
+ error: String(recoveryErr),
515
+ });
516
+ }
517
+ if (recovery) {
518
+ devServerMetrics.recordDiskRecovery({ recovered: recovery.recovered });
519
+ if (recovery.recovered) {
520
+ logger.info(
521
+ "Package installation completed successfully after disk recovery",
522
+ );
523
+ return;
524
+ }
525
+ // Reclassify the final attempt so the surfaced error reflects the last
526
+ // npm output (still disk_full if genuinely out of space).
527
+ if (recovery.lastError !== undefined) {
528
+ errorToLog = recovery.lastError;
529
+ serverError = classifyInitialInstallError(
530
+ toFailure(recovery.lastError),
531
+ ctx,
532
+ );
533
+ }
534
+ }
535
+ }
536
+
537
+ logger.error("Error during package installation", getErrorMeta(errorToLog));
467
538
  throw new InitialInstallFailed(serverError);
468
539
  }
469
540
  }
@@ -968,6 +1039,7 @@ export async function dev(options: {
968
1039
  logger,
969
1040
  tokenManager,
970
1041
  features: AiServiceFeatureFlags.create(flagBootstrap),
1042
+ flagBootstrap,
971
1043
  pluginExecutionVersions:
972
1044
  currentUser.organizations[0].pluginExecutionVersions,
973
1045
  });
@@ -983,6 +1055,10 @@ export async function dev(options: {
983
1055
  },
984
1056
  );
985
1057
 
1058
+ if (uploadFirst) {
1059
+ aiService?.notifyMigrationAppArtifactsReady();
1060
+ }
1061
+
986
1062
  logger.info(
987
1063
  `[dev-startup] Starting git sync (${downloadFirst ? "download" : "upload"}-first) on branch '${activeDbfsBranchName}'`,
988
1064
  );
@@ -1174,6 +1250,10 @@ export async function dev(options: {
1174
1250
  logger.info("[dev-startup] Skipping download, already in sync");
1175
1251
  }
1176
1252
 
1253
+ if (downloadFirst) {
1254
+ aiService?.notifyMigrationAppArtifactsReady();
1255
+ }
1256
+
1177
1257
  // Unconditional lockfile sanitation (APPS-4300): strip
1178
1258
  // cross-registry `resolved` URLs from any lockfile on disk
1179
1259
  // regardless of whether install runs next. The lockfile here is