veryfront 0.1.403 → 0.1.405

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 (39) hide show
  1. package/esm/cli/commands/install/registry.d.ts.map +1 -1
  2. package/esm/cli/commands/install/registry.js +9 -3
  3. package/esm/cli/templates/manifest.d.ts +30 -0
  4. package/esm/cli/templates/manifest.js +30 -0
  5. package/esm/deno.js +1 -1
  6. package/esm/src/agent/hosted-child-status.d.ts +5 -0
  7. package/esm/src/agent/hosted-child-status.d.ts.map +1 -1
  8. package/esm/src/agent/hosted-child-status.js +14 -0
  9. package/esm/src/agent/index.d.ts +1 -1
  10. package/esm/src/agent/index.d.ts.map +1 -1
  11. package/esm/src/agent/index.js +1 -1
  12. package/esm/src/config/schemas/config.schema.d.ts +18 -0
  13. package/esm/src/config/schemas/config.schema.d.ts.map +1 -1
  14. package/esm/src/config/schemas/config.schema.js +16 -0
  15. package/esm/src/observability/file-log-subscriber.d.ts +32 -0
  16. package/esm/src/observability/file-log-subscriber.d.ts.map +1 -0
  17. package/esm/src/observability/file-log-subscriber.js +163 -0
  18. package/esm/src/observability/index.d.ts +1 -0
  19. package/esm/src/observability/index.d.ts.map +1 -1
  20. package/esm/src/observability/index.js +1 -0
  21. package/esm/src/server/bootstrap.d.ts.map +1 -1
  22. package/esm/src/server/bootstrap.js +149 -97
  23. package/esm/src/utils/version-constant.d.ts +1 -1
  24. package/esm/src/utils/version-constant.js +1 -1
  25. package/package.json +1 -1
  26. package/src/cli/commands/install/registry.ts +10 -3
  27. package/src/cli/templates/manifest.js +30 -0
  28. package/src/deno.js +1 -1
  29. package/src/deps/esm.sh/@types/react-dom@19.2.3/client.d.ts +1 -1
  30. package/src/deps/esm.sh/@types/{react@19.2.3 → react@19.2.14}/global.d.ts +1 -0
  31. package/src/deps/esm.sh/@types/{react@19.2.3 → react@19.2.14}/index.d.ts +93 -24
  32. package/src/deps/esm.sh/react-dom@19.2.4/client.d.ts +1 -1
  33. package/src/src/agent/hosted-child-status.ts +27 -0
  34. package/src/src/agent/index.ts +2 -0
  35. package/src/src/config/schemas/config.schema.ts +16 -0
  36. package/src/src/observability/file-log-subscriber.ts +187 -0
  37. package/src/src/observability/index.ts +7 -0
  38. package/src/src/server/bootstrap.ts +184 -119
  39. package/src/src/utils/version-constant.ts +1 -1
@@ -4,7 +4,7 @@
4
4
 
5
5
  // See https://github.com/facebook/react/blob/main/packages/react-dom/client.js to see how the exports are declared,
6
6
 
7
- import React = require("https://esm.sh/@types/react@19.2.3/index.d.ts");
7
+ import React = require("https://esm.sh/@types/react@19.2.14/index.d.ts");
8
8
 
9
9
  export {};
10
10
 
@@ -30,6 +30,33 @@ export function isHostedChildTerminalErrorCode(
30
30
  );
31
31
  }
32
32
 
33
+ export interface HostedChildSameTurnRetryBlockSignal {
34
+ terminalErrorCode?: string | null;
35
+ terminalErrorMessage?: string | null;
36
+ }
37
+
38
+ function getOptionalStringProperty(value: object, property: string): string | null {
39
+ const descriptor = Object.getOwnPropertyDescriptor(value, property);
40
+ return typeof descriptor?.value === "string" ? descriptor.value : null;
41
+ }
42
+
43
+ export function shouldBlockHostedChildSameTurnRetry(
44
+ result: unknown,
45
+ ): result is HostedChildSameTurnRetryBlockSignal {
46
+ if (typeof result !== "object" || result === null) {
47
+ return false;
48
+ }
49
+
50
+ const terminalErrorCode = getOptionalStringProperty(result, "terminalErrorCode");
51
+ const terminalErrorMessage = getOptionalStringProperty(result, "terminalErrorMessage");
52
+
53
+ return (
54
+ terminalErrorCode === "CANCELLED" ||
55
+ terminalErrorCode === hostedChildTerminalErrorCodes.cancelled ||
56
+ terminalErrorMessage === "Child run cancelled"
57
+ );
58
+ }
59
+
33
60
  export type HostedChildTerminalStatus = Extract<
34
61
  HostedConversationRunStatus,
35
62
  "completed" | "failed" | "cancelled"
@@ -871,6 +871,7 @@ export {
871
871
  } from "./hosted-child-fork-runtime-start.js";
872
872
  export {
873
873
  type HostedChildRunIdentifiers,
874
+ type HostedChildSameTurnRetryBlockSignal,
874
875
  type HostedChildTerminalErrorCode,
875
876
  hostedChildTerminalErrorCodes,
876
877
  HostedChildTerminalStateError,
@@ -879,6 +880,7 @@ export {
879
880
  monitorHostedChildRunStatus,
880
881
  type MonitorHostedChildRunStatusInput,
881
882
  resolveHostedChildTerminalErrorCode,
883
+ shouldBlockHostedChildSameTurnRetry,
882
884
  } from "./hosted-child-status.js";
883
885
  export {
884
886
  type HostedLifecycleAdapter,
@@ -261,6 +261,22 @@ export const veryfrontConfigSchema = z
261
261
  })
262
262
  .partial()
263
263
  .optional(),
264
+ logging: z
265
+ .object({
266
+ file: z
267
+ .object({
268
+ enabled: z.boolean().optional(),
269
+ path: z.string().optional(),
270
+ maxSize: z.union([z.number().int().positive(), z.string()]).optional(),
271
+ maxFiles: z.number().int().positive().optional(),
272
+ level: z.enum(["debug", "info", "warn", "error"]).optional(),
273
+ format: z.enum(["json", "text"]).optional(),
274
+ })
275
+ .partial()
276
+ .optional(),
277
+ })
278
+ .partial()
279
+ .optional(),
264
280
  })
265
281
  .partial()
266
282
  .optional(),
@@ -0,0 +1,187 @@
1
+ import * as dntShim from "../../_dnt.shims.js";
2
+ import type { LogEntry, LogLevel, LogSubscriber } from "./log-buffer.js";
3
+
4
+ export interface FileLogConfig {
5
+ enabled: boolean;
6
+ path: string;
7
+ maxSize: number | string;
8
+ maxFiles: number;
9
+ level: LogLevel;
10
+ format: "json" | "text";
11
+ }
12
+
13
+ const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
14
+ debug: 0,
15
+ info: 1,
16
+ warn: 2,
17
+ error: 3,
18
+ };
19
+
20
+ const SIZE_UNITS: Record<string, number> = {
21
+ b: 1,
22
+ kb: 1024,
23
+ mb: 1024 * 1024,
24
+ gb: 1024 * 1024 * 1024,
25
+ };
26
+
27
+ export function parseMaxSize(value: number | string): number {
28
+ if (typeof value === "number") return value;
29
+
30
+ const match = value.trim().toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
31
+ if (!match?.[1]) {
32
+ throw new Error(`Invalid maxSize value: "${value}". Expected a number or string like "10mb".`);
33
+ }
34
+
35
+ const num = parseFloat(match[1]);
36
+ const unit = match[2] ?? "b";
37
+ return Math.floor(num * (SIZE_UNITS[unit] ?? 1));
38
+ }
39
+
40
+ function formatEntryText(entry: LogEntry): string {
41
+ const time = new Date(entry.timestamp).toISOString();
42
+ const level = entry.level.toUpperCase().padEnd(5);
43
+ const data = entry.data ? ` ${JSON.stringify(entry.data)}` : "";
44
+ return `${time} ${level} [${entry.source}] ${entry.message}${data}`;
45
+ }
46
+
47
+ function formatEntryJson(entry: LogEntry): string {
48
+ return JSON.stringify(entry);
49
+ }
50
+
51
+ export class FileLogSubscriber {
52
+ private file: dntShim.Deno.FsFile | null = null;
53
+ private currentSize = 0;
54
+ private writeQueue: Promise<void> = Promise.resolve();
55
+ private maxSizeBytes: number;
56
+ private minLevel: number;
57
+ private formatter: (entry: LogEntry) => string;
58
+ private closed = false;
59
+ private permissionFailed = false;
60
+ private config: FileLogConfig;
61
+
62
+ constructor(config: FileLogConfig) {
63
+ this.config = config;
64
+ this.maxSizeBytes = parseMaxSize(config.maxSize);
65
+ this.minLevel = LOG_LEVEL_PRIORITY[config.level];
66
+ this.formatter = config.format === "json" ? formatEntryJson : formatEntryText;
67
+ }
68
+
69
+ getSubscriber(): LogSubscriber {
70
+ return (entry: LogEntry) => {
71
+ if (this.closed || this.permissionFailed) return;
72
+ if (LOG_LEVEL_PRIORITY[entry.level] < this.minLevel) return;
73
+ this.enqueue(entry);
74
+ };
75
+ }
76
+
77
+ private enqueue(entry: LogEntry): void {
78
+ this.writeQueue = this.writeQueue.then(() => this.writeEntry(entry)).catch(() => {});
79
+ }
80
+
81
+ private async writeEntry(entry: LogEntry): Promise<void> {
82
+ try {
83
+ if (!this.file) await this.openFile();
84
+
85
+ const line = this.formatter(entry) + "\n";
86
+ const bytes = new TextEncoder().encode(line);
87
+
88
+ if (this.currentSize + bytes.length > this.maxSizeBytes) {
89
+ await this.rotate();
90
+ }
91
+
92
+ await this.file!.write(bytes);
93
+ this.currentSize += bytes.length;
94
+ } catch (err) {
95
+ if (err instanceof dntShim.Deno.errors.PermissionDenied) {
96
+ this.permissionFailed = true;
97
+ console.error(
98
+ `[FileLogSubscriber] Permission denied writing to ${this.config.path}. File logging disabled.`,
99
+ );
100
+ return;
101
+ }
102
+ throw err;
103
+ }
104
+ }
105
+
106
+ private async openFile(): Promise<void> {
107
+ await this.ensureDir();
108
+ this.file = await dntShim.Deno.open(this.config.path, {
109
+ write: true,
110
+ create: true,
111
+ append: true,
112
+ });
113
+ try {
114
+ const stat = await this.file.stat();
115
+ this.currentSize = stat.size;
116
+ } catch {
117
+ this.currentSize = 0;
118
+ }
119
+ }
120
+
121
+ private async ensureDir(): Promise<void> {
122
+ const dir = this.config.path.substring(0, this.config.path.lastIndexOf("/"));
123
+ if (dir) {
124
+ await dntShim.Deno.mkdir(dir, { recursive: true });
125
+ }
126
+ }
127
+
128
+ private async rotate(): Promise<void> {
129
+ if (this.file) {
130
+ this.file.close();
131
+ this.file = null;
132
+ }
133
+
134
+ for (let i = this.config.maxFiles - 1; i >= 1; i--) {
135
+ const from = i === 1 ? this.config.path : `${this.config.path}.${i - 1}`;
136
+ const to = `${this.config.path}.${i}`;
137
+ try {
138
+ await dntShim.Deno.rename(from, to);
139
+ } catch (err) {
140
+ if (!(err instanceof dntShim.Deno.errors.NotFound)) throw err;
141
+ }
142
+ }
143
+
144
+ if (this.config.maxFiles <= 1) {
145
+ try {
146
+ await dntShim.Deno.remove(this.config.path);
147
+ } catch (err) {
148
+ if (!(err instanceof dntShim.Deno.errors.NotFound)) throw err;
149
+ }
150
+ }
151
+
152
+ this.file = await dntShim.Deno.open(this.config.path, {
153
+ write: true,
154
+ create: true,
155
+ truncate: true,
156
+ });
157
+ this.currentSize = 0;
158
+ }
159
+
160
+ async flush(): Promise<void> {
161
+ await this.writeQueue;
162
+ if (this.file) {
163
+ try {
164
+ await this.file.sync();
165
+ } catch {
166
+ // file may already be closed
167
+ }
168
+ }
169
+ }
170
+
171
+ async close(): Promise<void> {
172
+ this.closed = true;
173
+ await this.flush();
174
+ if (this.file) {
175
+ try {
176
+ this.file.close();
177
+ } catch {
178
+ // already closed
179
+ }
180
+ this.file = null;
181
+ }
182
+ }
183
+ }
184
+
185
+ export function createFileLogSubscriber(config: FileLogConfig): FileLogSubscriber {
186
+ return new FileLogSubscriber(config);
187
+ }
@@ -92,3 +92,10 @@ export {
92
92
  type LogSubscriber,
93
93
  resetLogBuffer,
94
94
  } from "./log-buffer.js";
95
+
96
+ export {
97
+ createFileLogSubscriber,
98
+ type FileLogConfig,
99
+ FileLogSubscriber,
100
+ parseMaxSize,
101
+ } from "./file-log-subscriber.js";
@@ -33,6 +33,12 @@ import {
33
33
  markEnvLoaded,
34
34
  supportsEnvFiles,
35
35
  } from "../utils/env-loader.js";
36
+ import { getLogBuffer } from "../observability/log-buffer.js";
37
+ import {
38
+ createFileLogSubscriber,
39
+ type FileLogConfig,
40
+ type FileLogSubscriber,
41
+ } from "../observability/file-log-subscriber.js";
36
42
  import { ReloadNotifier } from "./reload-notifier.js";
37
43
  import { clearDomainCache } from "./utils/domain-lookup.js";
38
44
 
@@ -119,15 +125,60 @@ function wireTracingShim(): void {
119
125
  }
120
126
  }
121
127
 
128
+ const DEFAULT_FILE_LOG_PATH = ".veryfront/logs/server.log";
129
+ const DEFAULT_FILE_LOG_MAX_SIZE = "10mb";
130
+ const DEFAULT_FILE_LOG_MAX_FILES = 5;
131
+ const DEFAULT_FILE_LOG_LEVEL = "warn" as const;
132
+ const DEFAULT_FILE_LOG_FORMAT = "json" as const;
133
+
134
+ interface FileLogHandle {
135
+ subscriber: FileLogSubscriber;
136
+ unsubscribe: () => void;
137
+ }
138
+
139
+ function maybeAttachFileLogSubscriber(config: VeryfrontConfig): FileLogHandle | null {
140
+ const fileConfig = config.observability?.logging?.file;
141
+ if (!fileConfig?.enabled) return null;
142
+
143
+ const resolved: FileLogConfig = {
144
+ enabled: true,
145
+ path: fileConfig.path ?? DEFAULT_FILE_LOG_PATH,
146
+ maxSize: fileConfig.maxSize ?? DEFAULT_FILE_LOG_MAX_SIZE,
147
+ maxFiles: fileConfig.maxFiles ?? DEFAULT_FILE_LOG_MAX_FILES,
148
+ level: fileConfig.level ?? DEFAULT_FILE_LOG_LEVEL,
149
+ format: fileConfig.format ?? DEFAULT_FILE_LOG_FORMAT,
150
+ };
151
+
152
+ const subscriber = createFileLogSubscriber(resolved);
153
+ const unsubscribe = getLogBuffer().subscribe(subscriber.getSubscriber());
154
+ bootstrapLog.debug("[bootstrap] File log subscriber attached", {
155
+ path: resolved.path,
156
+ level: resolved.level,
157
+ format: resolved.format,
158
+ });
159
+ return { subscriber, unsubscribe };
160
+ }
161
+
162
+ async function teardownFileLog(handle: FileLogHandle | null): Promise<void> {
163
+ if (!handle) return;
164
+ handle.unsubscribe();
165
+ await handle.subscriber.close();
166
+ }
167
+
122
168
  function combineDispose(
123
169
  extensionLoader: ExtensionLoader,
124
170
  fsDispose?: () => void,
171
+ fileLogHandle?: FileLogHandle | null,
125
172
  ): () => Promise<void> {
126
173
  return async () => {
127
174
  try {
128
175
  await extensionLoader.teardownAll();
129
176
  } finally {
130
- if (fsDispose) fsDispose();
177
+ try {
178
+ await teardownFileLog(fileLogHandle ?? null);
179
+ } finally {
180
+ if (fsDispose) fsDispose();
181
+ }
131
182
  }
132
183
  };
133
184
  }
@@ -218,141 +269,155 @@ export async function bootstrap(
218
269
  bootstrapLog.debug("Loading config with base adapter");
219
270
  let config = await getConfig(projectDir, adapter);
220
271
 
221
- const fsType = config.fs?.type;
222
- const needsFSAdapter = fsType != null && fsType !== "local";
272
+ let fileLog = maybeAttachFileLogSubscriber(config);
223
273
 
224
- if (!needsFSAdapter) {
225
- bootstrapLog.debug("Using local filesystem (no FSAdapter needed)");
226
- const extensionLoader = await orchestrateExtensions({
227
- projectDir,
228
- config,
229
- logger: bootstrapLog,
230
- primeContracts: { [AIProviderRegistryName]: createAIProviderRegistry() },
231
- builtinExtensions: createBuiltinExtensions(),
232
- });
233
- wireTracingShim();
234
- assertRequiredContracts();
235
- return {
236
- adapter,
237
- config,
238
- usingFSAdapter: false,
239
- extensionLoader,
240
- dispose: combineDispose(extensionLoader),
274
+ try {
275
+ const fsType = config.fs?.type;
276
+ const needsFSAdapter = fsType != null && fsType !== "local";
277
+
278
+ if (!needsFSAdapter) {
279
+ bootstrapLog.debug("Using local filesystem (no FSAdapter needed)");
280
+ const extensionLoader = await orchestrateExtensions({
281
+ projectDir,
282
+ config,
283
+ logger: bootstrapLog,
284
+ primeContracts: { [AIProviderRegistryName]: createAIProviderRegistry() },
285
+ builtinExtensions: createBuiltinExtensions(),
286
+ });
287
+ wireTracingShim();
288
+ assertRequiredContracts();
289
+ return {
290
+ adapter,
291
+ config,
292
+ usingFSAdapter: false,
293
+ extensionLoader,
294
+ dispose: combineDispose(extensionLoader, undefined, fileLog),
295
+ };
296
+ }
297
+
298
+ bootstrapLog.debug("Initializing FSAdapter", { type: fsType });
299
+
300
+ // Inject server-layer callbacks into FS config so the platform layer
301
+ // doesn't need to import from the server layer
302
+ const fsWithCallbacks = {
303
+ ...config.fs,
304
+ invalidationCallbacks: {
305
+ triggerReload: (changedPaths?: string[], project?: InvalidationProjectContext) =>
306
+ ReloadNotifier.triggerReload(changedPaths, project),
307
+ clearDomainCache,
308
+ },
241
309
  };
242
- }
243
310
 
244
- bootstrapLog.debug("Initializing FSAdapter", { type: fsType });
245
-
246
- // Inject server-layer callbacks into FS config so the platform layer
247
- // doesn't need to import from the server layer
248
- const fsWithCallbacks = {
249
- ...config.fs,
250
- invalidationCallbacks: {
251
- triggerReload: (changedPaths?: string[], project?: InvalidationProjectContext) =>
252
- ReloadNotifier.triggerReload(changedPaths, project),
253
- clearDomainCache,
254
- },
255
- };
311
+ const enhancedAdapter = await enhanceAdapterWithFS(
312
+ adapter,
313
+ { ...config, fs: fsWithCallbacks },
314
+ projectDir,
315
+ );
256
316
 
257
- const enhancedAdapter = await enhanceAdapterWithFS(
258
- adapter,
259
- { ...config, fs: fsWithCallbacks },
260
- projectDir,
261
- );
317
+ if (enhancedAdapter === adapter) {
318
+ bootstrapLog.debug("Framework initialized successfully", {
319
+ projectDir,
320
+ runtime: adapter.id,
321
+ fsAdapter: "local",
322
+ });
323
+
324
+ const extensionLoader = await orchestrateExtensions({
325
+ projectDir,
326
+ config,
327
+ logger: bootstrapLog,
328
+ primeContracts: { [AIProviderRegistryName]: createAIProviderRegistry() },
329
+ builtinExtensions: createBuiltinExtensions(),
330
+ });
331
+ wireTracingShim();
332
+ assertRequiredContracts();
333
+ return {
334
+ adapter,
335
+ config,
336
+ usingFSAdapter: false,
337
+ extensionLoader,
338
+ dispose: combineDispose(extensionLoader, undefined, fileLog),
339
+ };
340
+ }
341
+
342
+ const isProxyMode = config.fs?.veryfront?.proxyMode === true;
343
+ const isProductionMode = config.fs?.veryfront?.productionMode === true;
344
+
345
+ if (isProxyMode) {
346
+ bootstrapLog.debug("Skipping config reload in proxy mode (using local config)");
347
+ } else if (isProductionMode) {
348
+ bootstrapLog.debug("Skipping config reload in production mode (using local config)");
349
+ } else {
350
+ bootstrapLog.debug("Reloading config with FSAdapter");
351
+ clearConfigCache();
352
+
353
+ const originalConfig = config;
354
+ const reloadedConfig = await getConfig(projectDir, enhancedAdapter);
355
+
356
+ const usesDefaultDevConfig = reloadedConfig.dev?.port === 3000 &&
357
+ reloadedConfig.dev?.host === "localhost" &&
358
+ !reloadedConfig.dev?.hmr;
359
+
360
+ if (usesDefaultDevConfig && originalConfig.dev) {
361
+ bootstrapLog.debug("Keeping original config (FSAdapter returned defaults)");
362
+ config = originalConfig;
363
+ } else {
364
+ config = reloadedConfig;
365
+ }
366
+
367
+ // Re-attach file log subscriber if config was reloaded with different settings
368
+ const newFileLog = maybeAttachFileLogSubscriber(config);
369
+ if (newFileLog) {
370
+ await teardownFileLog(fileLog);
371
+ fileLog = newFileLog;
372
+ }
373
+ }
262
374
 
263
- if (enhancedAdapter === adapter) {
264
375
  bootstrapLog.debug("Framework initialized successfully", {
265
376
  projectDir,
266
377
  runtime: adapter.id,
267
- fsAdapter: "local",
378
+ fsAdapter: fsType,
268
379
  });
269
380
 
270
- const extensionLoader = await orchestrateExtensions({
271
- projectDir,
272
- config,
273
- logger: bootstrapLog,
274
- primeContracts: { [AIProviderRegistryName]: createAIProviderRegistry() },
275
- builtinExtensions: createBuiltinExtensions(),
276
- });
381
+ let fsDispose: (() => void) | undefined;
382
+ if (isExtendedFSAdapter(enhancedAdapter.fs)) {
383
+ const underlying = enhancedAdapter.fs.getUnderlyingAdapter();
384
+ if (
385
+ "dispose" in underlying &&
386
+ typeof (underlying as { dispose?: () => void }).dispose === "function"
387
+ ) {
388
+ fsDispose = () => (underlying as { dispose: () => void }).dispose();
389
+ }
390
+ }
391
+
392
+ // If extension orchestration fails after the FS adapter has been wired up,
393
+ // release the FS resources (WebSocket connections, caches) before
394
+ // propagating the error — otherwise the adapter would leak.
395
+ const extensionLoader = await orchestrateOrDisposeFS(
396
+ () =>
397
+ orchestrateExtensions({
398
+ projectDir,
399
+ config,
400
+ logger: bootstrapLog,
401
+ primeContracts: { [AIProviderRegistryName]: createAIProviderRegistry() },
402
+ builtinExtensions: createBuiltinExtensions(),
403
+ }),
404
+ fsDispose,
405
+ );
277
406
  wireTracingShim();
278
407
  assertRequiredContracts();
408
+
279
409
  return {
280
- adapter,
410
+ adapter: enhancedAdapter,
281
411
  config,
282
- usingFSAdapter: false,
412
+ usingFSAdapter: true,
413
+ fsAdapterType: fsType,
283
414
  extensionLoader,
284
- dispose: combineDispose(extensionLoader),
415
+ dispose: combineDispose(extensionLoader, fsDispose, fileLog),
285
416
  };
417
+ } catch (err) {
418
+ await teardownFileLog(fileLog);
419
+ throw err;
286
420
  }
287
-
288
- const isProxyMode = config.fs?.veryfront?.proxyMode === true;
289
- const isProductionMode = config.fs?.veryfront?.productionMode === true;
290
-
291
- if (isProxyMode) {
292
- bootstrapLog.debug("Skipping config reload in proxy mode (using local config)");
293
- } else if (isProductionMode) {
294
- bootstrapLog.debug("Skipping config reload in production mode (using local config)");
295
- } else {
296
- bootstrapLog.debug("Reloading config with FSAdapter");
297
- clearConfigCache();
298
-
299
- const originalConfig = config;
300
- const reloadedConfig = await getConfig(projectDir, enhancedAdapter);
301
-
302
- const usesDefaultDevConfig = reloadedConfig.dev?.port === 3000 &&
303
- reloadedConfig.dev?.host === "localhost" &&
304
- !reloadedConfig.dev?.hmr;
305
-
306
- if (usesDefaultDevConfig && originalConfig.dev) {
307
- bootstrapLog.debug("Keeping original config (FSAdapter returned defaults)");
308
- config = originalConfig;
309
- } else {
310
- config = reloadedConfig;
311
- }
312
- }
313
-
314
- bootstrapLog.debug("Framework initialized successfully", {
315
- projectDir,
316
- runtime: adapter.id,
317
- fsAdapter: fsType,
318
- });
319
-
320
- let fsDispose: (() => void) | undefined;
321
- if (isExtendedFSAdapter(enhancedAdapter.fs)) {
322
- const underlying = enhancedAdapter.fs.getUnderlyingAdapter();
323
- if (
324
- "dispose" in underlying &&
325
- typeof (underlying as { dispose?: () => void }).dispose === "function"
326
- ) {
327
- fsDispose = () => (underlying as { dispose: () => void }).dispose();
328
- }
329
- }
330
-
331
- // If extension orchestration fails after the FS adapter has been wired up,
332
- // release the FS resources (WebSocket connections, caches) before
333
- // propagating the error — otherwise the adapter would leak.
334
- const extensionLoader = await orchestrateOrDisposeFS(
335
- () =>
336
- orchestrateExtensions({
337
- projectDir,
338
- config,
339
- logger: bootstrapLog,
340
- primeContracts: { [AIProviderRegistryName]: createAIProviderRegistry() },
341
- builtinExtensions: createBuiltinExtensions(),
342
- }),
343
- fsDispose,
344
- );
345
- wireTracingShim();
346
- assertRequiredContracts();
347
-
348
- return {
349
- adapter: enhancedAdapter,
350
- config,
351
- usingFSAdapter: true,
352
- fsAdapterType: fsType,
353
- extensionLoader,
354
- dispose: combineDispose(extensionLoader, fsDispose),
355
- };
356
421
  }
357
422
 
358
423
  export async function bootstrapDev(
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.403";
3
+ export const VERSION = "0.1.405";