@faapi/faapi 6.24.0 → 6.26.0

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/dist/index.js CHANGED
@@ -1662,7 +1662,7 @@ var init_atomicWrite = __esm({
1662
1662
 
1663
1663
  // src/cli/generateZodArtifacts.ts
1664
1664
  import path11 from "path";
1665
- import { existsSync } from "fs";
1665
+ import { existsSync as existsSync2 } from "fs";
1666
1666
  async function generateZodArtifacts(sources, options) {
1667
1667
  const { generateFileSource, resolversByFile, rootDir, dist } = options;
1668
1668
  if (sources.length === 0) return;
@@ -1696,7 +1696,7 @@ async function generateZodArtifacts(sources, options) {
1696
1696
  const allSourceCode = fileEntries.map((e) => e.source).join("\n");
1697
1697
  if (usesCoerceHelpers(allSourceCode)) {
1698
1698
  const helpersPath = path11.resolve(rootDir, dist, HELPERS_FILENAME);
1699
- if (!existsSync(helpersPath)) {
1699
+ if (!existsSync2(helpersPath)) {
1700
1700
  await atomicWriteFile(helpersPath, generateHelpersFileSource());
1701
1701
  }
1702
1702
  }
@@ -2046,14 +2046,14 @@ var ValidationError = class extends FaapiError {
2046
2046
  issues;
2047
2047
  };
2048
2048
  var RouteNotFoundError = class extends FaapiError {
2049
- constructor(path31) {
2050
- super("ROUTE_NOT_FOUND", `Route not found: ${path31}`, 404);
2049
+ constructor(path32) {
2050
+ super("ROUTE_NOT_FOUND", `Route not found: ${path32}`, 404);
2051
2051
  this.name = "RouteNotFoundError";
2052
2052
  }
2053
2053
  };
2054
2054
  var MethodNotAllowedError = class extends FaapiError {
2055
- constructor(method, path31, allowedMethods) {
2056
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path31}`, 405);
2055
+ constructor(method, path32, allowedMethods) {
2056
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path32}`, 405);
2057
2057
  this.allowedMethods = allowedMethods;
2058
2058
  this.name = "MethodNotAllowedError";
2059
2059
  }
@@ -2079,8 +2079,16 @@ var PayloadTooLargeError = class extends FaapiError {
2079
2079
  };
2080
2080
 
2081
2081
  // src/task/taskWorker.ts
2082
+ import { existsSync } from "fs";
2082
2083
  import { Worker } from "worker_threads";
2083
- import { pathToFileURL } from "url";
2084
+ import { fileURLToPath, pathToFileURL } from "url";
2085
+ var workerEntryUrl;
2086
+ function resolveWorkerEntryUrl() {
2087
+ if (workerEntryUrl) return workerEntryUrl;
2088
+ const js = new URL("./workerEntry.js", import.meta.url);
2089
+ workerEntryUrl = existsSync(fileURLToPath(js)) ? js : new URL("./workerEntry.ts", import.meta.url);
2090
+ return workerEntryUrl;
2091
+ }
2084
2092
  var KILL_GRACE_MS = 5e3;
2085
2093
  var TaskCancelledError = class extends Error {
2086
2094
  constructor(message) {
@@ -2101,153 +2109,6 @@ function safeConfig(config) {
2101
2109
  return void 0;
2102
2110
  }
2103
2111
  }
2104
- var BUILD_VIEW_SOURCE = `
2105
- function buildRegistriesView(snapshot) {
2106
- var agents = new Map(((snapshot && snapshot.agents) || []).map(function (a) { return [a.name, a]; }));
2107
- var tools = new Map(((snapshot && snapshot.tools) || []).map(function (t) { return [t.name, t]; }));
2108
- var skills = new Map(((snapshot && snapshot.skills) || []).map(function (s) { return [s.name, s]; }));
2109
- return {
2110
- agent: {
2111
- getAgent: function (name) { return agents.get(name); },
2112
- getAgentEntry: function (name) { return agents.get(name); },
2113
- listAgents: function () { return Array.from(agents.values()); },
2114
- asTool: function (name) {
2115
- var agent = agents.get(name);
2116
- if (!agent) return undefined;
2117
- return {
2118
- kind: 'agent',
2119
- name: 'agent.' + agent.name,
2120
- agentName: agent.name,
2121
- description: agent.description,
2122
- metadata: agent,
2123
- };
2124
- },
2125
- resolveAgentTools: function (name) {
2126
- var agent = agents.get(name);
2127
- var result = new Map();
2128
- if (agent && agent.tools) {
2129
- agent.tools.forEach(function (toolName) {
2130
- var resolved = tools.get(toolName);
2131
- if (resolved) result.set(resolved.name, resolved);
2132
- });
2133
- }
2134
- return Array.from(result.values());
2135
- },
2136
- resolveSubAgents: function (name) {
2137
- var agent = agents.get(name);
2138
- var result = [];
2139
- if (agent && agent.agents) {
2140
- agent.agents.forEach(function (subName) {
2141
- var sub = agents.get(subName);
2142
- if (sub) result.push(sub);
2143
- });
2144
- }
2145
- return result;
2146
- },
2147
- },
2148
- tool: {
2149
- get: function (name) { return tools.get(name); },
2150
- list: function () { return Array.from(tools.values()); },
2151
- },
2152
- skill: {
2153
- get: function (name) { return skills.get(name); },
2154
- list: function () { return Array.from(skills.values()); },
2155
- },
2156
- };
2157
- }
2158
- `;
2159
- var SERIALIZE_ERROR_SOURCE = `
2160
- function serializeError(err) {
2161
- if (err instanceof Error) {
2162
- var props = {};
2163
- var keys = Object.keys(err);
2164
- for (var i = 0; i < keys.length; i++) {
2165
- props[keys[i]] = err[keys[i]];
2166
- }
2167
- return { name: err.name, message: err.message, stack: err.stack, props: props };
2168
- }
2169
- return { name: 'Error', message: String(err), stack: undefined, props: {} };
2170
- }
2171
- `;
2172
- var BUILD_LOGGER_SOURCE = `
2173
- var LOG_RANK = { debug: 0, info: 1, warn: 2, error: 3 };
2174
- function createTaskLogger(level, scope, fields) {
2175
- function make(suffix) {
2176
- function write(lvl, message, callFields) {
2177
- if (level !== undefined && LOG_RANK[lvl] < LOG_RANK[level]) return;
2178
- var entry = { level: lvl, message: message, time: new Date().toISOString() };
2179
- var fullScope = suffix === undefined ? scope : scope === undefined ? suffix : scope + ':' + suffix;
2180
- if (fullScope !== undefined) entry.scope = fullScope;
2181
- if (fields !== undefined || callFields !== undefined) {
2182
- entry.fields = Object.assign({}, fields, callFields);
2183
- }
2184
- try {
2185
- parentPort.postMessage({ type: 'log', entry: entry });
2186
- } catch (_) {
2187
- entry.fields = { warning: 'log fields not cloneable across worker boundary, dropped' };
2188
- parentPort.postMessage({ type: 'log', entry: entry });
2189
- }
2190
- }
2191
- return {
2192
- debug: function (m, f) { write('debug', m, f); },
2193
- info: function (m, f) { write('info', m, f); },
2194
- warn: function (m, f) { write('warn', m, f); },
2195
- error: function (m, f) { write('error', m, f); },
2196
- child: function (cs) { return make(suffix === undefined ? cs : suffix + ':' + cs); },
2197
- };
2198
- }
2199
- return make(undefined);
2200
- }
2201
- `;
2202
- function buildWrapperSource(moduleUrl) {
2203
- return `
2204
- import { parentPort } from 'node:worker_threads';
2205
- import * as mod from '${moduleUrl}';
2206
-
2207
- ${BUILD_VIEW_SOURCE}
2208
-
2209
- ${SERIALIZE_ERROR_SOURCE}
2210
-
2211
- ${BUILD_LOGGER_SOURCE}
2212
-
2213
- const run = mod.run;
2214
- if (typeof run !== 'function') {
2215
- parentPort.postMessage({
2216
- type: 'error',
2217
- error: { name: 'Error', message: 'Task module has no run export', stack: undefined, props: {} },
2218
- });
2219
- } else {
2220
- let controller = null;
2221
- parentPort.on('message', async (msg) => {
2222
- if (msg?.type === 'abort') {
2223
- controller?.abort(new Error(msg.reason));
2224
- return;
2225
- }
2226
- if (msg?.type !== 'run') return;
2227
- controller = new AbortController();
2228
- const { payload, taskCtx } = msg;
2229
- try {
2230
- const registries = buildRegistriesView(msg.registries);
2231
- const progress = (value) => parentPort.postMessage({ type: 'progress', value });
2232
- const taskLog = msg.log ? createTaskLogger(msg.log.level, msg.log.scope, msg.log.fields) : undefined;
2233
- const result = await run(payload, { ...taskCtx, signal: controller.signal, registries, progress, log: taskLog });
2234
- parentPort.postMessage({ type: 'done', result });
2235
- } catch (err) {
2236
- const serialized = serializeError(err);
2237
- try {
2238
- parentPort.postMessage({ type: 'error', error: serialized });
2239
- } catch (_) {
2240
- // props \u542B\u4E0D\u53EF\u514B\u9686\u503C\uFF08\u51FD\u6570\u7B49\uFF09\u65F6\u4E22\u5F03 props\uFF0C\u4FDD\u5E95 name/message/stack
2241
- parentPort.postMessage({
2242
- type: 'error',
2243
- error: { name: serialized.name, message: serialized.message, stack: serialized.stack, props: {} },
2244
- });
2245
- }
2246
- }
2247
- });
2248
- }
2249
- `;
2250
- }
2251
2112
  function reviveError(payload) {
2252
2113
  const err = new Error(payload?.message ?? "task worker error");
2253
2114
  if (payload?.name !== void 0) err.name = payload.name;
@@ -2268,13 +2129,12 @@ async function runTaskInWorker(options) {
2268
2129
  );
2269
2130
  }
2270
2131
  const moduleUrl = pathToFileURL(taskModulePath).href;
2271
- const wrapperUrl = new URL(
2272
- `data:text/javascript,${encodeURIComponent(buildWrapperSource(moduleUrl))}`
2273
- );
2274
2132
  return new Promise((resolve, reject) => {
2275
2133
  let worker;
2276
2134
  try {
2277
- worker = new Worker(wrapperUrl);
2135
+ worker = new Worker(resolveWorkerEntryUrl(), {
2136
+ workerData: { moduleUrl, resourcesDir: taskCtx.resourcesDir }
2137
+ });
2278
2138
  } catch (err) {
2279
2139
  reject(err);
2280
2140
  return;
@@ -2997,6 +2857,7 @@ function parseNumericLiteral(raw) {
2997
2857
  return Number(raw.replace(/_/g, ""));
2998
2858
  }
2999
2859
  var MIN_ISOLATED_TIMEOUT_MS = 6e4;
2860
+ var MAX_ISOLATED_TIMEOUT_MS = 23 * 60 * 6e4;
3000
2861
  function filePathToTaskName(filePath) {
3001
2862
  const normalized = filePath.replace(/\\/g, "/");
3002
2863
  const match = normalized.match(/(?:^|\/)tasks\/(.+)\/task\.ts$/);
@@ -3045,6 +2906,11 @@ async function scanTasks(rootDir, patterns) {
3045
2906
  `[faapi] Task "${name}" timeoutMs ${value} is below the minimum ${MIN_ISOLATED_TIMEOUT_MS}ms (1 minute). Declaring a timeout means this is a long-running task that needs real cancellation \u2014 tasks finishing within a minute do not need one: remove timeoutMs to run in-process (implement your own deadline with Promise.race if needed), or raise it.`
3046
2907
  );
3047
2908
  }
2909
+ if (value > MAX_ISOLATED_TIMEOUT_MS) {
2910
+ throw new Error(
2911
+ `[faapi] Task "${name}" timeoutMs ${value} exceeds the maximum ${MAX_ISOLATED_TIMEOUT_MS}ms (23 hours). The pg-boss driver budgets expire_in from timeoutMs (execution budget preventing mid-run re-delivery), and pg-boss versions before 12.28 assert expire_in strictly below 24h \u2014 the 23h cap keeps every driver and pg-boss version on a single bound. Split the work into a resumable pipeline (persist progress, enqueue the next stage), or use the bullmq driver (Redis has no such cap).`
2912
+ );
2913
+ }
3048
2914
  manifest.timeoutMs = value;
3049
2915
  }
3050
2916
  if (graceMs !== void 0) manifest.graceMs = parseNumericLiteral(graceMs);
@@ -3778,7 +3644,7 @@ async function loadToolModule(filePath, functionName, rootDir) {
3778
3644
  }
3779
3645
 
3780
3646
  // src/loader/loadToolSchema.ts
3781
- import { existsSync as existsSync2 } from "fs";
3647
+ import { existsSync as existsSync3 } from "fs";
3782
3648
 
3783
3649
  // src/cli/generateToolArtifacts.ts
3784
3650
  init_generateZodArtifacts();
@@ -4012,7 +3878,7 @@ async function loadToolSchema(tool, rootDir) {
4012
3878
  if (!tool.inputTypeName) return void 0;
4013
3879
  const schemaName = `${tool.inputTypeName}Schema`;
4014
3880
  const zodPath = getToolSchemaPath(tool, rootDir);
4015
- if (!existsSync2(zodPath)) return void 0;
3881
+ if (!existsSync3(zodPath)) return void 0;
4016
3882
  try {
4017
3883
  const mod = await importWithCacheBust(zodPath, isDevOnDemandEnabled());
4018
3884
  const schema = mod[`${tool.inputTypeName}Schema`];
@@ -4320,9 +4186,53 @@ function loadEnv(rootDir) {
4320
4186
  }
4321
4187
  }
4322
4188
 
4189
+ // src/utils/readResource.ts
4190
+ import path18 from "path";
4191
+ import fs15 from "fs/promises";
4192
+ var ACTIVE_RESOURCES_DIR_KEY = /* @__PURE__ */ Symbol.for("faapi.resources.dir");
4193
+ function getActiveResourcesDir() {
4194
+ return globalThis[ACTIVE_RESOURCES_DIR_KEY] ?? null;
4195
+ }
4196
+ function setActiveResourcesDir(dir) {
4197
+ if (dir === null) {
4198
+ delete globalThis[ACTIVE_RESOURCES_DIR_KEY];
4199
+ } else {
4200
+ globalThis[ACTIVE_RESOURCES_DIR_KEY] = dir;
4201
+ }
4202
+ }
4203
+ function resolveResourceTarget(resourcesDir, relativePath) {
4204
+ if (typeof relativePath !== "string" || relativePath === "") {
4205
+ throw new Error("[faapi] readResource: relativePath must be a non-empty string");
4206
+ }
4207
+ const root = toRealPath(path18.resolve(resourcesDir));
4208
+ const resolved = path18.resolve(root, relativePath);
4209
+ if (!isInsideDir(resolved, root)) {
4210
+ throw new Error(
4211
+ `[faapi] readResource: "${relativePath}" escapes the resources directory (absolute paths and ".." are not allowed)`
4212
+ );
4213
+ }
4214
+ const realTarget = toRealPath(resolved);
4215
+ if (!isInsideDir(realTarget, root)) {
4216
+ throw new Error(
4217
+ `[faapi] readResource: "${relativePath}" resolves outside the resources directory via a symlink`
4218
+ );
4219
+ }
4220
+ return realTarget;
4221
+ }
4222
+ async function readResource(relativePath, encoding) {
4223
+ const resourcesDir = getActiveResourcesDir();
4224
+ if (!resourcesDir) {
4225
+ throw new Error(
4226
+ "[faapi] readResource: no active app in this process \u2014 resources are bound at app startup (createAppBase); testing direct-call can pass `resourcesDir` to createTestContext to bind"
4227
+ );
4228
+ }
4229
+ const target = resolveResourceTarget(resourcesDir, relativePath);
4230
+ return encoding === void 0 ? fs15.readFile(target) : fs15.readFile(target, encoding);
4231
+ }
4232
+
4323
4233
  // src/cli/createAppCore.ts
4324
- import fs21 from "fs";
4325
- import path27 from "path";
4234
+ import fs22 from "fs";
4235
+ import path28 from "path";
4326
4236
 
4327
4237
  // src/router/sortRoutes.ts
4328
4238
  function sortRoutes(routes) {
@@ -4386,7 +4296,7 @@ import {
4386
4296
  import { createSecureServer as createHttp2SecureServer } from "http2";
4387
4297
  import { readFileSync } from "fs";
4388
4298
  import { Readable as Readable2 } from "stream";
4389
- import path20 from "path";
4299
+ import path21 from "path";
4390
4300
 
4391
4301
  // src/router/matchRoute.ts
4392
4302
  var httpIndexCache = /* @__PURE__ */ new WeakMap();
@@ -4428,25 +4338,25 @@ function getWsIndex(routes) {
4428
4338
  wsIndexCache.set(routes, index);
4429
4339
  return index;
4430
4340
  }
4431
- function matchRoute(routes, method, path31) {
4341
+ function matchRoute(routes, method, path32) {
4432
4342
  const index = getHttpIndex(routes);
4433
4343
  const upper = method.toUpperCase();
4434
- const hit = matchByMethod(index, upper, path31);
4344
+ const hit = matchByMethod(index, upper, path32);
4435
4345
  if (hit) return hit;
4436
4346
  if (upper === "HEAD") {
4437
- return matchByMethod(index, "GET", path31);
4347
+ return matchByMethod(index, "GET", path32);
4438
4348
  }
4439
4349
  return null;
4440
4350
  }
4441
- function matchByMethod(index, method, path31) {
4442
- const staticHit = index.static.get(`${method}|${path31}`);
4351
+ function matchByMethod(index, method, path32) {
4352
+ const staticHit = index.static.get(`${method}|${path32}`);
4443
4353
  if (staticHit) {
4444
4354
  return { route: staticHit, params: {} };
4445
4355
  }
4446
4356
  if (index.dynamics.length === 0) {
4447
4357
  return null;
4448
4358
  }
4449
- const pathSegments = path31.split("/").filter(Boolean);
4359
+ const pathSegments = path32.split("/").filter(Boolean);
4450
4360
  for (const entry of index.dynamics) {
4451
4361
  const route = entry.route;
4452
4362
  if (route.method !== method) {
@@ -4459,31 +4369,31 @@ function matchByMethod(index, method, path31) {
4459
4369
  }
4460
4370
  return null;
4461
4371
  }
4462
- function matchWsRoute(wsRoutes, path31) {
4372
+ function matchWsRoute(wsRoutes, path32) {
4463
4373
  const index = getWsIndex(wsRoutes);
4464
- const staticHit = index.static.get(path31);
4374
+ const staticHit = index.static.get(path32);
4465
4375
  if (staticHit) {
4466
4376
  return { route: staticHit, params: {} };
4467
4377
  }
4468
4378
  for (const route of index.dynamics) {
4469
- const params = matchDynamicPath(route.urlPath, path31, route.paramNames, route.isCatchAll);
4379
+ const params = matchDynamicPath(route.urlPath, path32, route.paramNames, route.isCatchAll);
4470
4380
  if (params !== null) {
4471
4381
  return { route, params };
4472
4382
  }
4473
4383
  }
4474
4384
  return null;
4475
4385
  }
4476
- function findAllowedMethods(routes, path31) {
4386
+ function findAllowedMethods(routes, path32) {
4477
4387
  const index = getHttpIndex(routes);
4478
4388
  const methods = /* @__PURE__ */ new Set();
4479
- const staticMethods = index.methodsByStaticPath.get(path31);
4389
+ const staticMethods = index.methodsByStaticPath.get(path32);
4480
4390
  if (staticMethods) {
4481
4391
  for (const method of staticMethods) {
4482
4392
  methods.add(method);
4483
4393
  }
4484
4394
  }
4485
4395
  if (index.dynamics.length > 0) {
4486
- const pathSegments = path31.split("/").filter(Boolean);
4396
+ const pathSegments = path32.split("/").filter(Boolean);
4487
4397
  for (const entry of index.dynamics) {
4488
4398
  const params = matchSegments(
4489
4399
  entry.segments,
@@ -4501,10 +4411,10 @@ function findAllowedMethods(routes, path31) {
4501
4411
  }
4502
4412
  return Array.from(methods);
4503
4413
  }
4504
- function matchDynamicPath(pattern, path31, paramNames, isCatchAll) {
4414
+ function matchDynamicPath(pattern, path32, paramNames, isCatchAll) {
4505
4415
  return matchSegments(
4506
4416
  pattern.split("/").filter(Boolean),
4507
- path31.split("/").filter(Boolean),
4417
+ path32.split("/").filter(Boolean),
4508
4418
  paramNames,
4509
4419
  isCatchAll
4510
4420
  );
@@ -5643,9 +5553,9 @@ async function validateInput(schemaPath, method, inputType, input) {
5643
5553
  function mapZodIssues(error) {
5644
5554
  return error.issues.map((issue) => {
5645
5555
  const code = mapZodCode(issue);
5646
- const path31 = issue.path.map(String).join(".") || "";
5556
+ const path32 = issue.path.map(String).join(".") || "";
5647
5557
  return {
5648
- path: path31,
5558
+ path: path32,
5649
5559
  code,
5650
5560
  expected: issue.expected ?? mapExpectedFromMessage(issue.message),
5651
5561
  received: issue.received ?? mapReceivedFromMessage(issue.message),
@@ -5866,16 +5776,16 @@ function etag(options = {}) {
5866
5776
  }
5867
5777
 
5868
5778
  // src/server/handleWsUpgrade.ts
5869
- import fs16 from "fs";
5779
+ import fs17 from "fs";
5870
5780
  import { WebSocketServer, WebSocket } from "ws";
5871
- import path19 from "path";
5781
+ import path20 from "path";
5872
5782
 
5873
5783
  // src/cli/copyResources.ts
5874
- import path18 from "path";
5875
- import fs15 from "fs";
5784
+ import path19 from "path";
5785
+ import fs16 from "fs";
5876
5786
  var RESOURCES_OUTPUT_DIR = "resources";
5877
5787
  function resolveResourcesDir(rootDir, dist) {
5878
- return path18.resolve(rootDir, dist, RESOURCES_OUTPUT_DIR);
5788
+ return path19.resolve(rootDir, dist, RESOURCES_OUTPUT_DIR);
5879
5789
  }
5880
5790
 
5881
5791
  // src/server/serverUtils.ts
@@ -6016,7 +5926,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
6016
5926
  const dist = getDevDist();
6017
5927
  if (dist) {
6018
5928
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
6019
- if (sourcePath && fs16.existsSync(sourcePath)) {
5929
+ if (sourcePath && fs17.existsSync(sourcePath)) {
6020
5930
  await ensureCompiled(sourcePath, rootDir, dist);
6021
5931
  }
6022
5932
  }
@@ -6158,7 +6068,7 @@ function attachWebSocket(options) {
6158
6068
  let upgraded = false;
6159
6069
  const finalHandler = async () => {
6160
6070
  const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
6161
- if (fs16.existsSync(schemaPath)) {
6071
+ if (fs17.existsSync(schemaPath)) {
6162
6072
  if (isDevOnDemandEnabled()) {
6163
6073
  const devDist = getDevDist();
6164
6074
  if (devDist) {
@@ -6197,7 +6107,7 @@ function attachWebSocket(options) {
6197
6107
  }
6198
6108
  let handlers;
6199
6109
  try {
6200
- const absoluteFilePath = path19.resolve(rootDir, route.filePath);
6110
+ const absoluteFilePath = path20.resolve(rootDir, route.filePath);
6201
6111
  handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
6202
6112
  } catch (err) {
6203
6113
  const reason = err instanceof Error ? err.message : String(err);
@@ -6460,7 +6370,7 @@ function getRoutePaths(route, rootDir, dist) {
6460
6370
  let cached = routePathCache.get(route);
6461
6371
  if (!cached) {
6462
6372
  cached = {
6463
- absFilePath: path20.resolve(rootDir, route.filePath),
6373
+ absFilePath: path21.resolve(rootDir, route.filePath),
6464
6374
  schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
6465
6375
  };
6466
6376
  routePathCache.set(route, cached);
@@ -6680,8 +6590,8 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
6680
6590
  }
6681
6591
 
6682
6592
  // src/cli/generateRoutes.ts
6683
- import fs17 from "fs";
6684
- import path21 from "path";
6593
+ import fs18 from "fs";
6594
+ import path22 from "path";
6685
6595
  async function hydrateRoutes(manifest) {
6686
6596
  const hydrateRoute = (serialized) => ({
6687
6597
  method: serialized.method,
@@ -6725,18 +6635,18 @@ function createIdleTaskDriver() {
6725
6635
  }
6726
6636
 
6727
6637
  // src/cli/loadPlugins.ts
6728
- import path23 from "path";
6729
- import fs19 from "fs";
6638
+ import path24 from "path";
6639
+ import fs20 from "fs";
6730
6640
  import { pathToFileURL as pathToFileURL3 } from "url";
6731
6641
 
6732
6642
  // src/cli/compileConfig.ts
6733
- import path22 from "path";
6734
- import fs18 from "fs";
6643
+ import path23 from "path";
6644
+ import fs19 from "fs";
6735
6645
  async function compileProjectModules(entryPoints, rootDir, dist) {
6736
6646
  const { insideFiles, outsideFiles } = await collectRelativeImports(entryPoints, rootDir);
6737
6647
  const esbuild = await import("esbuild");
6738
6648
  const aliasPlugins = buildAliasPlugins(rootDir);
6739
- const absDist = path22.resolve(rootDir, dist);
6649
+ const absDist = path23.resolve(rootDir, dist);
6740
6650
  await esbuild.build({
6741
6651
  entryPoints: [...entryPoints, ...outsideFiles],
6742
6652
  outdir: absDist,
@@ -6750,7 +6660,7 @@ async function compileProjectModules(entryPoints, rootDir, dist) {
6750
6660
  logLevel: "silent"
6751
6661
  });
6752
6662
  if (insideFiles.length > 0) {
6753
- const appOutbase = path22.resolve(rootDir, "src");
6663
+ const appOutbase = path23.resolve(rootDir, "src");
6754
6664
  await esbuild.build({
6755
6665
  entryPoints: insideFiles,
6756
6666
  outdir: absDist,
@@ -6831,30 +6741,30 @@ async function loadPlugins(declarations, ctx, rootDir, dist) {
6831
6741
  var TS_EXTS = /\.(ts|tsx|mts|cts)$/;
6832
6742
  var JS_EXTS = /\.(js|mjs|cjs|jsx)$/;
6833
6743
  function resolveLocalPluginSource(specifier, baseDir) {
6834
- const base = path23.isAbsolute(specifier) ? specifier : path23.resolve(baseDir, specifier);
6835
- if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs19.existsSync(base)) return base;
6744
+ const base = path24.isAbsolute(specifier) ? specifier : path24.resolve(baseDir, specifier);
6745
+ if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs20.existsSync(base)) return base;
6836
6746
  for (const ext of [".ts", ".js"]) {
6837
- if (fs19.existsSync(base + ext)) return base + ext;
6747
+ if (fs20.existsSync(base + ext)) return base + ext;
6838
6748
  }
6839
6749
  for (const indexExt of ["/index.ts", "/index.js"]) {
6840
- if (fs19.existsSync(base + indexExt)) return base + indexExt;
6750
+ if (fs20.existsSync(base + indexExt)) return base + indexExt;
6841
6751
  }
6842
6752
  return null;
6843
6753
  }
6844
6754
  function mirrorProductPath(sourcePath, baseDir, distDir) {
6845
- let rel = path23.relative(baseDir, sourcePath).replace(/\\/g, "/");
6755
+ let rel = path24.relative(baseDir, sourcePath).replace(/\\/g, "/");
6846
6756
  if (rel.startsWith("src/")) rel = rel.slice(4);
6847
- return path23.resolve(distDir, rel.replace(TS_EXTS, ".js"));
6757
+ return path24.resolve(distDir, rel.replace(TS_EXTS, ".js"));
6848
6758
  }
6849
6759
  async function importPluginModule(specifier, baseDir, dist) {
6850
6760
  const isRelative = specifier.startsWith("./") || specifier.startsWith("../");
6851
- if (!isRelative && !path23.isAbsolute(specifier)) {
6761
+ if (!isRelative && !path24.isAbsolute(specifier)) {
6852
6762
  return import(specifier);
6853
6763
  }
6854
- const distDir = dist ? path23.resolve(baseDir, dist) : null;
6764
+ const distDir = dist ? path24.resolve(baseDir, dist) : null;
6855
6765
  const sourcePath = resolveLocalPluginSource(specifier, baseDir);
6856
6766
  const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
6857
- if (productPath !== null && fs19.existsSync(productPath) && (sourcePath === null || isProductFresh(sourcePath, productPath))) {
6767
+ if (productPath !== null && fs20.existsSync(productPath) && (sourcePath === null || isProductFresh(sourcePath, productPath))) {
6858
6768
  return import(pathToFileURL3(productPath).href);
6859
6769
  }
6860
6770
  if (sourcePath && TS_EXTS.test(sourcePath)) {
@@ -6863,13 +6773,13 @@ async function importPluginModule(specifier, baseDir, dist) {
6863
6773
  `Local plugin "${specifier}" has no up-to-date build artifact (dist is stale or missing). Run "faapi build" first, or use "faapi dev" for on-demand compilation.`
6864
6774
  );
6865
6775
  }
6866
- const relFromRoot = path23.relative(baseDir, sourcePath).replace(/\\/g, "/");
6776
+ const relFromRoot = path24.relative(baseDir, sourcePath).replace(/\\/g, "/");
6867
6777
  if (relFromRoot.startsWith("src/")) {
6868
6778
  await ensureCompiled(sourcePath, baseDir, dist);
6869
6779
  } else {
6870
6780
  await compileProjectModules([sourcePath], baseDir, dist);
6871
6781
  }
6872
- if (productPath && fs19.existsSync(productPath)) {
6782
+ if (productPath && fs20.existsSync(productPath)) {
6873
6783
  return import(pathToFileURL3(productPath).href);
6874
6784
  }
6875
6785
  }
@@ -6881,7 +6791,7 @@ async function importPluginModule(specifier, baseDir, dist) {
6881
6791
  `${specifier}/index.ts`,
6882
6792
  `${specifier}.js`,
6883
6793
  `${specifier}/index.js`
6884
- ].map((c) => path23.isAbsolute(c) ? c : path23.join(baseDir, c));
6794
+ ].map((c) => path24.isAbsolute(c) ? c : path24.join(baseDir, c));
6885
6795
  throw new Error(
6886
6796
  `Cannot find local plugin "${specifier}" (resolved from ${baseDir}). Expected one of:
6887
6797
  ${candidates.join("\n ")}
@@ -6906,11 +6816,11 @@ function resolveDeclaration(decl) {
6906
6816
  }
6907
6817
 
6908
6818
  // src/cli/manifestLoader.ts
6909
- import fs20 from "fs";
6910
- import path26 from "path";
6819
+ import fs21 from "fs";
6820
+ import path27 from "path";
6911
6821
 
6912
6822
  // src/cli/generateAgentArtifacts.ts
6913
- import path24 from "path";
6823
+ import path25 from "path";
6914
6824
 
6915
6825
  // src/ast/extractAgentMetadata.ts
6916
6826
  import ts11 from "typescript";
@@ -7227,9 +7137,9 @@ function validateAgentList(metadata) {
7227
7137
  }
7228
7138
  async function generateAgentArtifacts(agents, rootDir, dist) {
7229
7139
  const metadata = [];
7230
- const programByFile = createPrograms(agents.map((m) => path24.resolve(rootDir, m.filePath)));
7140
+ const programByFile = createPrograms(agents.map((m) => path25.resolve(rootDir, m.filePath)));
7231
7141
  for (const manifest of agents) {
7232
- const absPath = path24.resolve(rootDir, manifest.filePath);
7142
+ const absPath = path25.resolve(rootDir, manifest.filePath);
7233
7143
  const program = programByFile.get(absPath);
7234
7144
  const result = extractAgentMetadata(program, absPath, {
7235
7145
  name: manifest.name,
@@ -7244,14 +7154,14 @@ async function generateAgentArtifacts(agents, rootDir, dist) {
7244
7154
  }
7245
7155
  validateAgentList(metadata);
7246
7156
  const serialized = serializeAgents(metadata, dist);
7247
- const agentsPath = path24.resolve(rootDir, dist, AGENTS_FILE);
7157
+ const agentsPath = path25.resolve(rootDir, dist, AGENTS_FILE);
7248
7158
  await writeAgentsModule(serialized, agentsPath);
7249
7159
  return metadata;
7250
7160
  }
7251
7161
 
7252
7162
  // src/cli/generateTaskArtifacts.ts
7253
7163
  init_generateZodArtifacts();
7254
- import path25 from "path";
7164
+ import path26 from "path";
7255
7165
  init_createProgram();
7256
7166
  init_atomicWrite();
7257
7167
  init_extractHandlerTypes();
@@ -7289,7 +7199,7 @@ function collectTaskSchemaSources(tasks, rootDir) {
7289
7199
  const tasksByFile = /* @__PURE__ */ new Map();
7290
7200
  for (const task of tasks) {
7291
7201
  if (!task.inputTypeName) continue;
7292
- const absPath = path25.resolve(rootDir, task.filePath);
7202
+ const absPath = path26.resolve(rootDir, task.filePath);
7293
7203
  let list = tasksByFile.get(absPath);
7294
7204
  if (!list) {
7295
7205
  list = [];
@@ -7346,9 +7256,9 @@ function generateTaskSchemaFileSource(sources, resolveType, helpersImportPath) {
7346
7256
  async function generateTaskArtifacts(manifests, rootDir, dist) {
7347
7257
  const metadata = [];
7348
7258
  if (manifests.length > 0) {
7349
- const programByFile = createPrograms(manifests.map((m) => path25.resolve(rootDir, m.filePath)));
7259
+ const programByFile = createPrograms(manifests.map((m) => path26.resolve(rootDir, m.filePath)));
7350
7260
  for (const manifest of manifests) {
7351
- const absPath = path25.resolve(rootDir, manifest.filePath);
7261
+ const absPath = path26.resolve(rootDir, manifest.filePath);
7352
7262
  const result = extractToolMetadata(programByFile.get(absPath), absPath, "run", {
7353
7263
  name: manifest.name,
7354
7264
  filePath: manifest.filePath
@@ -7359,7 +7269,7 @@ async function generateTaskArtifacts(manifests, rootDir, dist) {
7359
7269
  }
7360
7270
  }
7361
7271
  const serialized = serializeTasks(manifests, dist);
7362
- const tasksPath = path25.resolve(rootDir, dist, TASKS_FILE);
7272
+ const tasksPath = path26.resolve(rootDir, dist, TASKS_FILE);
7363
7273
  await writeTasksModule(serialized, tasksPath);
7364
7274
  if (metadata.length === 0) {
7365
7275
  return hydrateTasks(serialized);
@@ -7381,8 +7291,8 @@ async function generateTaskArtifacts(manifests, rootDir, dist) {
7381
7291
  var TOOLS_FILE2 = "faapi-tools.js";
7382
7292
  var AGENTS_FILE2 = "faapi-agents.js";
7383
7293
  async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries) {
7384
- const toolsPath = path26.resolve(rootDir, dist, TOOLS_FILE2);
7385
- if (!fs20.existsSync(toolsPath)) {
7294
+ const toolsPath = path27.resolve(rootDir, dist, TOOLS_FILE2);
7295
+ if (!fs21.existsSync(toolsPath)) {
7386
7296
  return [];
7387
7297
  }
7388
7298
  const serialized = await importWithCacheBust(toolsPath);
@@ -7391,8 +7301,8 @@ async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries
7391
7301
  return hydrated;
7392
7302
  }
7393
7303
  async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistries) {
7394
- const agentsPath = path26.resolve(rootDir, dist, AGENTS_FILE2);
7395
- if (!fs20.existsSync(agentsPath)) {
7304
+ const agentsPath = path27.resolve(rootDir, dist, AGENTS_FILE2);
7305
+ if (!fs21.existsSync(agentsPath)) {
7396
7306
  return [];
7397
7307
  }
7398
7308
  const serialized = await importWithCacheBust(agentsPath);
@@ -7407,8 +7317,8 @@ function getTaskDriverOptions(config) {
7407
7317
  return void 0;
7408
7318
  }
7409
7319
  async function loadAndHydrateTasks(rootDir, dist, registries = defaultRegistries) {
7410
- const tasksPath = path26.resolve(rootDir, dist, TASKS_FILE);
7411
- if (!fs20.existsSync(tasksPath)) {
7320
+ const tasksPath = path27.resolve(rootDir, dist, TASKS_FILE);
7321
+ if (!fs21.existsSync(tasksPath)) {
7412
7322
  return [];
7413
7323
  }
7414
7324
  const serialized = await importWithCacheBust(tasksPath);
@@ -7597,11 +7507,17 @@ function logStartupSummary(routes, wsRoutes, tools, agents, taskMetas, p) {
7597
7507
  }
7598
7508
  }
7599
7509
  async function createAppBase(options) {
7510
+ if (getCurrentApp()) {
7511
+ throw new Error(
7512
+ "[faapi] createApp: an app is already alive in this process \u2014 faapi supports a single app per process. Close it first (`await app.close()`) or run the second app in a separate process."
7513
+ );
7514
+ }
7600
7515
  const rootDir = options?.rootDir ?? process.cwd();
7601
7516
  const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
7602
7517
  const resourcesDir = resolveResourcesDir(rootDir, dist);
7603
- const routesPath = path27.resolve(rootDir, dist, ROUTES_FILE);
7604
- if (!fs21.existsSync(routesPath)) {
7518
+ setActiveResourcesDir(resourcesDir);
7519
+ const routesPath = path28.resolve(rootDir, dist, ROUTES_FILE);
7520
+ if (!fs22.existsSync(routesPath)) {
7605
7521
  throw new Error(
7606
7522
  `[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
7607
7523
  );
@@ -7766,6 +7682,7 @@ async function createAppBase(options) {
7766
7682
  registries.task.clear();
7767
7683
  registries.agentHandle.clear();
7768
7684
  registries.taskHandle.clear();
7685
+ setActiveResourcesDir(null);
7769
7686
  if (!server.listening) {
7770
7687
  app.server = null;
7771
7688
  if (getCurrentApp() === app) setCurrentApp(null);
@@ -7819,21 +7736,21 @@ async function createAppBase(options) {
7819
7736
  }
7820
7737
 
7821
7738
  // src/cli/createDevApp.ts
7822
- import path30 from "path";
7739
+ import path31 from "path";
7823
7740
 
7824
7741
  // src/router/scanRoutes.ts
7825
7742
  import fg3 from "fast-glob";
7826
- import path28 from "path";
7827
- import fs22 from "fs";
7743
+ import path29 from "path";
7744
+ import fs23 from "fs";
7828
7745
 
7829
7746
  // src/router/constants.ts
7830
7747
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
7831
7748
  var HTTP_METHOD_SET = new Set(HTTP_METHODS);
7832
7749
 
7833
7750
  // src/utils/normalizePath.ts
7834
- function normalizePath(path31) {
7835
- if (!path31) return "";
7836
- let result = path31.replace(/\\/g, "/");
7751
+ function normalizePath(path32) {
7752
+ if (!path32) return "";
7753
+ let result = path32.replace(/\\/g, "/");
7837
7754
  result = result.replace(/\/+/g, "/");
7838
7755
  result = result.replace(/\/+$/, "");
7839
7756
  if (result && !result.startsWith("/")) {
@@ -7894,34 +7811,34 @@ function extractExportsFromSource(source) {
7894
7811
  return names;
7895
7812
  }
7896
7813
  function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
7897
- const routeDir = path28.dirname(routeFilePath);
7898
- const resolvedRoot = path28.resolve(rootDir);
7814
+ const routeDir = path29.dirname(routeFilePath);
7815
+ const resolvedRoot = path29.resolve(rootDir);
7899
7816
  const paths = [];
7900
- let currentDir = path28.resolve(rootDir, routeDir);
7817
+ let currentDir = path29.resolve(rootDir, routeDir);
7901
7818
  while (true) {
7902
7819
  if (dist) {
7903
- const mwTsPath = path28.join(currentDir, "middlewares.ts");
7904
- const mwJsPath = path28.join(currentDir, "middlewares.js");
7905
- const absTsPath = path28.resolve(rootDir, mwTsPath);
7906
- const absJsPath = path28.resolve(rootDir, mwJsPath);
7907
- const absMwPath = fs22.existsSync(absTsPath) ? absTsPath : fs22.existsSync(absJsPath) ? absJsPath : null;
7820
+ const mwTsPath = path29.join(currentDir, "middlewares.ts");
7821
+ const mwJsPath = path29.join(currentDir, "middlewares.js");
7822
+ const absTsPath = path29.resolve(rootDir, mwTsPath);
7823
+ const absJsPath = path29.resolve(rootDir, mwJsPath);
7824
+ const absMwPath = fs23.existsSync(absTsPath) ? absTsPath : fs23.existsSync(absJsPath) ? absJsPath : null;
7908
7825
  if (absMwPath) {
7909
- const relMwPath = path28.relative(rootDir, absMwPath);
7910
- const prodAbsPath = path28.resolve(rootDir, toProdFilePath(relMwPath, dist));
7826
+ const relMwPath = path29.relative(rootDir, absMwPath);
7827
+ const prodAbsPath = path29.resolve(rootDir, toProdFilePath(relMwPath, dist));
7911
7828
  paths.push(prodAbsPath);
7912
7829
  }
7913
7830
  } else {
7914
7831
  for (const ext of [".ts", ".js"]) {
7915
- const mwPath = path28.join(currentDir, `middlewares${ext}`);
7916
- const absMwPath = path28.resolve(rootDir, mwPath);
7917
- if (fs22.existsSync(absMwPath)) {
7832
+ const mwPath = path29.join(currentDir, `middlewares${ext}`);
7833
+ const absMwPath = path29.resolve(rootDir, mwPath);
7834
+ if (fs23.existsSync(absMwPath)) {
7918
7835
  paths.push(absMwPath);
7919
7836
  break;
7920
7837
  }
7921
7838
  }
7922
7839
  }
7923
7840
  if (currentDir === resolvedRoot) break;
7924
- const parentDir = path28.dirname(currentDir);
7841
+ const parentDir = path29.dirname(currentDir);
7925
7842
  if (parentDir === currentDir) break;
7926
7843
  currentDir = parentDir;
7927
7844
  }
@@ -7940,7 +7857,7 @@ async function scanRoutes(rootDir, patterns, dist) {
7940
7857
  const normalizedFile = file.replace(/\\/g, "/");
7941
7858
  const fileName = normalizedFile.split("/").pop();
7942
7859
  if (fileName === "handler.ts" || fileName === "handler.js") {
7943
- const absPath = path28.resolve(rootDir, normalizedFile);
7860
+ const absPath = path29.resolve(rootDir, normalizedFile);
7944
7861
  const urlPath = filePathToUrlPath(normalizedFile);
7945
7862
  const paramNames = extractParamNames(urlPath);
7946
7863
  const isDynamic = paramNames.length > 0;
@@ -7953,7 +7870,7 @@ async function scanRoutes(rootDir, patterns, dist) {
7953
7870
  const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
7954
7871
  middlewareBundle = await loadMergedMiddlewares(mwPaths);
7955
7872
  }
7956
- const source = await fs22.promises.readFile(absPath, "utf8").catch(() => "");
7873
+ const source = await fs23.promises.readFile(absPath, "utf8").catch(() => "");
7957
7874
  const exportNames = extractExportsFromSource(source);
7958
7875
  const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
7959
7876
  for (const method of methods) {
@@ -7989,8 +7906,8 @@ async function scanRoutes(rootDir, patterns, dist) {
7989
7906
 
7990
7907
  // src/tools/scanTools.ts
7991
7908
  import fg4 from "fast-glob";
7992
- import path29 from "path";
7993
- import fs23 from "fs";
7909
+ import path30 from "path";
7910
+ import fs24 from "fs";
7994
7911
  var TOOL_PATTERNS = ["src/tools/**/*.ts"];
7995
7912
  var TOOL_EXPORT_RE = new RegExp(
7996
7913
  String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
@@ -8040,8 +7957,8 @@ async function scanTools(rootDir, patterns) {
8040
7957
  if (fileName !== "handler.ts" && fileName !== "handler.js") {
8041
7958
  continue;
8042
7959
  }
8043
- const absPath = path29.resolve(rootDir, normalizedFile);
8044
- const source = await fs23.promises.readFile(absPath, "utf8").catch(() => "");
7960
+ const absPath = path30.resolve(rootDir, normalizedFile);
7961
+ const source = await fs24.promises.readFile(absPath, "utf8").catch(() => "");
8045
7962
  const exportNames = extractToolExportsFromSource(source);
8046
7963
  const namespace = filePathToToolNamespace(normalizedFile);
8047
7964
  for (const fnName of exportNames) {
@@ -8158,7 +8075,7 @@ async function createDevApp(options) {
8158
8075
  if (!suppressProgramInvalidation) invalidateProgramCache();
8159
8076
  const tasks = await scanTasks(ctx.rootDir, TASK_PATTERNS);
8160
8077
  for (const task of tasks) {
8161
- await ensureCompiled(path30.resolve(ctx.rootDir, task.filePath), ctx.rootDir, ctx.dist);
8078
+ await ensureCompiled(path31.resolve(ctx.rootDir, task.filePath), ctx.rootDir, ctx.dist);
8162
8079
  }
8163
8080
  await generateTaskArtifacts(tasks, ctx.rootDir, ctx.dist);
8164
8081
  ctx.taskQueue.invalidateModules();
@@ -8230,6 +8147,7 @@ export {
8230
8147
  loadToolModule,
8231
8148
  loadToolSchema,
8232
8149
  logger,
8150
+ readResource,
8233
8151
  registerAgentHandleFactory,
8234
8152
  removeSkill,
8235
8153
  resolveAgentTools,