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.
- package/esm/cli/commands/install/registry.d.ts.map +1 -1
- package/esm/cli/commands/install/registry.js +9 -3
- package/esm/cli/templates/manifest.d.ts +30 -0
- package/esm/cli/templates/manifest.js +30 -0
- package/esm/deno.js +1 -1
- package/esm/src/agent/hosted-child-status.d.ts +5 -0
- package/esm/src/agent/hosted-child-status.d.ts.map +1 -1
- package/esm/src/agent/hosted-child-status.js +14 -0
- package/esm/src/agent/index.d.ts +1 -1
- package/esm/src/agent/index.d.ts.map +1 -1
- package/esm/src/agent/index.js +1 -1
- package/esm/src/config/schemas/config.schema.d.ts +18 -0
- package/esm/src/config/schemas/config.schema.d.ts.map +1 -1
- package/esm/src/config/schemas/config.schema.js +16 -0
- package/esm/src/observability/file-log-subscriber.d.ts +32 -0
- package/esm/src/observability/file-log-subscriber.d.ts.map +1 -0
- package/esm/src/observability/file-log-subscriber.js +163 -0
- package/esm/src/observability/index.d.ts +1 -0
- package/esm/src/observability/index.d.ts.map +1 -1
- package/esm/src/observability/index.js +1 -0
- package/esm/src/server/bootstrap.d.ts.map +1 -1
- package/esm/src/server/bootstrap.js +149 -97
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
- package/src/cli/commands/install/registry.ts +10 -3
- package/src/cli/templates/manifest.js +30 -0
- package/src/deno.js +1 -1
- package/src/deps/esm.sh/@types/react-dom@19.2.3/client.d.ts +1 -1
- package/src/deps/esm.sh/@types/{react@19.2.3 → react@19.2.14}/global.d.ts +1 -0
- package/src/deps/esm.sh/@types/{react@19.2.3 → react@19.2.14}/index.d.ts +93 -24
- package/src/deps/esm.sh/react-dom@19.2.4/client.d.ts +1 -1
- package/src/src/agent/hosted-child-status.ts +27 -0
- package/src/src/agent/index.ts +2 -0
- package/src/src/config/schemas/config.schema.ts +16 -0
- package/src/src/observability/file-log-subscriber.ts +187 -0
- package/src/src/observability/index.ts +7 -0
- package/src/src/server/bootstrap.ts +184 -119
- 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.
|
|
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"
|
package/src/src/agent/index.ts
CHANGED
|
@@ -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
|
+
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
222
|
-
const needsFSAdapter = fsType != null && fsType !== "local";
|
|
272
|
+
let fileLog = maybeAttachFileLogSubscriber(config);
|
|
223
273
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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:
|
|
378
|
+
fsAdapter: fsType,
|
|
268
379
|
});
|
|
269
380
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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:
|
|
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(
|