@faapi/faapi 6.31.0 → 6.33.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
@@ -1126,6 +1126,8 @@ var init_resolveInjection = __esm({
1126
1126
  // 任务子系统:TaskClient(入队/查询)
1127
1127
  log: "log",
1128
1128
  // 请求级日志器(ctx.log 同一实例:scope http,自动带 requestId 等字段)
1129
+ llm: "llm",
1130
+ // 轻量 LLM 补全通道(@faapi/agent 插件注册到 registries.llm;未加载时 undefined)
1129
1131
  // raw 系:恒原始输入(无 schema、无校验、管线永不转换),与 query/params/body
1130
1132
  // 的「校验转换后」口径二分互补
1131
1133
  rawQuery: "rawQuery",
@@ -1970,6 +1972,20 @@ function createAgentHandleStore() {
1970
1972
  }
1971
1973
  };
1972
1974
  }
1975
+ function createLlmChannelStore() {
1976
+ let current = null;
1977
+ return {
1978
+ register(channel) {
1979
+ current = channel;
1980
+ },
1981
+ get() {
1982
+ return current ?? void 0;
1983
+ },
1984
+ clear() {
1985
+ current = null;
1986
+ }
1987
+ };
1988
+ }
1973
1989
  function createTaskHandleStore() {
1974
1990
  let currentFactory = null;
1975
1991
  return {
@@ -2026,7 +2042,8 @@ function createAppRegistries() {
2026
2042
  const task = createTaskRegistry();
2027
2043
  const agentHandle = createAgentHandleStore();
2028
2044
  const taskHandle = createTaskHandleStore();
2029
- return { tool, agent, skill, task, agentHandle, taskHandle };
2045
+ const llm = createLlmChannelStore();
2046
+ return { tool, agent, skill, task, agentHandle, taskHandle, llm };
2030
2047
  }
2031
2048
  var defaultRegistries = createAppRegistries();
2032
2049
 
@@ -2109,19 +2126,6 @@ var TaskCancelledError = class extends Error {
2109
2126
  this.name = "TaskCancelledError";
2110
2127
  }
2111
2128
  };
2112
- function safeConfig(config) {
2113
- if (config === void 0 || config === null) return config;
2114
- try {
2115
- structuredClone(config);
2116
- return config;
2117
- } catch {
2118
- }
2119
- try {
2120
- return JSON.parse(JSON.stringify(config));
2121
- } catch {
2122
- return void 0;
2123
- }
2124
- }
2125
2129
  function reviveError(payload) {
2126
2130
  const err = new Error(payload?.message ?? "task worker error");
2127
2131
  if (payload?.name !== void 0) err.name = payload.name;
@@ -2236,22 +2240,130 @@ async function runTaskInWorker(options) {
2236
2240
  type: "run",
2237
2241
  payload,
2238
2242
  taskCtx: {
2239
- config: safeConfig(taskCtx.config),
2240
2243
  job: taskCtx.job
2241
2244
  },
2242
2245
  registries: options.registries,
2246
+ llms: options.llms,
2243
2247
  log: options.log
2244
2248
  });
2245
2249
  } catch (err) {
2246
2250
  finish(
2247
- () => reject(
2248
- new Error(`Task "${taskCtx.job.name}" payload/config is not cloneable: ${String(err)}`)
2249
- )
2251
+ () => reject(new Error(`Task "${taskCtx.job.name}" payload is not cloneable: ${String(err)}`))
2250
2252
  );
2251
2253
  }
2252
2254
  });
2253
2255
  }
2254
2256
 
2257
+ // src/utils/isPlainObject.ts
2258
+ function isPlainObject(value) {
2259
+ if (value === null || typeof value !== "object") {
2260
+ return false;
2261
+ }
2262
+ if (Array.isArray(value)) {
2263
+ return false;
2264
+ }
2265
+ const proto = Object.getPrototypeOf(value);
2266
+ return proto === null || proto === Object.prototype;
2267
+ }
2268
+
2269
+ // src/utils/stringifyJson.ts
2270
+ function stringifyJson(value) {
2271
+ if (!needsConversion(value, /* @__PURE__ */ new Set())) {
2272
+ return JSON.stringify(value);
2273
+ }
2274
+ return JSON.stringify(convertValue(value, /* @__PURE__ */ new Set()));
2275
+ }
2276
+ function needsConversion(value, ancestors) {
2277
+ if (value === null) return false;
2278
+ const type = typeof value;
2279
+ if (type === "bigint") return true;
2280
+ if (type === "number") return !Number.isFinite(value);
2281
+ if (type !== "object") return false;
2282
+ const obj = value;
2283
+ if (ancestors.has(obj)) {
2284
+ throw new TypeError("[faapi] Converting circular structure to JSON");
2285
+ }
2286
+ if (obj instanceof Date || obj instanceof Map || obj instanceof Set || obj instanceof RegExp) {
2287
+ return true;
2288
+ }
2289
+ if (typeof obj.toJSON === "function") return true;
2290
+ if (Array.isArray(obj)) {
2291
+ ancestors.add(obj);
2292
+ for (let i = 0; i < obj.length; i++) {
2293
+ if (needsConversion(obj[i], ancestors)) {
2294
+ ancestors.delete(obj);
2295
+ return true;
2296
+ }
2297
+ }
2298
+ ancestors.delete(obj);
2299
+ return false;
2300
+ }
2301
+ if (isPlainObject(obj)) {
2302
+ ancestors.add(obj);
2303
+ for (const key in obj) {
2304
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
2305
+ if (needsConversion(obj[key], ancestors)) {
2306
+ ancestors.delete(obj);
2307
+ return true;
2308
+ }
2309
+ }
2310
+ ancestors.delete(obj);
2311
+ return false;
2312
+ }
2313
+ return false;
2314
+ }
2315
+ function convertValue(value, ancestors) {
2316
+ if (value === null) return value;
2317
+ const type = typeof value;
2318
+ if (type === "bigint") return value.toString();
2319
+ if (type === "number") return Number.isFinite(value) ? value : String(value);
2320
+ if (type !== "object") return value;
2321
+ const obj = value;
2322
+ if (ancestors.has(obj)) {
2323
+ throw new TypeError("[faapi] Converting circular structure to JSON");
2324
+ }
2325
+ if (obj instanceof Date) return obj.getTime();
2326
+ if (obj instanceof Map) {
2327
+ ancestors.add(obj);
2328
+ const entries = Array.from(obj, ([k, v]) => [
2329
+ convertValue(k, ancestors),
2330
+ convertValue(v, ancestors)
2331
+ ]);
2332
+ ancestors.delete(obj);
2333
+ return entries;
2334
+ }
2335
+ if (obj instanceof Set) {
2336
+ ancestors.add(obj);
2337
+ const values = Array.from(obj, (v) => convertValue(v, ancestors));
2338
+ ancestors.delete(obj);
2339
+ return values;
2340
+ }
2341
+ if (obj instanceof RegExp) return obj.toString();
2342
+ const toJSON = obj.toJSON;
2343
+ if (typeof toJSON === "function") {
2344
+ ancestors.add(obj);
2345
+ const result = convertValue(toJSON.call(obj), ancestors);
2346
+ ancestors.delete(obj);
2347
+ return result;
2348
+ }
2349
+ if (Array.isArray(obj)) {
2350
+ ancestors.add(obj);
2351
+ const arr = obj.map((item) => convertValue(item, ancestors));
2352
+ ancestors.delete(obj);
2353
+ return arr;
2354
+ }
2355
+ if (isPlainObject(obj)) {
2356
+ ancestors.add(obj);
2357
+ const out = {};
2358
+ for (const [key, val] of Object.entries(obj)) {
2359
+ out[key] = convertValue(val, ancestors);
2360
+ }
2361
+ ancestors.delete(obj);
2362
+ return out;
2363
+ }
2364
+ return obj;
2365
+ }
2366
+
2255
2367
  // src/logger/formatEntry.ts
2256
2368
  function serializeFieldValue(value) {
2257
2369
  if (value instanceof Error) {
@@ -2267,11 +2379,7 @@ function formatEntry(entry) {
2267
2379
  const plain = Object.fromEntries(
2268
2380
  Object.entries(entry.fields).map(([k, v]) => [k, serializeFieldValue(v)])
2269
2381
  );
2270
- try {
2271
- line += ` ${JSON.stringify(plain)}`;
2272
- } catch (err) {
2273
- line += ` [unserializable fields: ${err instanceof Error ? err.message : String(err)}]`;
2274
- }
2382
+ line += ` ${stringifyJson(plain)}`;
2275
2383
  }
2276
2384
  return line;
2277
2385
  }
@@ -2289,7 +2397,14 @@ function createFileLogSink(options) {
2289
2397
  };
2290
2398
  const openStream = (name) => {
2291
2399
  const stream = fs2.createWriteStream(path3.join(options.dir, `${name}.log`), { flags: "a" });
2292
- stream.on("error", () => {
2400
+ let streamErrorLogged = false;
2401
+ stream.on("error", (err) => {
2402
+ if (streamErrorLogged) return;
2403
+ streamErrorLogged = true;
2404
+ console.error(
2405
+ `[faapi] log file stream "${name}.log" failed \u2014 file logging on this stream stopped (subsequent errors suppressed). Check disk space / permissions for ${options.dir}:`,
2406
+ err
2407
+ );
2293
2408
  });
2294
2409
  streams.set(name, stream);
2295
2410
  };
@@ -2521,13 +2636,14 @@ function createTaskQueue(deps) {
2521
2636
  const loadTaskModule = deps.loadTaskModule ?? (async (filePath) => await import(filePath));
2522
2637
  const loadPayloadSchema = deps.loadPayloadSchema ?? (async (filePath) => {
2523
2638
  const zodPath = path4.join(path4.dirname(filePath), "zod.js");
2524
- try {
2525
- const mod = await import(zodPath);
2526
- const schemaKey = Object.keys(mod).find((k) => k.endsWith("Schema"));
2527
- return schemaKey ? mod[schemaKey] : void 0;
2528
- } catch {
2529
- return void 0;
2639
+ const mod = await import(zodPath);
2640
+ const schemaKey = Object.keys(mod).find((k) => k.endsWith("Schema"));
2641
+ if (!schemaKey) {
2642
+ throw new Error(
2643
+ `[faapi] Task payload schema artifact has no *Schema export: ${zodPath} \u2014 re-run "faapi dev" / "faapi build" to regenerate task artifacts.`
2644
+ );
2530
2645
  }
2646
+ return mod[schemaKey];
2531
2647
  });
2532
2648
  function assertKnownTask(name) {
2533
2649
  const meta = registry.get(name);
@@ -2546,14 +2662,13 @@ function createTaskQueue(deps) {
2546
2662
  async function validatePayload(name, payload) {
2547
2663
  if (!schemaCache.has(name)) {
2548
2664
  const meta = registry.get(name);
2549
- schemaCache.set(
2550
- name,
2551
- await loadPayloadSchema(path4.resolve(rootDir, meta.filePath)).catch(() => void 0)
2552
- );
2665
+ schemaCache.set(name, await loadPayloadSchema(path4.resolve(rootDir, meta.filePath)));
2553
2666
  }
2554
2667
  const schema = schemaCache.get(name);
2555
2668
  if (!schema || typeof schema.safeParse !== "function") {
2556
- return payload;
2669
+ throw new Error(
2670
+ `[faapi] Task "${name}" has no valid payload schema (zod.js missing or no *Schema export). Payload declarations are mandatory: re-run "faapi dev" / "faapi build" to regenerate task artifacts.`
2671
+ );
2557
2672
  }
2558
2673
  const result = schema.safeParse(payload);
2559
2674
  if (!result.success) {
@@ -2587,11 +2702,15 @@ function createTaskQueue(deps) {
2587
2702
  taskModulePath: path4.resolve(rootDir, meta.filePath),
2588
2703
  payload: job.payload,
2589
2704
  taskCtx: {
2590
- config: deps.config,
2705
+ // 隔离任务不传 config:worker 线程不接收进程配置(config 含函数字段
2706
+ // 不可结构化克隆,框架不做降级传递)——任务数据经 payload 显式传入
2591
2707
  resourcesDir: deps.resourcesDir,
2592
2708
  job: { id: job.id, name: job.name, attempt: job.attempt }
2593
2709
  },
2594
2710
  registries: snapshotRegistries(),
2711
+ // agent.llms 纯数据快照随 workerData 下发,worker 内动态加载 @faapi/agent
2712
+ // 重建轻量补全通道(taskCtx.llm)——llms 未配置时不传
2713
+ llms: deps.llms,
2595
2714
  // 日志配置随 workerData 下发(纯数据),worker 内联重建 taskCtx.log,
2596
2715
  // 条目回传宿主走统一管道(scope/fields 与进程内路径一致)
2597
2716
  log: {
@@ -2621,6 +2740,8 @@ function createTaskQueue(deps) {
2621
2740
  config: deps.config,
2622
2741
  job: { id: job.id, name: job.name, attempt: job.attempt },
2623
2742
  registries: registriesView,
2743
+ // 轻量补全通道惰性读取(插件晚于队列构造注册,执行时刻取值才可见)
2744
+ llm: deps.llm?.get(),
2624
2745
  log: createLogger(`task:${job.name}`, {
2625
2746
  fields: { jobId: job.id, task: job.name, attempt: job.attempt }
2626
2747
  }),
@@ -2649,7 +2770,8 @@ function createTaskQueue(deps) {
2649
2770
  cancelled,
2650
2771
  error: record.error
2651
2772
  })
2652
- ).catch(() => {
2773
+ ).catch((hookErr) => {
2774
+ console.error(`[faapi] task onFailed hook threw for "${job.name}":`, hookErr);
2653
2775
  });
2654
2776
  }
2655
2777
  throw err;
@@ -2872,25 +2994,27 @@ function detectIgnoredMetaFields(source, strictHit) {
2872
2994
  for (const line of source.split("\n")) {
2873
2995
  const trimmed = line.trim();
2874
2996
  if (trimmed.startsWith("//")) continue;
2997
+ if (trimmed.startsWith("*") || trimmed.startsWith("/*") || trimmed.endsWith("*/")) continue;
2875
2998
  for (const field of ALL_META_FIELDS) {
2876
2999
  if (new RegExp(`\\b${field}\\s*:`).test(trimmed)) declared.add(field);
2877
3000
  }
2878
3001
  }
2879
3002
  return ALL_META_FIELDS.filter((field) => declared.has(field) && !strictHit[field]);
2880
3003
  }
2881
- function warnIgnoredMetaFields(name, fields) {
2882
- for (const field of fields) {
3004
+ function throwIgnoredMetaFields(name, fields) {
3005
+ if (fields.length === 0) return;
3006
+ const details = fields.map((field) => {
2883
3007
  if (STRING_META_FIELDS.includes(field)) {
2884
- console.warn(
2885
- `[faapi] Task "${name}" declares "${field}" but its value is not a string literal \u2014 the declaration was ignored and the task will not be cron-scheduled. Meta values are extracted from source code without importing it, so only literal values are recognized: use a quoted literal (cron: '0 3 * * *'), not an expression or a dynamic value.`
2886
- );
2887
- } else {
2888
- const consequence = field === "timeoutMs" ? " and the task will run in-process with no timeout" : "";
2889
- console.warn(
2890
- `[faapi] Task "${name}" declares "${field}" but its value is not a numeric literal \u2014 the declaration was ignored${consequence}. Meta values are extracted from source code without importing it, so only literal values are recognized (underscores allowed, e.g. 1_800_000); expressions like 30 * 60_000 or dynamic values are not supported.`
2891
- );
3008
+ return `"${field}": value is not a string literal \u2014 the declaration would be ignored and the task would not be cron-scheduled. Use a quoted literal (cron: '0 3 * * *'), not an expression or a dynamic value.`;
2892
3009
  }
2893
- }
3010
+ const consequence = field === "timeoutMs" ? " \u2014 the declaration would be ignored and the task would silently run in-process with no timeout" : " \u2014 the declaration would be ignored";
3011
+ return `"${field}": value is not a numeric literal${consequence}. Only literal values are recognized (underscores allowed, e.g. 1_800_000); expressions like 30 * 60_000 or dynamic values are not supported.`;
3012
+ });
3013
+ throw new Error(
3014
+ `[faapi] Task "${name}" declares meta field(s) with non-literal values:
3015
+ ${details.join("\n ")}
3016
+ Meta values are extracted from source code without importing it, so only literals are recognized. If a matched line is inside a block comment (e.g. a single-line /* ... */ comment containing a meta-like declaration), rewrite the comment so it does not look like a meta declaration.`
3017
+ );
2894
3018
  }
2895
3019
  function parseNumericLiteral(raw) {
2896
3020
  return Number(raw.replace(/_/g, ""));
@@ -2960,7 +3084,7 @@ async function scanTasks(rootDir, patterns) {
2960
3084
  timeoutMs: timeoutMs !== void 0,
2961
3085
  graceMs: graceMs !== void 0
2962
3086
  };
2963
- warnIgnoredMetaFields(name, detectIgnoredMetaFields(source, strictHit));
3087
+ throwIgnoredMetaFields(name, detectIgnoredMetaFields(source, strictHit));
2964
3088
  tasks.push(manifest);
2965
3089
  }
2966
3090
  return tasks.sort((a, b) => a.name.localeCompare(b.name));
@@ -3560,11 +3684,7 @@ async function ensureMiddlewaresCompiled(middlewarePaths, rootDir) {
3560
3684
  if (!dist) return;
3561
3685
  for (const mwPath of middlewarePaths) {
3562
3686
  const sourcePath = prodPathToSourcePath(mwPath, rootDir, dist);
3563
- try {
3564
- await ensureCompiled(sourcePath, rootDir, dist);
3565
- } catch (err) {
3566
- console.error(`[faapi] Failed to compile middleware source ${sourcePath}:`, err);
3567
- }
3687
+ await ensureCompiled(sourcePath, rootDir, dist);
3568
3688
  }
3569
3689
  }
3570
3690
  function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
@@ -4568,116 +4688,6 @@ function hasBufferedBody(response) {
4568
4688
  return bufferedBodyResponses.has(response);
4569
4689
  }
4570
4690
 
4571
- // src/utils/isPlainObject.ts
4572
- function isPlainObject(value) {
4573
- if (value === null || typeof value !== "object") {
4574
- return false;
4575
- }
4576
- if (Array.isArray(value)) {
4577
- return false;
4578
- }
4579
- const proto = Object.getPrototypeOf(value);
4580
- return proto === null || proto === Object.prototype;
4581
- }
4582
-
4583
- // src/utils/stringifyJson.ts
4584
- function stringifyJson(value) {
4585
- if (!needsConversion(value, /* @__PURE__ */ new Set())) {
4586
- return JSON.stringify(value);
4587
- }
4588
- return JSON.stringify(convertValue(value, /* @__PURE__ */ new Set()));
4589
- }
4590
- function needsConversion(value, ancestors) {
4591
- if (value === null) return false;
4592
- const type = typeof value;
4593
- if (type === "bigint") return true;
4594
- if (type === "number") return !Number.isFinite(value);
4595
- if (type !== "object") return false;
4596
- const obj = value;
4597
- if (ancestors.has(obj)) {
4598
- throw new TypeError("[faapi] Converting circular structure to JSON");
4599
- }
4600
- if (obj instanceof Date || obj instanceof Map || obj instanceof Set || obj instanceof RegExp) {
4601
- return true;
4602
- }
4603
- if (typeof obj.toJSON === "function") return true;
4604
- if (Array.isArray(obj)) {
4605
- ancestors.add(obj);
4606
- for (let i = 0; i < obj.length; i++) {
4607
- if (needsConversion(obj[i], ancestors)) {
4608
- ancestors.delete(obj);
4609
- return true;
4610
- }
4611
- }
4612
- ancestors.delete(obj);
4613
- return false;
4614
- }
4615
- if (isPlainObject(obj)) {
4616
- ancestors.add(obj);
4617
- for (const key in obj) {
4618
- if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
4619
- if (needsConversion(obj[key], ancestors)) {
4620
- ancestors.delete(obj);
4621
- return true;
4622
- }
4623
- }
4624
- ancestors.delete(obj);
4625
- return false;
4626
- }
4627
- return false;
4628
- }
4629
- function convertValue(value, ancestors) {
4630
- if (value === null) return value;
4631
- const type = typeof value;
4632
- if (type === "bigint") return value.toString();
4633
- if (type === "number") return Number.isFinite(value) ? value : String(value);
4634
- if (type !== "object") return value;
4635
- const obj = value;
4636
- if (ancestors.has(obj)) {
4637
- throw new TypeError("[faapi] Converting circular structure to JSON");
4638
- }
4639
- if (obj instanceof Date) return obj.getTime();
4640
- if (obj instanceof Map) {
4641
- ancestors.add(obj);
4642
- const entries = Array.from(obj, ([k, v]) => [
4643
- convertValue(k, ancestors),
4644
- convertValue(v, ancestors)
4645
- ]);
4646
- ancestors.delete(obj);
4647
- return entries;
4648
- }
4649
- if (obj instanceof Set) {
4650
- ancestors.add(obj);
4651
- const values = Array.from(obj, (v) => convertValue(v, ancestors));
4652
- ancestors.delete(obj);
4653
- return values;
4654
- }
4655
- if (obj instanceof RegExp) return obj.toString();
4656
- const toJSON = obj.toJSON;
4657
- if (typeof toJSON === "function") {
4658
- ancestors.add(obj);
4659
- const result = convertValue(toJSON.call(obj), ancestors);
4660
- ancestors.delete(obj);
4661
- return result;
4662
- }
4663
- if (Array.isArray(obj)) {
4664
- ancestors.add(obj);
4665
- const arr = obj.map((item) => convertValue(item, ancestors));
4666
- ancestors.delete(obj);
4667
- return arr;
4668
- }
4669
- if (isPlainObject(obj)) {
4670
- ancestors.add(obj);
4671
- const out = {};
4672
- for (const [key, val] of Object.entries(obj)) {
4673
- out[key] = convertValue(val, ancestors);
4674
- }
4675
- ancestors.delete(obj);
4676
- return out;
4677
- }
4678
- return obj;
4679
- }
4680
-
4681
4691
  // src/runtime/sse.ts
4682
4692
  function encodeSseEvent(event) {
4683
4693
  let out = "";
@@ -5375,6 +5385,10 @@ function getBuiltinInjectionValue(type, ctx, body) {
5375
5385
  // 任务子系统:注入 TaskClient(入队/查询);未注册工厂(无 app 编排)时 undefined
5376
5386
  case "tasks":
5377
5387
  return ctx.registries ? ctx.registries.taskHandle.get(ctx) : void 0;
5388
+ // 轻量 LLM 补全通道:@faapi/agent 插件注册到 registries.llm;与请求上下文无关,
5389
+ // get 无参。插件未加载(或编程式直调 ctx 无 registries)时 undefined
5390
+ case "llm":
5391
+ return ctx.registries ? ctx.registries.llm.get() : void 0;
5378
5392
  // 请求级日志器:与 ctx.log 同一实例(scope http,自动带 requestId/method/path 字段)
5379
5393
  case "log":
5380
5394
  return ctx.log;
@@ -5871,38 +5885,30 @@ function setCachedMiddlewares(absPath, bundle) {
5871
5885
  middlewareCache.set(absPath, bundle);
5872
5886
  }
5873
5887
  async function loadMiddlewaresFile(filePath) {
5874
- let module;
5875
- try {
5876
- module = await importWithCacheBust(filePath);
5877
- } catch (err) {
5878
- console.error(
5879
- `[faapi] Failed to load middlewares from ${filePath}:`,
5880
- err instanceof Error ? err.stack ?? err.message : err
5881
- );
5882
- return { middlewares: [], injectors: {} };
5883
- }
5888
+ const module = await importWithCacheBust(filePath);
5884
5889
  const middlewares = module.default ?? module.middlewares ?? [];
5885
5890
  if (!Array.isArray(middlewares)) {
5886
- console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
5887
- return { middlewares: [], injectors: {} };
5891
+ throw new TypeError(
5892
+ `[faapi] middlewares.ts must export an array (default or named "middlewares"): ${filePath}`
5893
+ );
5888
5894
  }
5889
- const validMiddlewares = middlewares.filter((m) => {
5895
+ const validMiddlewares = [];
5896
+ for (const m of middlewares) {
5890
5897
  if (typeof m !== "function") {
5891
- console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
5892
- return false;
5898
+ throw new TypeError(
5899
+ `[faapi] every middleware must be a function, got ${typeof m}: ${filePath}`
5900
+ );
5893
5901
  }
5894
- return true;
5895
- });
5902
+ validMiddlewares.push(m);
5903
+ }
5896
5904
  const injectors = module.injectors ?? {};
5897
5905
  if (typeof injectors !== "object" || injectors === null) {
5898
- console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
5899
- return { middlewares: validMiddlewares, injectors: {} };
5906
+ throw new TypeError(`[faapi] "injectors" export must be an object: ${filePath}`);
5900
5907
  }
5901
5908
  const validInjectors = {};
5902
5909
  for (const [name, injector] of Object.entries(injectors)) {
5903
5910
  if (typeof injector !== "function") {
5904
- console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
5905
- continue;
5911
+ throw new TypeError(`[faapi] injector "${name}" must be a function: ${filePath}`);
5906
5912
  }
5907
5913
  validInjectors[name] = injector;
5908
5914
  }
@@ -5917,10 +5923,16 @@ async function loadMergedMiddlewares(middlewarePaths) {
5917
5923
  if (bundle === void 0) {
5918
5924
  let loading = inFlight.get(absMwPath);
5919
5925
  if (!loading) {
5920
- loading = loadMiddlewaresFile(absMwPath).then((result) => {
5921
- setCachedMiddlewares(absMwPath, result);
5922
- return result;
5923
- });
5926
+ loading = loadMiddlewaresFile(absMwPath).then(
5927
+ (result) => {
5928
+ setCachedMiddlewares(absMwPath, result);
5929
+ return result;
5930
+ },
5931
+ (err) => {
5932
+ inFlight.delete(absMwPath);
5933
+ throw err;
5934
+ }
5935
+ );
5924
5936
  inFlight.set(absMwPath, loading);
5925
5937
  }
5926
5938
  bundle = await loading;
@@ -6515,7 +6527,8 @@ async function sendErrorResponse(err, meta, res, onError, ctx) {
6515
6527
  if (onError && ctx) {
6516
6528
  try {
6517
6529
  await onError(err, ctx);
6518
- } catch {
6530
+ } catch (hookErr) {
6531
+ console.error("[faapi] lifecycle onError hook threw:", hookErr);
6519
6532
  }
6520
6533
  }
6521
6534
  }
@@ -6708,7 +6721,7 @@ async function loadPlugins(declarations, ctx, rootDir, dist) {
6708
6721
  const upgradeWrappers = [];
6709
6722
  const failures = [];
6710
6723
  if (!declarations || declarations.length === 0) {
6711
- return { handlerWrappers, upgradeWrappers, failures };
6724
+ return { handlerWrappers, upgradeWrappers };
6712
6725
  }
6713
6726
  const fullCtx = {
6714
6727
  ...ctx,
@@ -6735,7 +6748,7 @@ async function loadPlugins(declarations, ctx, rootDir, dist) {
6735
6748
  }
6736
6749
  if (enable === false) continue;
6737
6750
  if (loaded.has(specifier)) {
6738
- console.warn(`! Plugin already loaded: ${specifier}, skipping`);
6751
+ failures.push({ specifier, reason: "duplicate plugin declaration" });
6739
6752
  continue;
6740
6753
  }
6741
6754
  loaded.add(specifier);
@@ -6756,12 +6769,12 @@ async function loadPlugins(declarations, ctx, rootDir, dist) {
6756
6769
  }
6757
6770
  }
6758
6771
  if (failures.length > 0) {
6759
- console.error(
6772
+ throw new Error(
6760
6773
  `[faapi] ${failures.length} plugin(s) failed to load:
6761
6774
  ` + failures.map((f) => ` - ${f.specifier}: ${f.reason}`).join("\n")
6762
6775
  );
6763
6776
  }
6764
- return { handlerWrappers, upgradeWrappers, failures };
6777
+ return { handlerWrappers, upgradeWrappers };
6765
6778
  }
6766
6779
  var TS_EXTS = /\.(ts|tsx|mts|cts)$/;
6767
6780
  var JS_EXTS = /\.(js|mjs|cjs|jsx)$/;
@@ -7362,6 +7375,7 @@ function generateTaskSchemaFileSource(sources, resolveType, helpersImportPath) {
7362
7375
  }
7363
7376
  async function generateTaskArtifacts(manifests, rootDir, dist) {
7364
7377
  const metadata = [];
7378
+ const declarationErrors = [];
7365
7379
  if (manifests.length > 0) {
7366
7380
  const programByFile = createPrograms(manifests.map((m) => path25.resolve(rootDir, m.filePath)));
7367
7381
  for (const manifest of manifests) {
@@ -7370,11 +7384,28 @@ async function generateTaskArtifacts(manifests, rootDir, dist) {
7370
7384
  name: manifest.name,
7371
7385
  filePath: manifest.filePath
7372
7386
  });
7373
- if (result) {
7374
- metadata.push({ ...result, taskName: manifest.name });
7387
+ if (!result) {
7388
+ declarationErrors.push(
7389
+ `"${manifest.name}" (${manifest.filePath}): no exported run function found`
7390
+ );
7391
+ continue;
7375
7392
  }
7393
+ if (!result.inputTypeName) {
7394
+ declarationErrors.push(
7395
+ `"${manifest.name}" (${manifest.filePath}): run's first parameter has no type name \u2014 declare a Payload type (an empty interface for parameterless tasks), or explicitly opt out of validation with "type Payload = unknown"`
7396
+ );
7397
+ continue;
7398
+ }
7399
+ metadata.push({ ...result, taskName: manifest.name });
7376
7400
  }
7377
7401
  }
7402
+ if (declarationErrors.length > 0) {
7403
+ throw new Error(
7404
+ `[faapi] ${declarationErrors.length} task(s) missing a Payload type declaration \u2014 payload validation must not be silently skipped:
7405
+ ${declarationErrors.join("\n ")}
7406
+ Every task must declare a payload type on run's first parameter; "type Payload = unknown" generates an always-passing schema (explicit no-validation opt-out).`
7407
+ );
7408
+ }
7378
7409
  const serialized = serializeTasks(manifests, dist);
7379
7410
  const tasksPath = path25.resolve(rootDir, dist, TASKS_FILE);
7380
7411
  await writeTasksModule(serialized, tasksPath);
@@ -7652,9 +7683,13 @@ async function createAppBase(options) {
7652
7683
  const taskQueue = createTaskQueue({
7653
7684
  registry: registries.task,
7654
7685
  rootDir,
7655
- config,
7686
+ config: config ?? {},
7656
7687
  resourcesDir,
7657
7688
  registries: createTaskRegistriesView(registries),
7689
+ // 轻量补全通道:进程内路径经 store 惰性读取(插件注册后的活引用);
7690
+ // 隔离路径传 agent.llms 纯数据快照(worker 内重建)
7691
+ llm: registries.llm,
7692
+ llms: config?.agent?.llms,
7658
7693
  driver: taskDriver,
7659
7694
  onFailed: config?.task?.onFailed
7660
7695
  });
@@ -7679,7 +7714,7 @@ async function createAppBase(options) {
7679
7714
  dist,
7680
7715
  cors: config?.cors ?? true,
7681
7716
  onError: config?.lifecycle?.onError,
7682
- config: config ?? void 0,
7717
+ config,
7683
7718
  wsRoutes,
7684
7719
  middlewares: config?.middlewares,
7685
7720
  injectors: config?.injectors,
@@ -7792,6 +7827,7 @@ async function createAppBase(options) {
7792
7827
  registries.task.clear();
7793
7828
  registries.agentHandle.clear();
7794
7829
  registries.taskHandle.clear();
7830
+ registries.llm.clear();
7795
7831
  setActiveResourcesDir(null);
7796
7832
  if (!server.listening) {
7797
7833
  app.server = null;