@faapi/faapi 6.2.0 → 6.3.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/cli/index.js CHANGED
@@ -649,9 +649,9 @@ var init_constants = __esm({
649
649
  });
650
650
 
651
651
  // src/utils/normalizePath.ts
652
- function normalizePath(path27) {
653
- if (!path27) return "";
654
- let result = path27.replace(/\\/g, "/");
652
+ function normalizePath(path31) {
653
+ if (!path31) return "";
654
+ let result = path31.replace(/\\/g, "/");
655
655
  result = result.replace(/\/+/g, "/");
656
656
  result = result.replace(/\/+$/, "");
657
657
  if (result && !result.startsWith("/")) {
@@ -2258,8 +2258,10 @@ var init_resolveInjection = __esm({
2258
2258
  fields: "fields",
2259
2259
  agent: "agent",
2260
2260
  // Phase 2.3
2261
- agents: "agents"
2261
+ agents: "agents",
2262
2262
  // Phase 2.3
2263
+ tasks: "tasks"
2264
+ // 任务子系统:TaskClient(入队/查询)
2263
2265
  };
2264
2266
  injectionCache = /* @__PURE__ */ new WeakMap();
2265
2267
  }
@@ -3370,16 +3372,498 @@ var init_generateAgentArtifacts = __esm({
3370
3372
  }
3371
3373
  });
3372
3374
 
3375
+ // src/task/scanTasks.ts
3376
+ import fg4 from "fast-glob";
3377
+ import path16 from "path";
3378
+ import fs12 from "fs";
3379
+ function filePathToTaskName(filePath) {
3380
+ const normalized = filePath.replace(/\\/g, "/");
3381
+ const match = normalized.match(/(?:^|\/)tasks\/(.+)\/task\.ts$/);
3382
+ if (!match) {
3383
+ throw new Error(`[faapi] Invalid task file path: ${filePath}`);
3384
+ }
3385
+ return match[1].split("/").join(".");
3386
+ }
3387
+ async function scanTasks(rootDir, patterns) {
3388
+ const files = await fg4(patterns, {
3389
+ cwd: rootDir,
3390
+ onlyFiles: true,
3391
+ absolute: false
3392
+ });
3393
+ const tasks = [];
3394
+ const seen = /* @__PURE__ */ new Map();
3395
+ for (const file of files) {
3396
+ const normalizedFile = file.replace(/\\/g, "/");
3397
+ const fileName = path16.posix.basename(normalizedFile);
3398
+ if (fileName !== TASK_FILENAME) {
3399
+ continue;
3400
+ }
3401
+ const absPath = path16.resolve(rootDir, normalizedFile);
3402
+ const source = await fs12.promises.readFile(absPath, "utf8").catch(() => "");
3403
+ const name = filePathToTaskName(normalizedFile);
3404
+ const prevFile = seen.get(name);
3405
+ if (prevFile) {
3406
+ throw new Error(
3407
+ `Task conflict: "${name}" declared in both ${prevFile} and ${normalizedFile}`
3408
+ );
3409
+ }
3410
+ seen.set(name, normalizedFile);
3411
+ const manifest = { name, filePath: normalizedFile };
3412
+ const cron = CRON_RE.exec(source)?.[1];
3413
+ const concurrency = CONCURRENCY_RE.exec(source)?.[1];
3414
+ const retries = RETRIES_RE.exec(source)?.[1];
3415
+ if (cron !== void 0) manifest.cron = cron;
3416
+ if (concurrency !== void 0) manifest.concurrency = Number(concurrency);
3417
+ if (retries !== void 0) manifest.retries = Number(retries);
3418
+ tasks.push(manifest);
3419
+ }
3420
+ return tasks;
3421
+ }
3422
+ var TASK_PATTERNS, TASK_FILENAME, CRON_RE, CONCURRENCY_RE, RETRIES_RE;
3423
+ var init_scanTasks = __esm({
3424
+ "src/task/scanTasks.ts"() {
3425
+ "use strict";
3426
+ TASK_PATTERNS = ["src/tasks/**/task.ts"];
3427
+ TASK_FILENAME = "task.ts";
3428
+ CRON_RE = /(?:^|\n)\s*cron:\s*['"`]([^'"`\n]+)['"`]/;
3429
+ CONCURRENCY_RE = /(?:^|\n)\s*concurrency:\s*(\d+)/;
3430
+ RETRIES_RE = /(?:^|\n)\s*retries:\s*(\d+)/;
3431
+ }
3432
+ });
3433
+
3434
+ // src/cli/generateTaskArtifacts.ts
3435
+ import path17 from "path";
3436
+ function serializeTasks(tasks, dist = "dist") {
3437
+ return tasks.map((t) => ({
3438
+ name: t.name,
3439
+ filePath: toProdFilePath(t.filePath, dist),
3440
+ ...t.cron !== void 0 ? { cron: t.cron } : {},
3441
+ ...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
3442
+ ...t.retries !== void 0 ? { retries: t.retries } : {}
3443
+ }));
3444
+ }
3445
+ async function writeTasksModule(manifest, outputPath) {
3446
+ const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
3447
+ export const tasks = ${JSON.stringify(manifest, null, 2)};
3448
+ `;
3449
+ await atomicWriteFile(outputPath, content);
3450
+ }
3451
+ function hydrateTasks(manifest) {
3452
+ return manifest.map((t) => ({
3453
+ name: t.name,
3454
+ filePath: t.filePath,
3455
+ cron: t.cron ?? void 0,
3456
+ concurrency: t.concurrency ?? void 0,
3457
+ retries: t.retries ?? void 0
3458
+ }));
3459
+ }
3460
+ function collectTaskSchemaSources(tasks, rootDir) {
3461
+ const tasksByFile = /* @__PURE__ */ new Map();
3462
+ for (const task of tasks) {
3463
+ if (!task.inputTypeName) continue;
3464
+ const absPath = path17.resolve(rootDir, task.filePath);
3465
+ let list = tasksByFile.get(absPath);
3466
+ if (!list) {
3467
+ list = [];
3468
+ tasksByFile.set(absPath, list);
3469
+ }
3470
+ list.push(task);
3471
+ }
3472
+ const programByFile = createPrograms([...tasksByFile.keys()]);
3473
+ const resolversByFile = /* @__PURE__ */ new Map();
3474
+ for (const filePath of tasksByFile.keys()) {
3475
+ resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath), filePath));
3476
+ }
3477
+ const sources = [];
3478
+ for (const [filePath, fileTasks] of tasksByFile) {
3479
+ const program = programByFile.get(filePath);
3480
+ for (const task of fileTasks) {
3481
+ const typeInfo = extractTypeInfo(program, filePath, task.inputTypeName);
3482
+ sources.push({
3483
+ name: task.taskName,
3484
+ filePath,
3485
+ schemaName: task.inputTypeName,
3486
+ typeInfo
3487
+ });
3488
+ }
3489
+ }
3490
+ return { sources, resolversByFile };
3491
+ }
3492
+ function generateTaskSchemaFileSource(sources, resolveType, helpersImportPath) {
3493
+ const lines = ["import { z } from 'zod';"];
3494
+ const schemaBlocks = [];
3495
+ for (const source of sources) {
3496
+ if (!source.typeInfo) continue;
3497
+ const block = [`// task ${source.name} \u2192 ${source.schemaName}`];
3498
+ const schemaCode = generateZodSchemaSource(
3499
+ source.typeInfo,
3500
+ resolveType,
3501
+ source.schemaName,
3502
+ false
3503
+ ).replace(/^import \{ z \} from 'zod';\s*\n\s*\n/, "");
3504
+ block.push(schemaCode);
3505
+ block.push("");
3506
+ schemaBlocks.push(block.join("\n"));
3507
+ }
3508
+ const allSchemaCode = schemaBlocks.join("\n");
3509
+ if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
3510
+ lines.push(
3511
+ `import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
3512
+ );
3513
+ }
3514
+ lines.push("");
3515
+ lines.push(...schemaBlocks);
3516
+ return lines.join("\n").replace(/\n+$/, "\n");
3517
+ }
3518
+ async function generateTaskArtifacts(manifests, rootDir, dist) {
3519
+ const metadata = [];
3520
+ if (manifests.length > 0) {
3521
+ const programByFile = createPrograms(manifests.map((m) => path17.resolve(rootDir, m.filePath)));
3522
+ for (const manifest of manifests) {
3523
+ const absPath = path17.resolve(rootDir, manifest.filePath);
3524
+ const result = extractToolMetadata(programByFile.get(absPath), absPath, "run", {
3525
+ name: manifest.name,
3526
+ filePath: manifest.filePath
3527
+ });
3528
+ if (result) {
3529
+ metadata.push({ ...result, taskName: manifest.name });
3530
+ }
3531
+ }
3532
+ }
3533
+ const serialized = serializeTasks(manifests, dist);
3534
+ const tasksPath = path17.resolve(rootDir, dist, TASKS_FILE);
3535
+ await writeTasksModule(serialized, tasksPath);
3536
+ if (metadata.length === 0) {
3537
+ return hydrateTasks(serialized);
3538
+ }
3539
+ const { sources, resolversByFile } = collectTaskSchemaSources(metadata, rootDir);
3540
+ if (sources.length === 0) {
3541
+ return hydrateTasks(serialized);
3542
+ }
3543
+ const sourcesByFile = /* @__PURE__ */ new Map();
3544
+ for (const source of sources) {
3545
+ let list = sourcesByFile.get(source.filePath);
3546
+ if (!list) {
3547
+ list = [];
3548
+ sourcesByFile.set(source.filePath, list);
3549
+ }
3550
+ list.push(source);
3551
+ }
3552
+ const fileEntries = [];
3553
+ for (const [filePath, fileSources] of sourcesByFile) {
3554
+ const relFile = path17.relative(rootDir, filePath).replace(/\\/g, "/");
3555
+ const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
3556
+ const resolver = resolversByFile.get(filePath);
3557
+ let relForDir = relFile;
3558
+ if (relForDir.startsWith("src/")) {
3559
+ relForDir = relForDir.slice(4);
3560
+ }
3561
+ const dirIdx = relForDir.lastIndexOf("/");
3562
+ const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
3563
+ const helpersImportPath = getHelpersImportPath(zodRelDir);
3564
+ const source = generateTaskSchemaFileSource(
3565
+ fileSources,
3566
+ (name) => resolver?.resolve(name)?.runtimeType,
3567
+ helpersImportPath
3568
+ );
3569
+ fileEntries.push({ outputPath, source });
3570
+ }
3571
+ const allSourceCode = fileEntries.map((e) => e.source).join("\n");
3572
+ if (usesCoerceHelpers(allSourceCode)) {
3573
+ const helpersPath = path17.resolve(rootDir, dist, HELPERS_FILENAME);
3574
+ const { existsSync: existsSync2 } = await import("fs");
3575
+ if (!existsSync2(helpersPath)) {
3576
+ await atomicWriteFile(helpersPath, generateHelpersFileSource());
3577
+ }
3578
+ }
3579
+ await Promise.all(
3580
+ fileEntries.map(({ outputPath, source }) => atomicWriteFile(outputPath, source))
3581
+ );
3582
+ return hydrateTasks(serialized);
3583
+ }
3584
+ var TASKS_FILE;
3585
+ var init_generateTaskArtifacts = __esm({
3586
+ "src/cli/generateTaskArtifacts.ts"() {
3587
+ "use strict";
3588
+ init_extractToolMetadata();
3589
+ init_createProgram();
3590
+ init_prodPaths();
3591
+ init_atomicWrite();
3592
+ init_generateSchemaFiles();
3593
+ init_extractHandlerTypes();
3594
+ init_generateZodSchema();
3595
+ TASKS_FILE = "faapi-tasks.js";
3596
+ }
3597
+ });
3598
+
3599
+ // src/cli/compileSourceFiles.ts
3600
+ import path18 from "path";
3601
+ import fs13 from "fs";
3602
+ import fg5 from "fast-glob";
3603
+ async function compileSourceFiles(options) {
3604
+ const { rootDir, dist, files, logLevel = "silent", production, atomicWrite } = options;
3605
+ const entryPoints = files ?? await fg5([`${APP_DIR}/**/*.ts`], {
3606
+ cwd: rootDir,
3607
+ onlyFiles: true,
3608
+ absolute: true,
3609
+ ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
3610
+ });
3611
+ if (entryPoints.length === 0) {
3612
+ return { compiledFiles: [] };
3613
+ }
3614
+ const absDist = path18.resolve(rootDir, dist);
3615
+ await fs13.promises.mkdir(absDist, { recursive: true });
3616
+ const plugins = buildAliasPlugins(rootDir);
3617
+ const esbuild = await import("esbuild");
3618
+ const outbase = path18.resolve(rootDir, APP_DIR);
3619
+ const result = await esbuild.build({
3620
+ entryPoints,
3621
+ outdir: absDist,
3622
+ outbase,
3623
+ bundle: false,
3624
+ platform: "node",
3625
+ format: "esm",
3626
+ sourcemap: true,
3627
+ packages: "external",
3628
+ plugins,
3629
+ // build 语义:编译期 NODE_ENV 替换 + 死分支删除(见 AGENTS.md §5.3)
3630
+ ...production ? { define: { "process.env.NODE_ENV": '"production"' }, minifySyntax: true } : {},
3631
+ logLevel,
3632
+ // dev 语义:esbuild 返回内存内容,由下方原子写落盘
3633
+ ...atomicWrite ? { write: false } : {}
3634
+ });
3635
+ if (atomicWrite && result.outputFiles) {
3636
+ await Promise.all(
3637
+ result.outputFiles.map(async (file) => {
3638
+ await fs13.promises.mkdir(path18.dirname(file.path), { recursive: true });
3639
+ const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
3640
+ await fs13.promises.writeFile(tmp, file.contents);
3641
+ await fs13.promises.rename(tmp, file.path);
3642
+ })
3643
+ );
3644
+ }
3645
+ return { compiledFiles: entryPoints };
3646
+ }
3647
+ var init_compileSourceFiles = __esm({
3648
+ "src/cli/compileSourceFiles.ts"() {
3649
+ "use strict";
3650
+ init_aliasPlugin();
3651
+ init_prodPaths();
3652
+ }
3653
+ });
3654
+
3655
+ // src/cli/compileDevRoutes.ts
3656
+ async function compileDevRoutes(options) {
3657
+ return compileSourceFiles({ ...options, atomicWrite: true });
3658
+ }
3659
+ var init_compileDevRoutes = __esm({
3660
+ "src/cli/compileDevRoutes.ts"() {
3661
+ "use strict";
3662
+ init_compileSourceFiles();
3663
+ }
3664
+ });
3665
+
3666
+ // src/cli/compileOnDemand.ts
3667
+ import path19 from "path";
3668
+ import fs14 from "fs";
3669
+ function isProductFresh(sourceAbsPath, productAbsPath) {
3670
+ try {
3671
+ const srcStat = fs14.statSync(sourceAbsPath);
3672
+ const prodStat = fs14.statSync(productAbsPath);
3673
+ return prodStat.mtimeMs >= srcStat.mtimeMs;
3674
+ } catch {
3675
+ return false;
3676
+ }
3677
+ }
3678
+ function createDevOnDemandState() {
3679
+ return {
3680
+ enabled: false,
3681
+ distDir: void 0,
3682
+ compiledFiles: /* @__PURE__ */ new Set(),
3683
+ generatedSchemas: /* @__PURE__ */ new Set(),
3684
+ inFlightCompilations: /* @__PURE__ */ new Map(),
3685
+ inFlightSchemaGenerations: /* @__PURE__ */ new Map()
3686
+ };
3687
+ }
3688
+ function clearCompiledFiles() {
3689
+ state.compiledFiles.clear();
3690
+ state.inFlightCompilations.clear();
3691
+ sourcePathCache.clear();
3692
+ }
3693
+ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
3694
+ const inFlight2 = state.inFlightCompilations.get(sourceAbsPath);
3695
+ if (inFlight2) {
3696
+ await inFlight2.catch(() => {
3697
+ });
3698
+ return false;
3699
+ }
3700
+ if (state.compiledFiles.has(sourceAbsPath)) {
3701
+ return false;
3702
+ }
3703
+ if (!fs14.existsSync(sourceAbsPath)) {
3704
+ return false;
3705
+ }
3706
+ const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
3707
+ if (productPath && isProductFresh(sourceAbsPath, productPath)) {
3708
+ state.compiledFiles.add(sourceAbsPath);
3709
+ return false;
3710
+ }
3711
+ const compilePromise = (async () => {
3712
+ const { insideFiles } = await collectRelativeImports([sourceAbsPath], rootDir);
3713
+ const files = [sourceAbsPath];
3714
+ for (const dep of insideFiles) {
3715
+ if (state.compiledFiles.has(dep)) continue;
3716
+ const depProduct = prodSourcePathToProductPath(dep, rootDir, dist);
3717
+ if (depProduct && isProductFresh(dep, depProduct)) continue;
3718
+ files.push(dep);
3719
+ }
3720
+ await compileDevRoutes({
3721
+ rootDir,
3722
+ dist,
3723
+ files,
3724
+ logLevel: "silent"
3725
+ });
3726
+ for (const file of files) {
3727
+ state.compiledFiles.add(file);
3728
+ }
3729
+ state.compiledFiles.add(sourceAbsPath);
3730
+ })();
3731
+ state.inFlightCompilations.set(sourceAbsPath, compilePromise);
3732
+ try {
3733
+ await compilePromise;
3734
+ return true;
3735
+ } finally {
3736
+ state.inFlightCompilations.delete(sourceAbsPath);
3737
+ }
3738
+ }
3739
+ async function ensureMiddlewaresCompiled(middlewarePaths, rootDir) {
3740
+ if (!isDevOnDemandEnabled() || middlewarePaths.length === 0) return;
3741
+ const dist = getDevDist();
3742
+ if (!dist) return;
3743
+ for (const mwPath of middlewarePaths) {
3744
+ const sourcePath = prodPathToSourcePath(mwPath, rootDir, dist);
3745
+ try {
3746
+ await ensureCompiled(sourcePath, rootDir, dist);
3747
+ } catch (err) {
3748
+ console.error(`[faapi] Failed to compile middleware source ${sourcePath}:`, err);
3749
+ }
3750
+ }
3751
+ }
3752
+ function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
3753
+ const rel = path19.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
3754
+ if (!rel.startsWith("src/")) return null;
3755
+ const relWithoutSrc = rel.slice(4);
3756
+ const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
3757
+ return path19.resolve(rootDir, dist, jsRel);
3758
+ }
3759
+ function clearGeneratedSchemas() {
3760
+ state.generatedSchemas.clear();
3761
+ state.inFlightSchemaGenerations.clear();
3762
+ }
3763
+ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
3764
+ const inFlight2 = state.inFlightSchemaGenerations.get(schemaPath);
3765
+ if (inFlight2) {
3766
+ await inFlight2.catch(() => {
3767
+ });
3768
+ return false;
3769
+ }
3770
+ if (state.generatedSchemas.has(schemaPath)) {
3771
+ return false;
3772
+ }
3773
+ const prodAbsPath = path19.resolve(rootDir, routeFilePath);
3774
+ const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
3775
+ if (!fs14.existsSync(sourceAbsPath)) {
3776
+ return false;
3777
+ }
3778
+ if (isProductFresh(sourceAbsPath, schemaPath)) {
3779
+ state.generatedSchemas.add(schemaPath);
3780
+ return false;
3781
+ }
3782
+ const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
3783
+ if (fileRoutes.length === 0) {
3784
+ return false;
3785
+ }
3786
+ const sourceRelPath = path19.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
3787
+ const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
3788
+ const generatePromise = (async () => {
3789
+ await generateSchemaFiles(sourceRoutes, rootDir, dist);
3790
+ state.generatedSchemas.add(schemaPath);
3791
+ })();
3792
+ state.inFlightSchemaGenerations.set(schemaPath, generatePromise);
3793
+ try {
3794
+ await generatePromise;
3795
+ return true;
3796
+ } finally {
3797
+ state.inFlightSchemaGenerations.delete(schemaPath);
3798
+ }
3799
+ }
3800
+ async function deleteSchemaFiles(routes, rootDir, dist) {
3801
+ const deleted = /* @__PURE__ */ new Set();
3802
+ for (const route of routes) {
3803
+ const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
3804
+ if (deleted.has(schemaPath)) continue;
3805
+ deleted.add(schemaPath);
3806
+ try {
3807
+ await fs14.promises.unlink(schemaPath);
3808
+ } catch {
3809
+ }
3810
+ }
3811
+ }
3812
+ function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
3813
+ const cached = sourcePathCache.get(prodAbsPath);
3814
+ if (cached) return cached;
3815
+ const rel = path19.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
3816
+ let relWithoutDist = rel;
3817
+ if (relWithoutDist.startsWith(`${dist}/`)) {
3818
+ relWithoutDist = relWithoutDist.slice(dist.length + 1);
3819
+ }
3820
+ const srcRel = `src/${relWithoutDist}`;
3821
+ const tsRel = srcRel.replace(/\.js$/, ".ts");
3822
+ const tsAbs = path19.resolve(rootDir, tsRel);
3823
+ let result;
3824
+ if (fs14.existsSync(tsAbs)) {
3825
+ result = tsAbs;
3826
+ } else {
3827
+ result = path19.resolve(rootDir, srcRel);
3828
+ }
3829
+ sourcePathCache.set(prodAbsPath, result);
3830
+ return result;
3831
+ }
3832
+ function setDevOnDemandEnabled(enabled) {
3833
+ state.enabled = enabled;
3834
+ }
3835
+ function isDevOnDemandEnabled() {
3836
+ return state.enabled;
3837
+ }
3838
+ function setDevDist(dist) {
3839
+ state.distDir = dist;
3840
+ }
3841
+ function getDevDist() {
3842
+ return state.distDir;
3843
+ }
3844
+ var state, sourcePathCache;
3845
+ var init_compileOnDemand = __esm({
3846
+ "src/cli/compileOnDemand.ts"() {
3847
+ "use strict";
3848
+ init_compileDevRoutes();
3849
+ init_generateSchemaFiles();
3850
+ init_generateSchemaFiles();
3851
+ init_collectImports();
3852
+ state = createDevOnDemandState();
3853
+ sourcePathCache = /* @__PURE__ */ new Map();
3854
+ }
3855
+ });
3856
+
3373
3857
  // src/config/loadConfig.ts
3374
- import path16 from "path";
3375
- import fs12 from "fs";
3858
+ import path20 from "path";
3859
+ import fs15 from "fs";
3376
3860
  async function loadConfig(rootDir, dist) {
3377
- const configProductPath = path16.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
3378
- if (fs12.existsSync(configProductPath)) {
3861
+ const configProductPath = path20.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
3862
+ if (fs15.existsSync(configProductPath)) {
3379
3863
  const module = await importWithCacheBust(configProductPath);
3380
3864
  return module.default ?? {};
3381
3865
  }
3382
- const hasSourceConfig = fs12.existsSync(path16.join(rootDir, "faapi.config.ts")) || fs12.existsSync(path16.join(rootDir, "faapi.config.js"));
3866
+ const hasSourceConfig = fs15.existsSync(path20.join(rootDir, "faapi.config.ts")) || fs15.existsSync(path20.join(rootDir, "faapi.config.js"));
3383
3867
  if (hasSourceConfig) {
3384
3868
  throw new Error(
3385
3869
  `[faapi] ${dist}/${CONFIG_PRODUCT_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
@@ -3397,8 +3881,8 @@ var init_loadConfig = __esm({
3397
3881
  });
3398
3882
 
3399
3883
  // src/cli/loadEnv.ts
3400
- import fs13 from "fs";
3401
- import path17 from "path";
3884
+ import fs16 from "fs";
3885
+ import path21 from "path";
3402
3886
  function resolveEnv() {
3403
3887
  return process.env.NODE_ENV || "development";
3404
3888
  }
@@ -3465,9 +3949,9 @@ function loadEnv(rootDir) {
3465
3949
  const files = getEnvFiles(env);
3466
3950
  const merged = {};
3467
3951
  for (const file of files) {
3468
- const filePath = path17.join(rootDir, file);
3469
- if (!fs13.existsSync(filePath)) continue;
3470
- const content = fs13.readFileSync(filePath, "utf-8");
3952
+ const filePath = path21.join(rootDir, file);
3953
+ if (!fs16.existsSync(filePath)) continue;
3954
+ const content = fs16.readFileSync(filePath, "utf-8");
3471
3955
  const parsed = parseEnvFile(content, merged);
3472
3956
  Object.assign(merged, parsed);
3473
3957
  }
@@ -3573,7 +4057,7 @@ var init_esm = __esm({
3573
4057
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
3574
4058
  const statMethod = opts.lstat ? lstat : stat;
3575
4059
  if (wantBigintFsStats) {
3576
- this._stat = (path27) => statMethod(path27, { bigint: true });
4060
+ this._stat = (path31) => statMethod(path31, { bigint: true });
3577
4061
  } else {
3578
4062
  this._stat = statMethod;
3579
4063
  }
@@ -3598,8 +4082,8 @@ var init_esm = __esm({
3598
4082
  const par = this.parent;
3599
4083
  const fil = par && par.files;
3600
4084
  if (fil && fil.length > 0) {
3601
- const { path: path27, depth } = par;
3602
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path27));
4085
+ const { path: path31, depth } = par;
4086
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path31));
3603
4087
  const awaited = await Promise.all(slice);
3604
4088
  for (const entry of awaited) {
3605
4089
  if (!entry)
@@ -3639,20 +4123,20 @@ var init_esm = __esm({
3639
4123
  this.reading = false;
3640
4124
  }
3641
4125
  }
3642
- async _exploreDir(path27, depth) {
4126
+ async _exploreDir(path31, depth) {
3643
4127
  let files;
3644
4128
  try {
3645
- files = await readdir(path27, this._rdOptions);
4129
+ files = await readdir(path31, this._rdOptions);
3646
4130
  } catch (error) {
3647
4131
  this._onError(error);
3648
4132
  }
3649
- return { files, depth, path: path27 };
4133
+ return { files, depth, path: path31 };
3650
4134
  }
3651
- async _formatEntry(dirent, path27) {
4135
+ async _formatEntry(dirent, path31) {
3652
4136
  let entry;
3653
4137
  const basename3 = this._isDirent ? dirent.name : dirent;
3654
4138
  try {
3655
- const fullPath = presolve(pjoin(path27, basename3));
4139
+ const fullPath = presolve(pjoin(path31, basename3));
3656
4140
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
3657
4141
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
3658
4142
  } catch (err) {
@@ -3713,16 +4197,16 @@ import { watchFile, unwatchFile, watch as fs_watch } from "fs";
3713
4197
  import { open, stat as stat2, lstat as lstat2, realpath as fsrealpath } from "fs/promises";
3714
4198
  import * as sysPath from "path";
3715
4199
  import { type as osType } from "os";
3716
- function createFsWatchInstance(path27, options, listener, errHandler, emitRaw) {
4200
+ function createFsWatchInstance(path31, options, listener, errHandler, emitRaw) {
3717
4201
  const handleEvent = (rawEvent, evPath) => {
3718
- listener(path27);
3719
- emitRaw(rawEvent, evPath, { watchedPath: path27 });
3720
- if (evPath && path27 !== evPath) {
3721
- fsWatchBroadcast(sysPath.resolve(path27, evPath), KEY_LISTENERS, sysPath.join(path27, evPath));
4202
+ listener(path31);
4203
+ emitRaw(rawEvent, evPath, { watchedPath: path31 });
4204
+ if (evPath && path31 !== evPath) {
4205
+ fsWatchBroadcast(sysPath.resolve(path31, evPath), KEY_LISTENERS, sysPath.join(path31, evPath));
3722
4206
  }
3723
4207
  };
3724
4208
  try {
3725
- return fs_watch(path27, {
4209
+ return fs_watch(path31, {
3726
4210
  persistent: options.persistent
3727
4211
  }, handleEvent);
3728
4212
  } catch (error) {
@@ -4067,12 +4551,12 @@ var init_handler = __esm({
4067
4551
  listener(val1, val2, val3);
4068
4552
  });
4069
4553
  };
4070
- setFsWatchListener = (path27, fullPath, options, handlers) => {
4554
+ setFsWatchListener = (path31, fullPath, options, handlers) => {
4071
4555
  const { listener, errHandler, rawEmitter } = handlers;
4072
4556
  let cont = FsWatchInstances.get(fullPath);
4073
4557
  let watcher;
4074
4558
  if (!options.persistent) {
4075
- watcher = createFsWatchInstance(path27, options, listener, errHandler, rawEmitter);
4559
+ watcher = createFsWatchInstance(path31, options, listener, errHandler, rawEmitter);
4076
4560
  if (!watcher)
4077
4561
  return;
4078
4562
  return watcher.close.bind(watcher);
@@ -4083,7 +4567,7 @@ var init_handler = __esm({
4083
4567
  addAndConvert(cont, KEY_RAW, rawEmitter);
4084
4568
  } else {
4085
4569
  watcher = createFsWatchInstance(
4086
- path27,
4570
+ path31,
4087
4571
  options,
4088
4572
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
4089
4573
  errHandler,
@@ -4098,7 +4582,7 @@ var init_handler = __esm({
4098
4582
  cont.watcherUnusable = true;
4099
4583
  if (isWindows && error.code === "EPERM") {
4100
4584
  try {
4101
- const fd = await open(path27, "r");
4585
+ const fd = await open(path31, "r");
4102
4586
  await fd.close();
4103
4587
  broadcastErr(error);
4104
4588
  } catch (err) {
@@ -4129,7 +4613,7 @@ var init_handler = __esm({
4129
4613
  };
4130
4614
  };
4131
4615
  FsWatchFileInstances = /* @__PURE__ */ new Map();
4132
- setFsWatchFileListener = (path27, fullPath, options, handlers) => {
4616
+ setFsWatchFileListener = (path31, fullPath, options, handlers) => {
4133
4617
  const { listener, rawEmitter } = handlers;
4134
4618
  let cont = FsWatchFileInstances.get(fullPath);
4135
4619
  const copts = cont && cont.options;
@@ -4151,7 +4635,7 @@ var init_handler = __esm({
4151
4635
  });
4152
4636
  const currmtime = curr.mtimeMs;
4153
4637
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
4154
- foreach(cont.listeners, (listener2) => listener2(path27, curr));
4638
+ foreach(cont.listeners, (listener2) => listener2(path31, curr));
4155
4639
  }
4156
4640
  })
4157
4641
  };
@@ -4179,13 +4663,13 @@ var init_handler = __esm({
4179
4663
  * @param listener on fs change
4180
4664
  * @returns closer for the watcher instance
4181
4665
  */
4182
- _watchWithNodeFs(path27, listener) {
4666
+ _watchWithNodeFs(path31, listener) {
4183
4667
  const opts = this.fsw.options;
4184
- const directory = sysPath.dirname(path27);
4185
- const basename3 = sysPath.basename(path27);
4668
+ const directory = sysPath.dirname(path31);
4669
+ const basename3 = sysPath.basename(path31);
4186
4670
  const parent = this.fsw._getWatchedDir(directory);
4187
4671
  parent.add(basename3);
4188
- const absolutePath = sysPath.resolve(path27);
4672
+ const absolutePath = sysPath.resolve(path31);
4189
4673
  const options = {
4190
4674
  persistent: opts.persistent
4191
4675
  };
@@ -4195,12 +4679,12 @@ var init_handler = __esm({
4195
4679
  if (opts.usePolling) {
4196
4680
  const enableBin = opts.interval !== opts.binaryInterval;
4197
4681
  options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
4198
- closer = setFsWatchFileListener(path27, absolutePath, options, {
4682
+ closer = setFsWatchFileListener(path31, absolutePath, options, {
4199
4683
  listener,
4200
4684
  rawEmitter: this.fsw._emitRaw
4201
4685
  });
4202
4686
  } else {
4203
- closer = setFsWatchListener(path27, absolutePath, options, {
4687
+ closer = setFsWatchListener(path31, absolutePath, options, {
4204
4688
  listener,
4205
4689
  errHandler: this._boundHandleError,
4206
4690
  rawEmitter: this.fsw._emitRaw
@@ -4222,7 +4706,7 @@ var init_handler = __esm({
4222
4706
  let prevStats = stats;
4223
4707
  if (parent.has(basename3))
4224
4708
  return;
4225
- const listener = async (path27, newStats) => {
4709
+ const listener = async (path31, newStats) => {
4226
4710
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
4227
4711
  return;
4228
4712
  if (!newStats || newStats.mtimeMs === 0) {
@@ -4236,11 +4720,11 @@ var init_handler = __esm({
4236
4720
  this.fsw._emit(EV.CHANGE, file, newStats2);
4237
4721
  }
4238
4722
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
4239
- this.fsw._closeFile(path27);
4723
+ this.fsw._closeFile(path31);
4240
4724
  prevStats = newStats2;
4241
4725
  const closer2 = this._watchWithNodeFs(file, listener);
4242
4726
  if (closer2)
4243
- this.fsw._addPathCloser(path27, closer2);
4727
+ this.fsw._addPathCloser(path31, closer2);
4244
4728
  } else {
4245
4729
  prevStats = newStats2;
4246
4730
  }
@@ -4272,7 +4756,7 @@ var init_handler = __esm({
4272
4756
  * @param item basename of this item
4273
4757
  * @returns true if no more processing is needed for this entry.
4274
4758
  */
4275
- async _handleSymlink(entry, directory, path27, item) {
4759
+ async _handleSymlink(entry, directory, path31, item) {
4276
4760
  if (this.fsw.closed) {
4277
4761
  return;
4278
4762
  }
@@ -4282,7 +4766,7 @@ var init_handler = __esm({
4282
4766
  this.fsw._incrReadyCount();
4283
4767
  let linkPath;
4284
4768
  try {
4285
- linkPath = await fsrealpath(path27);
4769
+ linkPath = await fsrealpath(path31);
4286
4770
  } catch (e) {
4287
4771
  this.fsw._emitReady();
4288
4772
  return true;
@@ -4292,12 +4776,12 @@ var init_handler = __esm({
4292
4776
  if (dir.has(item)) {
4293
4777
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
4294
4778
  this.fsw._symlinkPaths.set(full, linkPath);
4295
- this.fsw._emit(EV.CHANGE, path27, entry.stats);
4779
+ this.fsw._emit(EV.CHANGE, path31, entry.stats);
4296
4780
  }
4297
4781
  } else {
4298
4782
  dir.add(item);
4299
4783
  this.fsw._symlinkPaths.set(full, linkPath);
4300
- this.fsw._emit(EV.ADD, path27, entry.stats);
4784
+ this.fsw._emit(EV.ADD, path31, entry.stats);
4301
4785
  }
4302
4786
  this.fsw._emitReady();
4303
4787
  return true;
@@ -4326,9 +4810,9 @@ var init_handler = __esm({
4326
4810
  return;
4327
4811
  }
4328
4812
  const item = entry.path;
4329
- let path27 = sysPath.join(directory, item);
4813
+ let path31 = sysPath.join(directory, item);
4330
4814
  current.add(item);
4331
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path27, item)) {
4815
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path31, item)) {
4332
4816
  return;
4333
4817
  }
4334
4818
  if (this.fsw.closed) {
@@ -4337,8 +4821,8 @@ var init_handler = __esm({
4337
4821
  }
4338
4822
  if (item === target || !target && !previous.has(item)) {
4339
4823
  this.fsw._incrReadyCount();
4340
- path27 = sysPath.join(dir, sysPath.relative(dir, path27));
4341
- this._addToNodeFs(path27, initialAdd, wh, depth + 1);
4824
+ path31 = sysPath.join(dir, sysPath.relative(dir, path31));
4825
+ this._addToNodeFs(path31, initialAdd, wh, depth + 1);
4342
4826
  }
4343
4827
  }).on(EV.ERROR, this._boundHandleError);
4344
4828
  return new Promise((resolve3, reject) => {
@@ -4407,13 +4891,13 @@ var init_handler = __esm({
4407
4891
  * @param depth Child path actually targeted for watch
4408
4892
  * @param target Child path actually targeted for watch
4409
4893
  */
4410
- async _addToNodeFs(path27, initialAdd, priorWh, depth, target) {
4894
+ async _addToNodeFs(path31, initialAdd, priorWh, depth, target) {
4411
4895
  const ready = this.fsw._emitReady;
4412
- if (this.fsw._isIgnored(path27) || this.fsw.closed) {
4896
+ if (this.fsw._isIgnored(path31) || this.fsw.closed) {
4413
4897
  ready();
4414
4898
  return false;
4415
4899
  }
4416
- const wh = this.fsw._getWatchHelpers(path27);
4900
+ const wh = this.fsw._getWatchHelpers(path31);
4417
4901
  if (priorWh) {
4418
4902
  wh.filterPath = (entry) => priorWh.filterPath(entry);
4419
4903
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -4429,8 +4913,8 @@ var init_handler = __esm({
4429
4913
  const follow = this.fsw.options.followSymlinks;
4430
4914
  let closer;
4431
4915
  if (stats.isDirectory()) {
4432
- const absPath = sysPath.resolve(path27);
4433
- const targetPath = follow ? await fsrealpath(path27) : path27;
4916
+ const absPath = sysPath.resolve(path31);
4917
+ const targetPath = follow ? await fsrealpath(path31) : path31;
4434
4918
  if (this.fsw.closed)
4435
4919
  return;
4436
4920
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -4440,29 +4924,29 @@ var init_handler = __esm({
4440
4924
  this.fsw._symlinkPaths.set(absPath, targetPath);
4441
4925
  }
4442
4926
  } else if (stats.isSymbolicLink()) {
4443
- const targetPath = follow ? await fsrealpath(path27) : path27;
4927
+ const targetPath = follow ? await fsrealpath(path31) : path31;
4444
4928
  if (this.fsw.closed)
4445
4929
  return;
4446
4930
  const parent = sysPath.dirname(wh.watchPath);
4447
4931
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
4448
4932
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
4449
- closer = await this._handleDir(parent, stats, initialAdd, depth, path27, wh, targetPath);
4933
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path31, wh, targetPath);
4450
4934
  if (this.fsw.closed)
4451
4935
  return;
4452
4936
  if (targetPath !== void 0) {
4453
- this.fsw._symlinkPaths.set(sysPath.resolve(path27), targetPath);
4937
+ this.fsw._symlinkPaths.set(sysPath.resolve(path31), targetPath);
4454
4938
  }
4455
4939
  } else {
4456
4940
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
4457
4941
  }
4458
4942
  ready();
4459
4943
  if (closer)
4460
- this.fsw._addPathCloser(path27, closer);
4944
+ this.fsw._addPathCloser(path31, closer);
4461
4945
  return false;
4462
4946
  } catch (error) {
4463
4947
  if (this.fsw._handleError(error)) {
4464
4948
  ready();
4465
- return path27;
4949
+ return path31;
4466
4950
  }
4467
4951
  }
4468
4952
  }
@@ -4501,26 +4985,26 @@ function createPattern(matcher) {
4501
4985
  }
4502
4986
  return () => false;
4503
4987
  }
4504
- function normalizePath2(path27) {
4505
- if (typeof path27 !== "string")
4988
+ function normalizePath2(path31) {
4989
+ if (typeof path31 !== "string")
4506
4990
  throw new Error("string expected");
4507
- path27 = sysPath2.normalize(path27);
4508
- path27 = path27.replace(/\\/g, "/");
4991
+ path31 = sysPath2.normalize(path31);
4992
+ path31 = path31.replace(/\\/g, "/");
4509
4993
  let prepend = false;
4510
- if (path27.startsWith("//"))
4994
+ if (path31.startsWith("//"))
4511
4995
  prepend = true;
4512
4996
  const DOUBLE_SLASH_RE2 = /\/\//;
4513
- while (path27.match(DOUBLE_SLASH_RE2))
4514
- path27 = path27.replace(DOUBLE_SLASH_RE2, "/");
4997
+ while (path31.match(DOUBLE_SLASH_RE2))
4998
+ path31 = path31.replace(DOUBLE_SLASH_RE2, "/");
4515
4999
  if (prepend)
4516
- path27 = "/" + path27;
4517
- return path27;
5000
+ path31 = "/" + path31;
5001
+ return path31;
4518
5002
  }
4519
5003
  function matchPatterns(patterns, testString, stats) {
4520
- const path27 = normalizePath2(testString);
5004
+ const path31 = normalizePath2(testString);
4521
5005
  for (let index = 0; index < patterns.length; index++) {
4522
5006
  const pattern = patterns[index];
4523
- if (pattern(path27, stats)) {
5007
+ if (pattern(path31, stats)) {
4524
5008
  return true;
4525
5009
  }
4526
5010
  }
@@ -4581,19 +5065,19 @@ var init_esm2 = __esm({
4581
5065
  }
4582
5066
  return str;
4583
5067
  };
4584
- normalizePathToUnix = (path27) => toUnix(sysPath2.normalize(toUnix(path27)));
4585
- normalizeIgnored = (cwd = "") => (path27) => {
4586
- if (typeof path27 === "string") {
4587
- return normalizePathToUnix(sysPath2.isAbsolute(path27) ? path27 : sysPath2.join(cwd, path27));
5068
+ normalizePathToUnix = (path31) => toUnix(sysPath2.normalize(toUnix(path31)));
5069
+ normalizeIgnored = (cwd = "") => (path31) => {
5070
+ if (typeof path31 === "string") {
5071
+ return normalizePathToUnix(sysPath2.isAbsolute(path31) ? path31 : sysPath2.join(cwd, path31));
4588
5072
  } else {
4589
- return path27;
5073
+ return path31;
4590
5074
  }
4591
5075
  };
4592
- getAbsolutePath = (path27, cwd) => {
4593
- if (sysPath2.isAbsolute(path27)) {
4594
- return path27;
5076
+ getAbsolutePath = (path31, cwd) => {
5077
+ if (sysPath2.isAbsolute(path31)) {
5078
+ return path31;
4595
5079
  }
4596
- return sysPath2.join(cwd, path27);
5080
+ return sysPath2.join(cwd, path31);
4597
5081
  };
4598
5082
  EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
4599
5083
  DirEntry = class {
@@ -4648,10 +5132,10 @@ var init_esm2 = __esm({
4648
5132
  STAT_METHOD_F = "stat";
4649
5133
  STAT_METHOD_L = "lstat";
4650
5134
  WatchHelper = class {
4651
- constructor(path27, follow, fsw) {
5135
+ constructor(path31, follow, fsw) {
4652
5136
  this.fsw = fsw;
4653
- const watchPath = path27;
4654
- this.path = path27 = path27.replace(REPLACER_RE, "");
5137
+ const watchPath = path31;
5138
+ this.path = path31 = path31.replace(REPLACER_RE, "");
4655
5139
  this.watchPath = watchPath;
4656
5140
  this.fullWatchPath = sysPath2.resolve(watchPath);
4657
5141
  this.dirParts = [];
@@ -4773,20 +5257,20 @@ var init_esm2 = __esm({
4773
5257
  this._closePromise = void 0;
4774
5258
  let paths = unifyPaths(paths_);
4775
5259
  if (cwd) {
4776
- paths = paths.map((path27) => {
4777
- const absPath = getAbsolutePath(path27, cwd);
5260
+ paths = paths.map((path31) => {
5261
+ const absPath = getAbsolutePath(path31, cwd);
4778
5262
  return absPath;
4779
5263
  });
4780
5264
  }
4781
- paths.forEach((path27) => {
4782
- this._removeIgnoredPath(path27);
5265
+ paths.forEach((path31) => {
5266
+ this._removeIgnoredPath(path31);
4783
5267
  });
4784
5268
  this._userIgnored = void 0;
4785
5269
  if (!this._readyCount)
4786
5270
  this._readyCount = 0;
4787
5271
  this._readyCount += paths.length;
4788
- Promise.all(paths.map(async (path27) => {
4789
- const res = await this._nodeFsHandler._addToNodeFs(path27, !_internal, void 0, 0, _origAdd);
5272
+ Promise.all(paths.map(async (path31) => {
5273
+ const res = await this._nodeFsHandler._addToNodeFs(path31, !_internal, void 0, 0, _origAdd);
4790
5274
  if (res)
4791
5275
  this._emitReady();
4792
5276
  return res;
@@ -4808,17 +5292,17 @@ var init_esm2 = __esm({
4808
5292
  return this;
4809
5293
  const paths = unifyPaths(paths_);
4810
5294
  const { cwd } = this.options;
4811
- paths.forEach((path27) => {
4812
- if (!sysPath2.isAbsolute(path27) && !this._closers.has(path27)) {
5295
+ paths.forEach((path31) => {
5296
+ if (!sysPath2.isAbsolute(path31) && !this._closers.has(path31)) {
4813
5297
  if (cwd)
4814
- path27 = sysPath2.join(cwd, path27);
4815
- path27 = sysPath2.resolve(path27);
5298
+ path31 = sysPath2.join(cwd, path31);
5299
+ path31 = sysPath2.resolve(path31);
4816
5300
  }
4817
- this._closePath(path27);
4818
- this._addIgnoredPath(path27);
4819
- if (this._watched.has(path27)) {
5301
+ this._closePath(path31);
5302
+ this._addIgnoredPath(path31);
5303
+ if (this._watched.has(path31)) {
4820
5304
  this._addIgnoredPath({
4821
- path: path27,
5305
+ path: path31,
4822
5306
  recursive: true
4823
5307
  });
4824
5308
  }
@@ -4882,38 +5366,38 @@ var init_esm2 = __esm({
4882
5366
  * @param stats arguments to be passed with event
4883
5367
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
4884
5368
  */
4885
- async _emit(event, path27, stats) {
5369
+ async _emit(event, path31, stats) {
4886
5370
  if (this.closed)
4887
5371
  return;
4888
5372
  const opts = this.options;
4889
5373
  if (isWindows)
4890
- path27 = sysPath2.normalize(path27);
5374
+ path31 = sysPath2.normalize(path31);
4891
5375
  if (opts.cwd)
4892
- path27 = sysPath2.relative(opts.cwd, path27);
4893
- const args = [path27];
5376
+ path31 = sysPath2.relative(opts.cwd, path31);
5377
+ const args = [path31];
4894
5378
  if (stats != null)
4895
5379
  args.push(stats);
4896
5380
  const awf = opts.awaitWriteFinish;
4897
5381
  let pw;
4898
- if (awf && (pw = this._pendingWrites.get(path27))) {
5382
+ if (awf && (pw = this._pendingWrites.get(path31))) {
4899
5383
  pw.lastChange = /* @__PURE__ */ new Date();
4900
5384
  return this;
4901
5385
  }
4902
5386
  if (opts.atomic) {
4903
5387
  if (event === EVENTS.UNLINK) {
4904
- this._pendingUnlinks.set(path27, [event, ...args]);
5388
+ this._pendingUnlinks.set(path31, [event, ...args]);
4905
5389
  setTimeout(() => {
4906
- this._pendingUnlinks.forEach((entry, path28) => {
5390
+ this._pendingUnlinks.forEach((entry, path32) => {
4907
5391
  this.emit(...entry);
4908
5392
  this.emit(EVENTS.ALL, ...entry);
4909
- this._pendingUnlinks.delete(path28);
5393
+ this._pendingUnlinks.delete(path32);
4910
5394
  });
4911
5395
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
4912
5396
  return this;
4913
5397
  }
4914
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path27)) {
5398
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path31)) {
4915
5399
  event = EVENTS.CHANGE;
4916
- this._pendingUnlinks.delete(path27);
5400
+ this._pendingUnlinks.delete(path31);
4917
5401
  }
4918
5402
  }
4919
5403
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -4931,16 +5415,16 @@ var init_esm2 = __esm({
4931
5415
  this.emitWithAll(event, args);
4932
5416
  }
4933
5417
  };
4934
- this._awaitWriteFinish(path27, awf.stabilityThreshold, event, awfEmit);
5418
+ this._awaitWriteFinish(path31, awf.stabilityThreshold, event, awfEmit);
4935
5419
  return this;
4936
5420
  }
4937
5421
  if (event === EVENTS.CHANGE) {
4938
- const isThrottled = !this._throttle(EVENTS.CHANGE, path27, 50);
5422
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path31, 50);
4939
5423
  if (isThrottled)
4940
5424
  return this;
4941
5425
  }
4942
5426
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
4943
- const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path27) : path27;
5427
+ const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path31) : path31;
4944
5428
  let stats2;
4945
5429
  try {
4946
5430
  stats2 = await stat3(fullPath);
@@ -4971,23 +5455,23 @@ var init_esm2 = __esm({
4971
5455
  * @param timeout duration of time to suppress duplicate actions
4972
5456
  * @returns tracking object or false if action should be suppressed
4973
5457
  */
4974
- _throttle(actionType, path27, timeout) {
5458
+ _throttle(actionType, path31, timeout) {
4975
5459
  if (!this._throttled.has(actionType)) {
4976
5460
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
4977
5461
  }
4978
5462
  const action = this._throttled.get(actionType);
4979
5463
  if (!action)
4980
5464
  throw new Error("invalid throttle");
4981
- const actionPath = action.get(path27);
5465
+ const actionPath = action.get(path31);
4982
5466
  if (actionPath) {
4983
5467
  actionPath.count++;
4984
5468
  return false;
4985
5469
  }
4986
5470
  let timeoutObject;
4987
5471
  const clear = () => {
4988
- const item = action.get(path27);
5472
+ const item = action.get(path31);
4989
5473
  const count = item ? item.count : 0;
4990
- action.delete(path27);
5474
+ action.delete(path31);
4991
5475
  clearTimeout(timeoutObject);
4992
5476
  if (item)
4993
5477
  clearTimeout(item.timeoutObject);
@@ -4995,7 +5479,7 @@ var init_esm2 = __esm({
4995
5479
  };
4996
5480
  timeoutObject = setTimeout(clear, timeout);
4997
5481
  const thr = { timeoutObject, clear, count: 0 };
4998
- action.set(path27, thr);
5482
+ action.set(path31, thr);
4999
5483
  return thr;
5000
5484
  }
5001
5485
  _incrReadyCount() {
@@ -5009,44 +5493,44 @@ var init_esm2 = __esm({
5009
5493
  * @param event
5010
5494
  * @param awfEmit Callback to be called when ready for event to be emitted.
5011
5495
  */
5012
- _awaitWriteFinish(path27, threshold, event, awfEmit) {
5496
+ _awaitWriteFinish(path31, threshold, event, awfEmit) {
5013
5497
  const awf = this.options.awaitWriteFinish;
5014
5498
  if (typeof awf !== "object")
5015
5499
  return;
5016
5500
  const pollInterval = awf.pollInterval;
5017
5501
  let timeoutHandler;
5018
- let fullPath = path27;
5019
- if (this.options.cwd && !sysPath2.isAbsolute(path27)) {
5020
- fullPath = sysPath2.join(this.options.cwd, path27);
5502
+ let fullPath = path31;
5503
+ if (this.options.cwd && !sysPath2.isAbsolute(path31)) {
5504
+ fullPath = sysPath2.join(this.options.cwd, path31);
5021
5505
  }
5022
5506
  const now = /* @__PURE__ */ new Date();
5023
5507
  const writes = this._pendingWrites;
5024
5508
  function awaitWriteFinishFn(prevStat) {
5025
5509
  statcb(fullPath, (err, curStat) => {
5026
- if (err || !writes.has(path27)) {
5510
+ if (err || !writes.has(path31)) {
5027
5511
  if (err && err.code !== "ENOENT")
5028
5512
  awfEmit(err);
5029
5513
  return;
5030
5514
  }
5031
5515
  const now2 = Number(/* @__PURE__ */ new Date());
5032
5516
  if (prevStat && curStat.size !== prevStat.size) {
5033
- writes.get(path27).lastChange = now2;
5517
+ writes.get(path31).lastChange = now2;
5034
5518
  }
5035
- const pw = writes.get(path27);
5519
+ const pw = writes.get(path31);
5036
5520
  const df = now2 - pw.lastChange;
5037
5521
  if (df >= threshold) {
5038
- writes.delete(path27);
5522
+ writes.delete(path31);
5039
5523
  awfEmit(void 0, curStat);
5040
5524
  } else {
5041
5525
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
5042
5526
  }
5043
5527
  });
5044
5528
  }
5045
- if (!writes.has(path27)) {
5046
- writes.set(path27, {
5529
+ if (!writes.has(path31)) {
5530
+ writes.set(path31, {
5047
5531
  lastChange: now,
5048
5532
  cancelWait: () => {
5049
- writes.delete(path27);
5533
+ writes.delete(path31);
5050
5534
  clearTimeout(timeoutHandler);
5051
5535
  return event;
5052
5536
  }
@@ -5057,8 +5541,8 @@ var init_esm2 = __esm({
5057
5541
  /**
5058
5542
  * Determines whether user has asked to ignore this path.
5059
5543
  */
5060
- _isIgnored(path27, stats) {
5061
- if (this.options.atomic && DOT_RE.test(path27))
5544
+ _isIgnored(path31, stats) {
5545
+ if (this.options.atomic && DOT_RE.test(path31))
5062
5546
  return true;
5063
5547
  if (!this._userIgnored) {
5064
5548
  const { cwd } = this.options;
@@ -5068,17 +5552,17 @@ var init_esm2 = __esm({
5068
5552
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
5069
5553
  this._userIgnored = anymatch(list, void 0);
5070
5554
  }
5071
- return this._userIgnored(path27, stats);
5555
+ return this._userIgnored(path31, stats);
5072
5556
  }
5073
- _isntIgnored(path27, stat4) {
5074
- return !this._isIgnored(path27, stat4);
5557
+ _isntIgnored(path31, stat4) {
5558
+ return !this._isIgnored(path31, stat4);
5075
5559
  }
5076
5560
  /**
5077
5561
  * Provides a set of common helpers and properties relating to symlink handling.
5078
5562
  * @param path file or directory pattern being watched
5079
5563
  */
5080
- _getWatchHelpers(path27) {
5081
- return new WatchHelper(path27, this.options.followSymlinks, this);
5564
+ _getWatchHelpers(path31) {
5565
+ return new WatchHelper(path31, this.options.followSymlinks, this);
5082
5566
  }
5083
5567
  // Directory helpers
5084
5568
  // -----------------
@@ -5110,63 +5594,63 @@ var init_esm2 = __esm({
5110
5594
  * @param item base path of item/directory
5111
5595
  */
5112
5596
  _remove(directory, item, isDirectory) {
5113
- const path27 = sysPath2.join(directory, item);
5114
- const fullPath = sysPath2.resolve(path27);
5115
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path27) || this._watched.has(fullPath);
5116
- if (!this._throttle("remove", path27, 100))
5597
+ const path31 = sysPath2.join(directory, item);
5598
+ const fullPath = sysPath2.resolve(path31);
5599
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path31) || this._watched.has(fullPath);
5600
+ if (!this._throttle("remove", path31, 100))
5117
5601
  return;
5118
5602
  if (!isDirectory && this._watched.size === 1) {
5119
5603
  this.add(directory, item, true);
5120
5604
  }
5121
- const wp = this._getWatchedDir(path27);
5605
+ const wp = this._getWatchedDir(path31);
5122
5606
  const nestedDirectoryChildren = wp.getChildren();
5123
- nestedDirectoryChildren.forEach((nested) => this._remove(path27, nested));
5607
+ nestedDirectoryChildren.forEach((nested) => this._remove(path31, nested));
5124
5608
  const parent = this._getWatchedDir(directory);
5125
5609
  const wasTracked = parent.has(item);
5126
5610
  parent.remove(item);
5127
5611
  if (this._symlinkPaths.has(fullPath)) {
5128
5612
  this._symlinkPaths.delete(fullPath);
5129
5613
  }
5130
- let relPath = path27;
5614
+ let relPath = path31;
5131
5615
  if (this.options.cwd)
5132
- relPath = sysPath2.relative(this.options.cwd, path27);
5616
+ relPath = sysPath2.relative(this.options.cwd, path31);
5133
5617
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
5134
5618
  const event = this._pendingWrites.get(relPath).cancelWait();
5135
5619
  if (event === EVENTS.ADD)
5136
5620
  return;
5137
5621
  }
5138
- this._watched.delete(path27);
5622
+ this._watched.delete(path31);
5139
5623
  this._watched.delete(fullPath);
5140
5624
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
5141
- if (wasTracked && !this._isIgnored(path27))
5142
- this._emit(eventName, path27);
5143
- this._closePath(path27);
5625
+ if (wasTracked && !this._isIgnored(path31))
5626
+ this._emit(eventName, path31);
5627
+ this._closePath(path31);
5144
5628
  }
5145
5629
  /**
5146
5630
  * Closes all watchers for a path
5147
5631
  */
5148
- _closePath(path27) {
5149
- this._closeFile(path27);
5150
- const dir = sysPath2.dirname(path27);
5151
- this._getWatchedDir(dir).remove(sysPath2.basename(path27));
5632
+ _closePath(path31) {
5633
+ this._closeFile(path31);
5634
+ const dir = sysPath2.dirname(path31);
5635
+ this._getWatchedDir(dir).remove(sysPath2.basename(path31));
5152
5636
  }
5153
5637
  /**
5154
5638
  * Closes only file-specific watchers
5155
5639
  */
5156
- _closeFile(path27) {
5157
- const closers = this._closers.get(path27);
5640
+ _closeFile(path31) {
5641
+ const closers = this._closers.get(path31);
5158
5642
  if (!closers)
5159
5643
  return;
5160
5644
  closers.forEach((closer) => closer());
5161
- this._closers.delete(path27);
5645
+ this._closers.delete(path31);
5162
5646
  }
5163
- _addPathCloser(path27, closer) {
5647
+ _addPathCloser(path31, closer) {
5164
5648
  if (!closer)
5165
5649
  return;
5166
- let list = this._closers.get(path27);
5650
+ let list = this._closers.get(path31);
5167
5651
  if (!list) {
5168
5652
  list = [];
5169
- this._closers.set(path27, list);
5653
+ this._closers.set(path31, list);
5170
5654
  }
5171
5655
  list.push(closer);
5172
5656
  }
@@ -5192,73 +5676,6 @@ var init_esm2 = __esm({
5192
5676
  }
5193
5677
  });
5194
5678
 
5195
- // src/cli/compileSourceFiles.ts
5196
- import path18 from "path";
5197
- import fs14 from "fs";
5198
- import fg4 from "fast-glob";
5199
- async function compileSourceFiles(options) {
5200
- const { rootDir, dist, files, logLevel = "silent", production, atomicWrite } = options;
5201
- const entryPoints = files ?? await fg4([`${APP_DIR}/**/*.ts`], {
5202
- cwd: rootDir,
5203
- onlyFiles: true,
5204
- absolute: true,
5205
- ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
5206
- });
5207
- if (entryPoints.length === 0) {
5208
- return { compiledFiles: [] };
5209
- }
5210
- const absDist = path18.resolve(rootDir, dist);
5211
- await fs14.promises.mkdir(absDist, { recursive: true });
5212
- const plugins = buildAliasPlugins(rootDir);
5213
- const esbuild = await import("esbuild");
5214
- const outbase = path18.resolve(rootDir, APP_DIR);
5215
- const result = await esbuild.build({
5216
- entryPoints,
5217
- outdir: absDist,
5218
- outbase,
5219
- bundle: false,
5220
- platform: "node",
5221
- format: "esm",
5222
- sourcemap: true,
5223
- packages: "external",
5224
- plugins,
5225
- // build 语义:编译期 NODE_ENV 替换 + 死分支删除(见 AGENTS.md §5.3)
5226
- ...production ? { define: { "process.env.NODE_ENV": '"production"' }, minifySyntax: true } : {},
5227
- logLevel,
5228
- // dev 语义:esbuild 返回内存内容,由下方原子写落盘
5229
- ...atomicWrite ? { write: false } : {}
5230
- });
5231
- if (atomicWrite && result.outputFiles) {
5232
- await Promise.all(
5233
- result.outputFiles.map(async (file) => {
5234
- await fs14.promises.mkdir(path18.dirname(file.path), { recursive: true });
5235
- const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
5236
- await fs14.promises.writeFile(tmp, file.contents);
5237
- await fs14.promises.rename(tmp, file.path);
5238
- })
5239
- );
5240
- }
5241
- return { compiledFiles: entryPoints };
5242
- }
5243
- var init_compileSourceFiles = __esm({
5244
- "src/cli/compileSourceFiles.ts"() {
5245
- "use strict";
5246
- init_aliasPlugin();
5247
- init_prodPaths();
5248
- }
5249
- });
5250
-
5251
- // src/cli/compileDevRoutes.ts
5252
- async function compileDevRoutes(options) {
5253
- return compileSourceFiles({ ...options, atomicWrite: true });
5254
- }
5255
- var init_compileDevRoutes = __esm({
5256
- "src/cli/compileDevRoutes.ts"() {
5257
- "use strict";
5258
- init_compileSourceFiles();
5259
- }
5260
- });
5261
-
5262
5679
  // src/cli/rebuildScheduler.ts
5263
5680
  function createRebuildScheduler(options) {
5264
5681
  const { rebuild, debounceMs = 100, onError } = options;
@@ -5321,7 +5738,7 @@ var init_rebuildScheduler = __esm({
5321
5738
  });
5322
5739
 
5323
5740
  // src/cli/watcher.ts
5324
- import path19 from "path";
5741
+ import path22 from "path";
5325
5742
  function startWatcher(options) {
5326
5743
  const { rootDir, app, devDist } = options;
5327
5744
  async function rebuildRoutes(files) {
@@ -5336,6 +5753,7 @@ function startWatcher(options) {
5336
5753
  await app.reloadRoutes();
5337
5754
  await app.reloadTools();
5338
5755
  await app.reloadAgents();
5756
+ await app.reloadTasks();
5339
5757
  console.log(
5340
5758
  `- Routes rebuilt${files.length > 0 ? `, ${files.length} file(s) recompiled` : ""}`
5341
5759
  );
@@ -5356,9 +5774,9 @@ function startWatcher(options) {
5356
5774
  }
5357
5775
  });
5358
5776
  const CONFIG_FILES = ["faapi.config.ts", "faapi.config.js"];
5359
- const configAbsPaths = new Set(CONFIG_FILES.map((f) => path19.resolve(rootDir, f)));
5777
+ const configAbsPaths = new Set(CONFIG_FILES.map((f) => path22.resolve(rootDir, f)));
5360
5778
  const watchPaths = ["src", "tsconfig.json", ...CONFIG_FILES];
5361
- const scheduleOnly = /* @__PURE__ */ new Set([...configAbsPaths, path19.resolve(rootDir, "tsconfig.json")]);
5779
+ const scheduleOnly = /* @__PURE__ */ new Set([...configAbsPaths, path22.resolve(rootDir, "tsconfig.json")]);
5362
5780
  const watcher = esm_default.watch(watchPaths, {
5363
5781
  cwd: rootDir,
5364
5782
  ignoreInitial: true,
@@ -5375,7 +5793,7 @@ function startWatcher(options) {
5375
5793
  watcher.on("add", (file) => handleFileEvent(file));
5376
5794
  watcher.on("change", (file) => handleFileEvent(file));
5377
5795
  function handleFileEvent(file) {
5378
- const abs = path19.resolve(rootDir, file);
5796
+ const abs = path22.resolve(rootDir, file);
5379
5797
  if (scheduleOnly.has(abs)) {
5380
5798
  scheduler.schedule();
5381
5799
  return;
@@ -5477,18 +5895,18 @@ function getWsIndex(routes) {
5477
5895
  wsIndexCache.set(routes, index);
5478
5896
  return index;
5479
5897
  }
5480
- function matchRoute(routes, method, path27) {
5898
+ function matchRoute(routes, method, path31) {
5481
5899
  const index = getHttpIndex(routes);
5482
5900
  const upper = method.toUpperCase();
5483
- const hit = matchByMethod(index, upper, path27);
5901
+ const hit = matchByMethod(index, upper, path31);
5484
5902
  if (hit) return hit;
5485
5903
  if (upper === "HEAD") {
5486
- return matchByMethod(index, "GET", path27);
5904
+ return matchByMethod(index, "GET", path31);
5487
5905
  }
5488
5906
  return null;
5489
5907
  }
5490
- function matchByMethod(index, method, path27) {
5491
- const staticHit = index.static.get(`${method}|${path27}`);
5908
+ function matchByMethod(index, method, path31) {
5909
+ const staticHit = index.static.get(`${method}|${path31}`);
5492
5910
  if (staticHit) {
5493
5911
  return { route: staticHit, params: {} };
5494
5912
  }
@@ -5497,31 +5915,31 @@ function matchByMethod(index, method, path27) {
5497
5915
  if (route.method !== method) {
5498
5916
  continue;
5499
5917
  }
5500
- const params = matchSegments(entry.segments, path27, route.paramNames, route.isCatchAll);
5918
+ const params = matchSegments(entry.segments, path31, route.paramNames, route.isCatchAll);
5501
5919
  if (params !== null) {
5502
5920
  return { route, params };
5503
5921
  }
5504
5922
  }
5505
5923
  return null;
5506
5924
  }
5507
- function matchWsRoute(wsRoutes, path27) {
5925
+ function matchWsRoute(wsRoutes, path31) {
5508
5926
  const index = getWsIndex(wsRoutes);
5509
- const staticHit = index.static.get(path27);
5927
+ const staticHit = index.static.get(path31);
5510
5928
  if (staticHit) {
5511
5929
  return { route: staticHit, params: {} };
5512
5930
  }
5513
5931
  for (const route of index.dynamics) {
5514
- const params = matchDynamicPath(route.urlPath, path27, route.paramNames, route.isCatchAll);
5932
+ const params = matchDynamicPath(route.urlPath, path31, route.paramNames, route.isCatchAll);
5515
5933
  if (params !== null) {
5516
5934
  return { route, params };
5517
5935
  }
5518
5936
  }
5519
5937
  return null;
5520
5938
  }
5521
- function findAllowedMethods(routes, path27) {
5939
+ function findAllowedMethods(routes, path31) {
5522
5940
  const index = getHttpIndex(routes);
5523
5941
  const methods = /* @__PURE__ */ new Set();
5524
- const staticMethods = index.methodsByStaticPath.get(path27);
5942
+ const staticMethods = index.methodsByStaticPath.get(path31);
5525
5943
  if (staticMethods) {
5526
5944
  for (const method of staticMethods) {
5527
5945
  methods.add(method);
@@ -5530,7 +5948,7 @@ function findAllowedMethods(routes, path27) {
5530
5948
  for (const entry of index.dynamics) {
5531
5949
  const params = matchSegments(
5532
5950
  entry.segments,
5533
- path27,
5951
+ path31,
5534
5952
  entry.route.paramNames,
5535
5953
  entry.route.isCatchAll
5536
5954
  );
@@ -5543,11 +5961,11 @@ function findAllowedMethods(routes, path27) {
5543
5961
  }
5544
5962
  return Array.from(methods);
5545
5963
  }
5546
- function matchDynamicPath(pattern, path27, paramNames, isCatchAll) {
5547
- return matchSegments(pattern.split("/").filter(Boolean), path27, paramNames, isCatchAll);
5964
+ function matchDynamicPath(pattern, path31, paramNames, isCatchAll) {
5965
+ return matchSegments(pattern.split("/").filter(Boolean), path31, paramNames, isCatchAll);
5548
5966
  }
5549
- function matchSegments(patternSegments, path27, paramNames, isCatchAll) {
5550
- const pathSegments = path27.split("/").filter(Boolean);
5967
+ function matchSegments(patternSegments, path31, paramNames, isCatchAll) {
5968
+ const pathSegments = path31.split("/").filter(Boolean);
5551
5969
  if (isCatchAll) {
5552
5970
  const nonCatchAllCount = patternSegments.length - 1;
5553
5971
  if (pathSegments.length <= nonCatchAllCount) {
@@ -5598,230 +6016,39 @@ var init_matchRoute = __esm({
5598
6016
  httpIndexCache = /* @__PURE__ */ new WeakMap();
5599
6017
  wsIndexCache = /* @__PURE__ */ new WeakMap();
5600
6018
  }
5601
- });
5602
-
5603
- // src/loader/resolveExports.ts
5604
- function resolveExport(module, exportName) {
5605
- if (exportName in module && typeof module[exportName] !== "undefined") {
5606
- return module[exportName];
5607
- }
5608
- const defaultExport = module.default;
5609
- if (defaultExport !== null && typeof defaultExport === "object") {
5610
- const value = defaultExport[exportName];
5611
- if (value !== void 0) {
5612
- return value;
5613
- }
5614
- }
5615
- return void 0;
5616
- }
5617
- var init_resolveExports = __esm({
5618
- "src/loader/resolveExports.ts"() {
5619
- "use strict";
5620
- }
5621
- });
5622
-
5623
- // src/loader/validateRouteModule.ts
5624
- function validateRouteModule(value, method, filePath) {
5625
- if (typeof value !== "function") {
5626
- throw new Error(
5627
- `Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
5628
- );
5629
- }
5630
- }
5631
- var init_validateRouteModule = __esm({
5632
- "src/loader/validateRouteModule.ts"() {
5633
- "use strict";
5634
- }
5635
- });
5636
-
5637
- // src/cli/compileOnDemand.ts
5638
- import path20 from "path";
5639
- import fs15 from "fs";
5640
- function isProductFresh(sourceAbsPath, productAbsPath) {
5641
- try {
5642
- const srcStat = fs15.statSync(sourceAbsPath);
5643
- const prodStat = fs15.statSync(productAbsPath);
5644
- return prodStat.mtimeMs >= srcStat.mtimeMs;
5645
- } catch {
5646
- return false;
5647
- }
5648
- }
5649
- function createDevOnDemandState() {
5650
- return {
5651
- enabled: false,
5652
- distDir: void 0,
5653
- compiledFiles: /* @__PURE__ */ new Set(),
5654
- generatedSchemas: /* @__PURE__ */ new Set(),
5655
- inFlightCompilations: /* @__PURE__ */ new Map(),
5656
- inFlightSchemaGenerations: /* @__PURE__ */ new Map()
5657
- };
5658
- }
5659
- function clearCompiledFiles() {
5660
- state.compiledFiles.clear();
5661
- state.inFlightCompilations.clear();
5662
- sourcePathCache.clear();
5663
- }
5664
- async function ensureCompiled(sourceAbsPath, rootDir, dist) {
5665
- const inFlight2 = state.inFlightCompilations.get(sourceAbsPath);
5666
- if (inFlight2) {
5667
- await inFlight2.catch(() => {
5668
- });
5669
- return false;
5670
- }
5671
- if (state.compiledFiles.has(sourceAbsPath)) {
5672
- return false;
5673
- }
5674
- if (!fs15.existsSync(sourceAbsPath)) {
5675
- return false;
5676
- }
5677
- const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
5678
- if (productPath && isProductFresh(sourceAbsPath, productPath)) {
5679
- state.compiledFiles.add(sourceAbsPath);
5680
- return false;
5681
- }
5682
- const compilePromise = (async () => {
5683
- const { insideFiles } = await collectRelativeImports([sourceAbsPath], rootDir);
5684
- const files = [sourceAbsPath];
5685
- for (const dep of insideFiles) {
5686
- if (state.compiledFiles.has(dep)) continue;
5687
- const depProduct = prodSourcePathToProductPath(dep, rootDir, dist);
5688
- if (depProduct && isProductFresh(dep, depProduct)) continue;
5689
- files.push(dep);
5690
- }
5691
- await compileDevRoutes({
5692
- rootDir,
5693
- dist,
5694
- files,
5695
- logLevel: "silent"
5696
- });
5697
- for (const file of files) {
5698
- state.compiledFiles.add(file);
5699
- }
5700
- state.compiledFiles.add(sourceAbsPath);
5701
- })();
5702
- state.inFlightCompilations.set(sourceAbsPath, compilePromise);
5703
- try {
5704
- await compilePromise;
5705
- return true;
5706
- } finally {
5707
- state.inFlightCompilations.delete(sourceAbsPath);
5708
- }
5709
- }
5710
- async function ensureMiddlewaresCompiled(middlewarePaths, rootDir) {
5711
- if (!isDevOnDemandEnabled() || middlewarePaths.length === 0) return;
5712
- const dist = getDevDist();
5713
- if (!dist) return;
5714
- for (const mwPath of middlewarePaths) {
5715
- const sourcePath = prodPathToSourcePath(mwPath, rootDir, dist);
5716
- try {
5717
- await ensureCompiled(sourcePath, rootDir, dist);
5718
- } catch (err) {
5719
- console.error(`[faapi] Failed to compile middleware source ${sourcePath}:`, err);
5720
- }
5721
- }
5722
- }
5723
- function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
5724
- const rel = path20.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
5725
- if (!rel.startsWith("src/")) return null;
5726
- const relWithoutSrc = rel.slice(4);
5727
- const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
5728
- return path20.resolve(rootDir, dist, jsRel);
5729
- }
5730
- function clearGeneratedSchemas() {
5731
- state.generatedSchemas.clear();
5732
- state.inFlightSchemaGenerations.clear();
5733
- }
5734
- async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
5735
- const inFlight2 = state.inFlightSchemaGenerations.get(schemaPath);
5736
- if (inFlight2) {
5737
- await inFlight2.catch(() => {
5738
- });
5739
- return false;
5740
- }
5741
- if (state.generatedSchemas.has(schemaPath)) {
5742
- return false;
5743
- }
5744
- const prodAbsPath = path20.resolve(rootDir, routeFilePath);
5745
- const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
5746
- if (!fs15.existsSync(sourceAbsPath)) {
5747
- return false;
5748
- }
5749
- if (isProductFresh(sourceAbsPath, schemaPath)) {
5750
- state.generatedSchemas.add(schemaPath);
5751
- return false;
5752
- }
5753
- const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
5754
- if (fileRoutes.length === 0) {
5755
- return false;
5756
- }
5757
- const sourceRelPath = path20.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
5758
- const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
5759
- const generatePromise = (async () => {
5760
- await generateSchemaFiles(sourceRoutes, rootDir, dist);
5761
- state.generatedSchemas.add(schemaPath);
5762
- })();
5763
- state.inFlightSchemaGenerations.set(schemaPath, generatePromise);
5764
- try {
5765
- await generatePromise;
5766
- return true;
5767
- } finally {
5768
- state.inFlightSchemaGenerations.delete(schemaPath);
6019
+ });
6020
+
6021
+ // src/loader/resolveExports.ts
6022
+ function resolveExport(module, exportName) {
6023
+ if (exportName in module && typeof module[exportName] !== "undefined") {
6024
+ return module[exportName];
5769
6025
  }
5770
- }
5771
- async function deleteSchemaFiles(routes, rootDir, dist) {
5772
- const deleted = /* @__PURE__ */ new Set();
5773
- for (const route of routes) {
5774
- const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
5775
- if (deleted.has(schemaPath)) continue;
5776
- deleted.add(schemaPath);
5777
- try {
5778
- await fs15.promises.unlink(schemaPath);
5779
- } catch {
6026
+ const defaultExport = module.default;
6027
+ if (defaultExport !== null && typeof defaultExport === "object") {
6028
+ const value = defaultExport[exportName];
6029
+ if (value !== void 0) {
6030
+ return value;
5780
6031
  }
5781
6032
  }
6033
+ return void 0;
5782
6034
  }
5783
- function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
5784
- const cached = sourcePathCache.get(prodAbsPath);
5785
- if (cached) return cached;
5786
- const rel = path20.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
5787
- let relWithoutDist = rel;
5788
- if (relWithoutDist.startsWith(`${dist}/`)) {
5789
- relWithoutDist = relWithoutDist.slice(dist.length + 1);
6035
+ var init_resolveExports = __esm({
6036
+ "src/loader/resolveExports.ts"() {
6037
+ "use strict";
5790
6038
  }
5791
- const srcRel = `src/${relWithoutDist}`;
5792
- const tsRel = srcRel.replace(/\.js$/, ".ts");
5793
- const tsAbs = path20.resolve(rootDir, tsRel);
5794
- let result;
5795
- if (fs15.existsSync(tsAbs)) {
5796
- result = tsAbs;
5797
- } else {
5798
- result = path20.resolve(rootDir, srcRel);
6039
+ });
6040
+
6041
+ // src/loader/validateRouteModule.ts
6042
+ function validateRouteModule(value, method, filePath) {
6043
+ if (typeof value !== "function") {
6044
+ throw new Error(
6045
+ `Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
6046
+ );
5799
6047
  }
5800
- sourcePathCache.set(prodAbsPath, result);
5801
- return result;
5802
- }
5803
- function setDevOnDemandEnabled(enabled) {
5804
- state.enabled = enabled;
5805
6048
  }
5806
- function isDevOnDemandEnabled() {
5807
- return state.enabled;
5808
- }
5809
- function setDevDist(dist) {
5810
- state.distDir = dist;
5811
- }
5812
- function getDevDist() {
5813
- return state.distDir;
5814
- }
5815
- var state, sourcePathCache;
5816
- var init_compileOnDemand = __esm({
5817
- "src/cli/compileOnDemand.ts"() {
6049
+ var init_validateRouteModule = __esm({
6050
+ "src/loader/validateRouteModule.ts"() {
5818
6051
  "use strict";
5819
- init_compileDevRoutes();
5820
- init_generateSchemaFiles();
5821
- init_generateSchemaFiles();
5822
- init_collectImports();
5823
- state = createDevOnDemandState();
5824
- sourcePathCache = /* @__PURE__ */ new Map();
5825
6052
  }
5826
6053
  });
5827
6054
 
@@ -6011,14 +6238,14 @@ var init_httpErrors = __esm({
6011
6238
  issues;
6012
6239
  };
6013
6240
  RouteNotFoundError = class extends FaapiError {
6014
- constructor(path27) {
6015
- super("ROUTE_NOT_FOUND", `Route not found: ${path27}`, 404);
6241
+ constructor(path31) {
6242
+ super("ROUTE_NOT_FOUND", `Route not found: ${path31}`, 404);
6016
6243
  this.name = "RouteNotFoundError";
6017
6244
  }
6018
6245
  };
6019
6246
  MethodNotAllowedError = class extends FaapiError {
6020
- constructor(method, path27, allowedMethods) {
6021
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path27}`, 405);
6247
+ constructor(method, path31, allowedMethods) {
6248
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path31}`, 405);
6022
6249
  this.allowedMethods = allowedMethods;
6023
6250
  this.name = "MethodNotAllowedError";
6024
6251
  }
@@ -6262,6 +6489,7 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
6262
6489
  };
6263
6490
  if (registries) {
6264
6491
  ctx.registries = registries;
6492
+ ctx.tasks = registries.taskHandle.get(ctx);
6265
6493
  }
6266
6494
  const extend = config?.extendContext;
6267
6495
  if (typeof extend === "function") {
@@ -6559,6 +6787,34 @@ var init_toResponse = __esm({
6559
6787
  }
6560
6788
  });
6561
6789
 
6790
+ // src/task/taskRegistry.ts
6791
+ function createTaskRegistry() {
6792
+ let registry = /* @__PURE__ */ new Map();
6793
+ return {
6794
+ hydrate(tasks) {
6795
+ const next = /* @__PURE__ */ new Map();
6796
+ for (const task of tasks) {
6797
+ next.set(task.name, task);
6798
+ }
6799
+ registry = next;
6800
+ },
6801
+ get(name) {
6802
+ return registry.get(name);
6803
+ },
6804
+ list() {
6805
+ return Array.from(registry.values());
6806
+ },
6807
+ clear() {
6808
+ registry = /* @__PURE__ */ new Map();
6809
+ }
6810
+ };
6811
+ }
6812
+ var init_taskRegistry = __esm({
6813
+ "src/task/taskRegistry.ts"() {
6814
+ "use strict";
6815
+ }
6816
+ });
6817
+
6562
6818
  // src/injection/registries.ts
6563
6819
  function createToolRegistry() {
6564
6820
  let registry = /* @__PURE__ */ new Map();
@@ -6681,17 +6937,35 @@ function createAgentHandleStore() {
6681
6937
  }
6682
6938
  };
6683
6939
  }
6940
+ function createTaskHandleStore() {
6941
+ let currentFactory = null;
6942
+ return {
6943
+ register(factory) {
6944
+ currentFactory = factory;
6945
+ },
6946
+ get(ctx) {
6947
+ if (currentFactory === null) return void 0;
6948
+ return currentFactory(ctx);
6949
+ },
6950
+ clear() {
6951
+ currentFactory = null;
6952
+ }
6953
+ };
6954
+ }
6684
6955
  function createAppRegistries() {
6685
6956
  const tool = createToolRegistry();
6686
6957
  const agent = createAgentRegistry(tool);
6687
6958
  const skill = createSkillRegistry();
6959
+ const task = createTaskRegistry();
6688
6960
  const agentHandle = createAgentHandleStore();
6689
- return { tool, agent, skill, agentHandle };
6961
+ const taskHandle = createTaskHandleStore();
6962
+ return { tool, agent, skill, task, agentHandle, taskHandle };
6690
6963
  }
6691
6964
  var defaultRegistries;
6692
6965
  var init_registries = __esm({
6693
6966
  "src/injection/registries.ts"() {
6694
6967
  "use strict";
6968
+ init_taskRegistry();
6695
6969
  defaultRegistries = createAppRegistries();
6696
6970
  }
6697
6971
  });
@@ -6758,6 +7032,9 @@ function getBuiltinInjectionValue(type, ctx, body) {
6758
7032
  // Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle
6759
7033
  case "agent":
6760
7034
  return ctx.registries ? ctx.registries.agentHandle.get(ctx) : getAgentHandle(ctx);
7035
+ // 任务子系统:注入 TaskClient(入队/查询);未注册工厂(无 app 编排)时 undefined
7036
+ case "tasks":
7037
+ return ctx.registries ? ctx.registries.taskHandle.get(ctx) : void 0;
6761
7038
  default:
6762
7039
  return void 0;
6763
7040
  }
@@ -6974,9 +7251,9 @@ async function validateInput(schemaPath, method, inputType, input) {
6974
7251
  function mapZodIssues(error) {
6975
7252
  return error.issues.map((issue) => {
6976
7253
  const code = mapZodCode(issue);
6977
- const path27 = issue.path.map(String).join(".") || "";
7254
+ const path31 = issue.path.map(String).join(".") || "";
6978
7255
  return {
6979
- path: path27,
7256
+ path: path31,
6980
7257
  code,
6981
7258
  expected: issue.expected ?? mapExpectedFromMessage(issue.message),
6982
7259
  received: issue.received ?? mapReceivedFromMessage(issue.message),
@@ -7474,9 +7751,9 @@ var init_wsHandler = __esm({
7474
7751
  });
7475
7752
 
7476
7753
  // src/server/handleWsUpgrade.ts
7477
- import fs16 from "fs";
7754
+ import fs17 from "fs";
7478
7755
  import { WebSocketServer, WebSocket } from "ws";
7479
- import path21 from "path";
7756
+ import path23 from "path";
7480
7757
  function getPathname(req) {
7481
7758
  const url = req.url ?? "/";
7482
7759
  const idx = url.indexOf("?");
@@ -7487,7 +7764,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
7487
7764
  const dist = getDevDist();
7488
7765
  if (dist) {
7489
7766
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
7490
- if (sourcePath && fs16.existsSync(sourcePath)) {
7767
+ if (sourcePath && fs17.existsSync(sourcePath)) {
7491
7768
  await ensureCompiled(sourcePath, rootDir, dist);
7492
7769
  }
7493
7770
  }
@@ -7601,7 +7878,7 @@ function attachWebSocket(options) {
7601
7878
  const finalHandler = async () => {
7602
7879
  let handlers;
7603
7880
  try {
7604
- const absoluteFilePath = path21.resolve(rootDir, route.filePath);
7881
+ const absoluteFilePath = path23.resolve(rootDir, route.filePath);
7605
7882
  handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
7606
7883
  } catch (err) {
7607
7884
  const reason = err instanceof Error ? err.message : String(err);
@@ -7677,7 +7954,7 @@ import {
7677
7954
  import { createSecureServer as createHttp2SecureServer } from "http2";
7678
7955
  import { readFileSync } from "fs";
7679
7956
  import { Readable as Readable3 } from "stream";
7680
- import path22 from "path";
7957
+ import path24 from "path";
7681
7958
  function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT, requestSignal) {
7682
7959
  const forwardedProto = req.headers["x-forwarded-proto"];
7683
7960
  const protocol = Array.isArray(forwardedProto) ? forwardedProto[0]?.split(",")[0]?.trim() ?? "http" : forwardedProto?.split(",")[0]?.trim() ?? "http";
@@ -7873,7 +8150,7 @@ function getRoutePaths(route, rootDir, dist) {
7873
8150
  let cached = routePathCache.get(route);
7874
8151
  if (!cached) {
7875
8152
  cached = {
7876
- absFilePath: path22.resolve(rootDir, route.filePath),
8153
+ absFilePath: path24.resolve(rootDir, route.filePath),
7877
8154
  schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
7878
8155
  };
7879
8156
  routePathCache.set(route, cached);
@@ -8027,9 +8304,371 @@ var init_startServer = __esm({
8027
8304
  }
8028
8305
  });
8029
8306
 
8307
+ // src/task/memoryDriver.ts
8308
+ import { randomUUID } from "crypto";
8309
+ function createMemoryDriver() {
8310
+ const jobs = /* @__PURE__ */ new Map();
8311
+ const pendingByTask = /* @__PURE__ */ new Map();
8312
+ const runningCount = /* @__PURE__ */ new Map();
8313
+ const workers = /* @__PURE__ */ new Map();
8314
+ let stopped = false;
8315
+ function pump() {
8316
+ if (stopped) return;
8317
+ for (const [name, worker] of workers) {
8318
+ const queue = pendingByTask.get(name);
8319
+ if (!queue || queue.length === 0) continue;
8320
+ let running = runningCount.get(name) ?? 0;
8321
+ while (running < worker.concurrency && queue.length > 0) {
8322
+ const head = jobs.get(queue[0]);
8323
+ if (!head) {
8324
+ queue.shift();
8325
+ continue;
8326
+ }
8327
+ if (head.runAt > Date.now()) break;
8328
+ queue.shift();
8329
+ running += 1;
8330
+ runningCount.set(name, running);
8331
+ void dispatch(name, worker, head);
8332
+ }
8333
+ if (running === 0) runningCount.delete(name);
8334
+ else runningCount.set(name, running);
8335
+ }
8336
+ }
8337
+ async function dispatch(name, worker, job) {
8338
+ job.attempts += 1;
8339
+ const driverJob = {
8340
+ id: job.id,
8341
+ name,
8342
+ payload: job.payload,
8343
+ attempt: job.attempts,
8344
+ signal: job.controller.signal
8345
+ };
8346
+ try {
8347
+ await worker.process(driverJob);
8348
+ } catch (err) {
8349
+ if (job.attempts <= job.retries) {
8350
+ const delay = Math.min(RETRY_BASE_DELAY_MS * 2 ** (job.attempts - 1), RETRY_MAX_DELAY_MS);
8351
+ job.runAt = Date.now() + delay;
8352
+ job.timer = setTimeout(() => {
8353
+ job.timer = void 0;
8354
+ pendingByTask.get(name)?.push(job.id);
8355
+ pump();
8356
+ }, delay);
8357
+ }
8358
+ void err;
8359
+ } finally {
8360
+ const running = (runningCount.get(name) ?? 1) - 1;
8361
+ if (running <= 0) runningCount.delete(name);
8362
+ else runningCount.set(name, running);
8363
+ pump();
8364
+ }
8365
+ }
8366
+ return {
8367
+ async enqueue(name, payload, opts) {
8368
+ if (stopped) {
8369
+ throw new Error("[faapi] Task queue is stopped and no longer accepts jobs");
8370
+ }
8371
+ const id = randomUUID();
8372
+ const job = {
8373
+ id,
8374
+ payload,
8375
+ retries: opts?.retries ?? 0,
8376
+ runAt: opts?.delayMs ? Date.now() + opts.delayMs : Date.now(),
8377
+ attempts: 0,
8378
+ controller: new AbortController()
8379
+ };
8380
+ jobs.set(id, job);
8381
+ if (job.runAt > Date.now()) {
8382
+ job.timer = setTimeout(() => {
8383
+ job.timer = void 0;
8384
+ pendingByTask.get(name)?.push(id);
8385
+ pump();
8386
+ }, job.runAt - Date.now());
8387
+ } else {
8388
+ let q = pendingByTask.get(name);
8389
+ if (!q) {
8390
+ q = [];
8391
+ pendingByTask.set(name, q);
8392
+ }
8393
+ q.push(id);
8394
+ pump();
8395
+ }
8396
+ return id;
8397
+ },
8398
+ startWorker(name, opts) {
8399
+ workers.set(name, opts);
8400
+ pump();
8401
+ },
8402
+ async stop(timeoutMs = 1e4) {
8403
+ stopped = true;
8404
+ for (const job of jobs.values()) {
8405
+ if (job.timer) {
8406
+ clearTimeout(job.timer);
8407
+ job.timer = void 0;
8408
+ }
8409
+ }
8410
+ const start = Date.now();
8411
+ while (Date.now() - start < timeoutMs) {
8412
+ const total = Array.from(runningCount.values()).reduce((sum, n) => sum + n, 0);
8413
+ if (total === 0) return;
8414
+ await new Promise((r) => setTimeout(r, 10));
8415
+ }
8416
+ for (const job of jobs.values()) {
8417
+ job.controller.abort();
8418
+ }
8419
+ },
8420
+ async stopWorkers() {
8421
+ workers.clear();
8422
+ }
8423
+ };
8424
+ }
8425
+ var RETRY_BASE_DELAY_MS, RETRY_MAX_DELAY_MS;
8426
+ var init_memoryDriver = __esm({
8427
+ "src/task/memoryDriver.ts"() {
8428
+ "use strict";
8429
+ RETRY_BASE_DELAY_MS = 500;
8430
+ RETRY_MAX_DELAY_MS = 3e4;
8431
+ }
8432
+ });
8433
+
8434
+ // src/task/taskQueue.ts
8435
+ import path25 from "path";
8436
+ function createTaskQueue(deps) {
8437
+ const { registry, rootDir } = deps;
8438
+ const driver = deps.driver ?? createMemoryDriver();
8439
+ const records = /* @__PURE__ */ new Map();
8440
+ const moduleCache2 = /* @__PURE__ */ new Map();
8441
+ const schemaCache = /* @__PURE__ */ new Map();
8442
+ let started = false;
8443
+ let stopped = false;
8444
+ const loadTaskModule = deps.loadTaskModule ?? (async (filePath) => await import(filePath));
8445
+ const loadPayloadSchema = deps.loadPayloadSchema ?? (async (filePath) => {
8446
+ const zodPath = path25.join(path25.dirname(filePath), "zod.js");
8447
+ try {
8448
+ const mod = await import(zodPath);
8449
+ const schemaKey = Object.keys(mod).find((k) => k.endsWith("Schema"));
8450
+ return schemaKey ? mod[schemaKey] : void 0;
8451
+ } catch {
8452
+ return void 0;
8453
+ }
8454
+ });
8455
+ function assertKnownTask(name) {
8456
+ const meta = registry.get(name);
8457
+ if (!meta) {
8458
+ const known = registry.list().map((t) => t.name).join(", ") || "(none)";
8459
+ throw new Error(`[faapi] Unknown task "${name}". Registered tasks: ${known}`);
8460
+ }
8461
+ }
8462
+ async function validatePayload(name, payload) {
8463
+ if (!schemaCache.has(name)) {
8464
+ const meta = registry.get(name);
8465
+ schemaCache.set(
8466
+ name,
8467
+ await loadPayloadSchema(path25.resolve(rootDir, meta.filePath)).catch(() => void 0)
8468
+ );
8469
+ }
8470
+ const schema = schemaCache.get(name);
8471
+ if (!schema || typeof schema.safeParse !== "function") {
8472
+ return payload;
8473
+ }
8474
+ const result = schema.safeParse(payload);
8475
+ if (!result.success) {
8476
+ throw new ValidationError(
8477
+ `Task payload validation failed for "${name}": ${JSON.stringify(result.error)}`,
8478
+ []
8479
+ );
8480
+ }
8481
+ return result.data;
8482
+ }
8483
+ async function runJob(job) {
8484
+ const meta = registry.get(job.name);
8485
+ if (!meta) throw new Error(`[faapi] Unknown task "${job.name}" at worker dispatch`);
8486
+ const record = records.get(job.id) ?? {
8487
+ id: job.id,
8488
+ name: job.name,
8489
+ payload: job.payload,
8490
+ status: "pending",
8491
+ attempts: 0,
8492
+ createdAt: Date.now()
8493
+ };
8494
+ record.attempts = job.attempt;
8495
+ record.status = "running";
8496
+ record.error = void 0;
8497
+ records.set(job.id, record);
8498
+ try {
8499
+ let mod = moduleCache2.get(job.name);
8500
+ if (!mod) {
8501
+ mod = await loadTaskModule(path25.resolve(rootDir, meta.filePath));
8502
+ moduleCache2.set(job.name, mod);
8503
+ }
8504
+ if (typeof mod.run !== "function") {
8505
+ throw new Error(`Task "${job.name}" module has no run export`);
8506
+ }
8507
+ const taskCtx = {
8508
+ signal: job.signal,
8509
+ config: deps.config,
8510
+ job: { id: job.id, name: job.name, attempt: job.attempt }
8511
+ };
8512
+ const result = await mod.run(job.payload, taskCtx);
8513
+ record.status = "done";
8514
+ record.result = result;
8515
+ return result;
8516
+ } catch (err) {
8517
+ record.status = "failed";
8518
+ record.error = err instanceof Error ? err.message : String(err);
8519
+ throw err;
8520
+ }
8521
+ }
8522
+ async function startWorkers() {
8523
+ for (const meta of registry.list()) {
8524
+ await driver.startWorker(meta.name, {
8525
+ concurrency: meta.concurrency ?? 1,
8526
+ process: runJob
8527
+ });
8528
+ }
8529
+ }
8530
+ const queue = {
8531
+ async enqueue(name, payload = {}, opts) {
8532
+ if (stopped) {
8533
+ throw new Error("[faapi] Task queue is stopped and no longer accepts jobs");
8534
+ }
8535
+ assertKnownTask(name);
8536
+ const data = await validatePayload(name, payload);
8537
+ const meta = registry.get(name);
8538
+ const id = await driver.enqueue(name, data, {
8539
+ delayMs: opts?.delayMs,
8540
+ retries: meta.retries ?? 0
8541
+ });
8542
+ if (!records.has(id)) {
8543
+ records.set(id, {
8544
+ id,
8545
+ name,
8546
+ payload: data,
8547
+ status: "pending",
8548
+ attempts: 0,
8549
+ createdAt: Date.now(),
8550
+ ...opts?.delayMs ? { runAt: Date.now() + opts.delayMs } : {}
8551
+ });
8552
+ }
8553
+ return { id };
8554
+ },
8555
+ list(name) {
8556
+ const snapshot = [];
8557
+ for (const job of records.values()) {
8558
+ if (name !== void 0 && job.name !== name) continue;
8559
+ snapshot.push({ ...job });
8560
+ }
8561
+ return snapshot;
8562
+ },
8563
+ async start() {
8564
+ if (stopped) {
8565
+ throw new Error("[faapi] Task queue is stopped and cannot be restarted");
8566
+ }
8567
+ if (started) return;
8568
+ started = true;
8569
+ await startWorkers();
8570
+ },
8571
+ async stop(timeoutMs = 1e4) {
8572
+ stopped = true;
8573
+ await driver.stop(timeoutMs);
8574
+ },
8575
+ async reload() {
8576
+ await driver.stopWorkers?.();
8577
+ await startWorkers();
8578
+ },
8579
+ invalidateModules() {
8580
+ moduleCache2.clear();
8581
+ },
8582
+ invalidateSchemas() {
8583
+ schemaCache.clear();
8584
+ }
8585
+ };
8586
+ return queue;
8587
+ }
8588
+ var init_taskQueue = __esm({
8589
+ "src/task/taskQueue.ts"() {
8590
+ "use strict";
8591
+ init_httpErrors();
8592
+ init_memoryDriver();
8593
+ }
8594
+ });
8595
+
8596
+ // src/task/loadTaskDriver.ts
8597
+ async function loadTaskDriver(driver, driverOptions) {
8598
+ if (driver === void 0 || driver === "memory") {
8599
+ return createMemoryDriver();
8600
+ }
8601
+ if (typeof driver === "object") {
8602
+ return driver;
8603
+ }
8604
+ if (driver !== "pgboss" && driver !== "bullmq") {
8605
+ throw new Error(
8606
+ `[faapi] Unknown task driver "${driver}". Supported: 'memory' (default), 'pgboss', 'bullmq', or a TaskDriver instance.`
8607
+ );
8608
+ }
8609
+ const specifier = `@faapi/task-${driver}`;
8610
+ let mod;
8611
+ try {
8612
+ mod = await import(
8613
+ /* @vite-ignore */
8614
+ specifier
8615
+ );
8616
+ } catch {
8617
+ throw new Error(
8618
+ `[faapi] Task driver "${driver}" requires package "${specifier}" to be installed in your project.`
8619
+ );
8620
+ }
8621
+ const factoryName = driver === "pgboss" ? "createPgBossDriver" : "createBullMQDriver";
8622
+ const factory = mod[factoryName];
8623
+ if (typeof factory !== "function") {
8624
+ throw new Error(
8625
+ `[faapi] Package "${specifier}" does not export ${factoryName}() \u2014 check the package version.`
8626
+ );
8627
+ }
8628
+ return factory(driverOptions);
8629
+ }
8630
+ var init_loadTaskDriver = __esm({
8631
+ "src/task/loadTaskDriver.ts"() {
8632
+ "use strict";
8633
+ init_memoryDriver();
8634
+ }
8635
+ });
8636
+
8637
+ // src/task/cronScheduler.ts
8638
+ import { Cron } from "croner";
8639
+ function createCronScheduler(registry, enqueue) {
8640
+ let schedules = [];
8641
+ return {
8642
+ start() {
8643
+ this.stop();
8644
+ for (const task of registry.list()) {
8645
+ if (!task.cron) continue;
8646
+ schedules.push(
8647
+ new Cron(task.cron, () => {
8648
+ void Promise.resolve().then(() => enqueue(task.name)).catch((err) => {
8649
+ console.error(`[faapi] Cron enqueue failed for task "${task.name}":`, err);
8650
+ });
8651
+ })
8652
+ );
8653
+ }
8654
+ },
8655
+ stop() {
8656
+ for (const schedule of schedules) {
8657
+ schedule.stop();
8658
+ }
8659
+ schedules = [];
8660
+ }
8661
+ };
8662
+ }
8663
+ var init_cronScheduler = __esm({
8664
+ "src/task/cronScheduler.ts"() {
8665
+ "use strict";
8666
+ }
8667
+ });
8668
+
8030
8669
  // src/cli/loadPlugins.ts
8031
- import path23 from "path";
8032
- import fs17 from "fs";
8670
+ import path26 from "path";
8671
+ import fs18 from "fs";
8033
8672
  import { pathToFileURL as pathToFileURL2 } from "url";
8034
8673
  async function loadPlugins(declarations, ctx, rootDir, dist) {
8035
8674
  const handlerWrappers = [];
@@ -8092,37 +8731,37 @@ async function loadPlugins(declarations, ctx, rootDir, dist) {
8092
8731
  return { handlerWrappers, upgradeWrappers, failures };
8093
8732
  }
8094
8733
  function resolveLocalPluginSource(specifier, baseDir) {
8095
- const base = path23.isAbsolute(specifier) ? specifier : path23.resolve(baseDir, specifier);
8096
- if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs17.existsSync(base)) return base;
8734
+ const base = path26.isAbsolute(specifier) ? specifier : path26.resolve(baseDir, specifier);
8735
+ if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs18.existsSync(base)) return base;
8097
8736
  for (const ext of [".ts", ".js"]) {
8098
- if (fs17.existsSync(base + ext)) return base + ext;
8737
+ if (fs18.existsSync(base + ext)) return base + ext;
8099
8738
  }
8100
8739
  for (const indexExt of ["/index.ts", "/index.js"]) {
8101
- if (fs17.existsSync(base + indexExt)) return base + indexExt;
8740
+ if (fs18.existsSync(base + indexExt)) return base + indexExt;
8102
8741
  }
8103
8742
  return null;
8104
8743
  }
8105
8744
  function mirrorProductPath(sourcePath, baseDir, distDir) {
8106
- let rel = path23.relative(baseDir, sourcePath).replace(/\\/g, "/");
8745
+ let rel = path26.relative(baseDir, sourcePath).replace(/\\/g, "/");
8107
8746
  if (rel.startsWith("src/")) rel = rel.slice(4);
8108
- return path23.resolve(distDir, rel.replace(TS_EXTS, ".js"));
8747
+ return path26.resolve(distDir, rel.replace(TS_EXTS, ".js"));
8109
8748
  }
8110
8749
  function isSourceNewer(sourcePath, productPath) {
8111
8750
  try {
8112
- return fs17.statSync(sourcePath).mtimeMs > fs17.statSync(productPath).mtimeMs;
8751
+ return fs18.statSync(sourcePath).mtimeMs > fs18.statSync(productPath).mtimeMs;
8113
8752
  } catch {
8114
8753
  return true;
8115
8754
  }
8116
8755
  }
8117
8756
  async function importPluginModule(specifier, baseDir, dist) {
8118
8757
  const isRelative = specifier.startsWith("./") || specifier.startsWith("../");
8119
- if (!isRelative && !path23.isAbsolute(specifier)) {
8758
+ if (!isRelative && !path26.isAbsolute(specifier)) {
8120
8759
  return import(specifier);
8121
8760
  }
8122
- const distDir = dist ? path23.resolve(baseDir, dist) : null;
8761
+ const distDir = dist ? path26.resolve(baseDir, dist) : null;
8123
8762
  const sourcePath = resolveLocalPluginSource(specifier, baseDir);
8124
8763
  const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
8125
- if (productPath !== null && fs17.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
8764
+ if (productPath !== null && fs18.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
8126
8765
  return import(pathToFileURL2(productPath).href);
8127
8766
  }
8128
8767
  if (sourcePath && TS_EXTS.test(sourcePath)) {
@@ -8131,13 +8770,13 @@ async function importPluginModule(specifier, baseDir, dist) {
8131
8770
  `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.`
8132
8771
  );
8133
8772
  }
8134
- const relFromRoot = path23.relative(baseDir, sourcePath).replace(/\\/g, "/");
8773
+ const relFromRoot = path26.relative(baseDir, sourcePath).replace(/\\/g, "/");
8135
8774
  if (relFromRoot.startsWith("src/")) {
8136
8775
  await ensureCompiled(sourcePath, baseDir, dist);
8137
8776
  } else {
8138
8777
  await compileProjectModules([sourcePath], baseDir, dist);
8139
8778
  }
8140
- if (productPath && fs17.existsSync(productPath)) {
8779
+ if (productPath && fs18.existsSync(productPath)) {
8141
8780
  return import(pathToFileURL2(productPath).href);
8142
8781
  }
8143
8782
  }
@@ -8149,7 +8788,7 @@ async function importPluginModule(specifier, baseDir, dist) {
8149
8788
  `${specifier}/index.ts`,
8150
8789
  `${specifier}.js`,
8151
8790
  `${specifier}/index.js`
8152
- ].map((c) => path23.isAbsolute(c) ? c : path23.join(baseDir, c));
8791
+ ].map((c) => path26.isAbsolute(c) ? c : path26.join(baseDir, c));
8153
8792
  throw new Error(
8154
8793
  `Cannot find local plugin "${specifier}" (resolved from ${baseDir}). Expected one of:
8155
8794
  ${candidates.join("\n ")}
@@ -8184,12 +8823,12 @@ var init_loadPlugins = __esm({
8184
8823
  });
8185
8824
 
8186
8825
  // src/cli/createAppCore.ts
8187
- import fs18 from "fs";
8188
- import path24 from "path";
8826
+ import fs19 from "fs";
8827
+ import path27 from "path";
8189
8828
  import { PassThrough, Readable as Readable4 } from "stream";
8190
8829
  async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries) {
8191
- const toolsPath = path24.resolve(rootDir, dist, TOOLS_FILE2);
8192
- if (!fs18.existsSync(toolsPath)) {
8830
+ const toolsPath = path27.resolve(rootDir, dist, TOOLS_FILE2);
8831
+ if (!fs19.existsSync(toolsPath)) {
8193
8832
  return [];
8194
8833
  }
8195
8834
  const serialized = await importWithCacheBust(toolsPath);
@@ -8198,8 +8837,8 @@ async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries
8198
8837
  return hydrated;
8199
8838
  }
8200
8839
  async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistries) {
8201
- const agentsPath = path24.resolve(rootDir, dist, AGENTS_FILE2);
8202
- if (!fs18.existsSync(agentsPath)) {
8840
+ const agentsPath = path27.resolve(rootDir, dist, AGENTS_FILE2);
8841
+ if (!fs19.existsSync(agentsPath)) {
8203
8842
  return [];
8204
8843
  }
8205
8844
  const serialized = await importWithCacheBust(agentsPath);
@@ -8207,6 +8846,24 @@ async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistrie
8207
8846
  registries.agent.hydrate(hydrated);
8208
8847
  return hydrated;
8209
8848
  }
8849
+ function getTaskDriverOptions(config) {
8850
+ if (!config?.task) return void 0;
8851
+ const taskConfig = config.task;
8852
+ const driver = taskConfig.driver;
8853
+ if (driver === "pgboss") return taskConfig.pgboss;
8854
+ if (driver === "bullmq") return taskConfig.bullmq;
8855
+ return void 0;
8856
+ }
8857
+ async function loadAndHydrateTasks(rootDir, dist, registries = defaultRegistries) {
8858
+ const tasksPath = path27.resolve(rootDir, dist, TASKS_FILE);
8859
+ if (!fs19.existsSync(tasksPath)) {
8860
+ return [];
8861
+ }
8862
+ const serialized = await importWithCacheBust(tasksPath);
8863
+ const hydrated = hydrateTasks(serialized.tasks ?? []);
8864
+ registries.task.hydrate(hydrated);
8865
+ return hydrated;
8866
+ }
8210
8867
  function getCurrentApp() {
8211
8868
  return globalThis[APP_INSTANCE_KEY] ?? null;
8212
8869
  }
@@ -8239,8 +8896,8 @@ function isFaapiConfigKey(key) {
8239
8896
  async function createAppBase(options) {
8240
8897
  const rootDir = options?.rootDir ?? process.cwd();
8241
8898
  const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
8242
- const routesPath = path24.resolve(rootDir, dist, ROUTES_FILE);
8243
- if (!fs18.existsSync(routesPath)) {
8899
+ const routesPath = path27.resolve(rootDir, dist, ROUTES_FILE);
8900
+ if (!fs19.existsSync(routesPath)) {
8244
8901
  throw new Error(
8245
8902
  `[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
8246
8903
  );
@@ -8262,6 +8919,28 @@ async function createAppBase(options) {
8262
8919
  const registries = createAppRegistries();
8263
8920
  const tools = await loadAndHydrateTools(rootDir, dist, registries);
8264
8921
  const agents = await loadAndHydrateAgents(rootDir, dist, registries);
8922
+ const taskMetas = await loadAndHydrateTasks(rootDir, dist, registries);
8923
+ const taskDriver = await loadTaskDriver(config?.task?.driver, getTaskDriverOptions(config));
8924
+ const taskQueue = createTaskQueue({
8925
+ registry: registries.task,
8926
+ rootDir,
8927
+ config,
8928
+ driver: taskDriver
8929
+ });
8930
+ const cronScheduler = createCronScheduler(
8931
+ registries.task,
8932
+ (name) => taskQueue.enqueue(name)
8933
+ );
8934
+ const taskEnabled = process.env.FAAPI_TASKS_DISABLED === "1" ? false : config?.task?.enabled ?? true;
8935
+ const stopTaskRuntime = async () => {
8936
+ cronScheduler.stop();
8937
+ await taskQueue.stop(config?.task?.shutdownTimeoutMs ?? 1e4);
8938
+ };
8939
+ if (taskEnabled) {
8940
+ taskQueue.start();
8941
+ cronScheduler.start();
8942
+ }
8943
+ registries.taskHandle.register(() => taskQueue);
8265
8944
  const pluginConfig = config ? Object.fromEntries(Object.entries(config).filter(([k]) => !isFaapiConfigKey(k))) : {};
8266
8945
  const { server, routesRef } = createServer({
8267
8946
  routes: sorted,
@@ -8303,14 +8982,22 @@ async function createAppBase(options) {
8303
8982
  routes: sorted,
8304
8983
  wsRoutes,
8305
8984
  rootDir,
8985
+ tasks: taskQueue,
8306
8986
  async listen(listenPort) {
8307
8987
  const envPort = process.env.PORT ? Number(process.env.PORT) : void 0;
8308
8988
  const actualPort = listenPort ?? options?.port ?? envPort ?? DEFAULT_PORT;
8309
8989
  if (config?.lifecycle?.onBoot) {
8310
8990
  try {
8311
- await config.lifecycle.onBoot({ rootDir, routes: sorted, server, registries });
8991
+ await config.lifecycle.onBoot({
8992
+ rootDir,
8993
+ routes: sorted,
8994
+ server,
8995
+ registries,
8996
+ tasks: taskQueue
8997
+ });
8312
8998
  console.log("- onBoot hook executed");
8313
8999
  } catch (err) {
9000
+ await stopTaskRuntime();
8314
9001
  const message = err instanceof Error ? err.message : String(err);
8315
9002
  console.error(`[faapi] onBoot hook failed: ${message}`);
8316
9003
  throw err;
@@ -8358,9 +9045,22 @@ async function createAppBase(options) {
8358
9045
  console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
8359
9046
  }
8360
9047
  }
9048
+ if (taskMetas.length > 0) {
9049
+ console.log(`- Loaded ${taskMetas.length} task(s):`);
9050
+ for (const taskMeta of taskMetas) {
9051
+ const schedule = taskMeta.cron ? ` cron=${taskMeta.cron}` : "";
9052
+ console.log(` ${taskMeta.name}${schedule} ${taskMeta.filePath}`);
9053
+ }
9054
+ }
8361
9055
  registerDefaultShutdownHandlers();
8362
9056
  if (config?.lifecycle?.onReady) {
8363
- await config.lifecycle.onReady({ rootDir, routes: sorted, server, registries });
9057
+ await config.lifecycle.onReady({
9058
+ rootDir,
9059
+ routes: sorted,
9060
+ server,
9061
+ registries,
9062
+ tasks: taskQueue
9063
+ });
8364
9064
  console.log("- onReady hook executed");
8365
9065
  }
8366
9066
  app.server = server;
@@ -8453,13 +9153,22 @@ async function createAppBase(options) {
8453
9153
  closed = true;
8454
9154
  const s = server;
8455
9155
  s.closeIdleConnections?.();
9156
+ await stopTaskRuntime();
8456
9157
  if (config?.lifecycle?.onClose) {
8457
- await config.lifecycle.onClose({ rootDir, routes: sorted, server, registries });
9158
+ await config.lifecycle.onClose({
9159
+ rootDir,
9160
+ routes: sorted,
9161
+ server,
9162
+ registries,
9163
+ tasks: taskQueue
9164
+ });
8458
9165
  }
8459
9166
  registries.tool.clear();
8460
9167
  registries.agent.clear();
8461
9168
  registries.skill.clear();
9169
+ registries.task.clear();
8462
9170
  registries.agentHandle.clear();
9171
+ registries.taskHandle.clear();
8463
9172
  if (!server.listening) {
8464
9173
  app.server = null;
8465
9174
  if (getCurrentApp() === app) setCurrentApp(null);
@@ -8496,6 +9205,7 @@ async function createAppBase(options) {
8496
9205
  registries,
8497
9206
  dist,
8498
9207
  patterns: ROUTE_PATTERNS,
9208
+ taskQueue,
8499
9209
  server,
8500
9210
  routesRef,
8501
9211
  config,
@@ -8522,6 +9232,10 @@ var init_createAppCore = __esm({
8522
9232
  init_generateRoutes();
8523
9233
  init_generateToolArtifacts();
8524
9234
  init_generateAgentArtifacts();
9235
+ init_generateTaskArtifacts();
9236
+ init_taskQueue();
9237
+ init_loadTaskDriver();
9238
+ init_cronScheduler();
8525
9239
  init_loadPlugins();
8526
9240
  init_importWithCacheBust();
8527
9241
  init_registries();
@@ -8547,12 +9261,14 @@ var init_createAppCore = __esm({
8547
9261
  "logger",
8548
9262
  "http2",
8549
9263
  "trustedProxy",
8550
- "response"
9264
+ "response",
9265
+ "task"
8551
9266
  ]);
8552
9267
  }
8553
9268
  });
8554
9269
 
8555
9270
  // src/cli/createDevApp.ts
9271
+ import path28 from "path";
8556
9272
  async function createDevApp(options) {
8557
9273
  const { app, ctx } = await createAppBase(options);
8558
9274
  const devApp = app;
@@ -8597,6 +9313,19 @@ async function createDevApp(options) {
8597
9313
  await generateAgentArtifacts(agents, ctx.rootDir, ctx.dist);
8598
9314
  await loadAndHydrateAgents(ctx.rootDir, ctx.dist, ctx.registries);
8599
9315
  };
9316
+ devApp.reloadTasks = async () => {
9317
+ setLoadTimestamp(Date.now());
9318
+ invalidateProgramCache();
9319
+ const tasks = await scanTasks(ctx.rootDir, TASK_PATTERNS);
9320
+ for (const task of tasks) {
9321
+ await ensureCompiled(path28.resolve(ctx.rootDir, task.filePath), ctx.rootDir, ctx.dist);
9322
+ }
9323
+ await generateTaskArtifacts(tasks, ctx.rootDir, ctx.dist);
9324
+ ctx.taskQueue.invalidateModules();
9325
+ ctx.taskQueue.invalidateSchemas();
9326
+ await ctx.taskQueue.reload();
9327
+ await loadAndHydrateTasks(ctx.rootDir, ctx.dist, ctx.registries);
9328
+ };
8600
9329
  return devApp;
8601
9330
  }
8602
9331
  var init_createDevApp = __esm({
@@ -8611,6 +9340,9 @@ var init_createDevApp = __esm({
8611
9340
  init_scanAgents();
8612
9341
  init_scanAgents();
8613
9342
  init_generateAgentArtifacts();
9343
+ init_scanTasks();
9344
+ init_generateTaskArtifacts();
9345
+ init_compileOnDemand();
8614
9346
  init_loadMiddlewares();
8615
9347
  init_createProgram();
8616
9348
  init_validateInput();
@@ -8625,9 +9357,10 @@ __export(devCommand_exports, {
8625
9357
  devCommand: () => devCommand,
8626
9358
  generateAgentArtifactsForDev: () => generateAgentArtifactsForDev,
8627
9359
  generateRouteArtifacts: () => generateRouteArtifacts,
9360
+ generateTaskArtifactsForDev: () => generateTaskArtifactsForDev,
8628
9361
  generateToolArtifactsForDev: () => generateToolArtifactsForDev
8629
9362
  });
8630
- import path25 from "path";
9363
+ import path29 from "path";
8631
9364
  async function devCommand(options) {
8632
9365
  const rootDir = process.cwd();
8633
9366
  if (!process.env.NODE_ENV) process.env.NODE_ENV = "development";
@@ -8646,6 +9379,8 @@ async function devCommand(options) {
8646
9379
  await generateToolArtifactsForDev(rootDir, devDist);
8647
9380
  console.log("- Generating agent manifest...");
8648
9381
  await generateAgentArtifactsForDev(rootDir, devDist);
9382
+ console.log("- Generating task artifacts...");
9383
+ await generateTaskArtifactsForDev(rootDir, devDist);
8649
9384
  console.log("- Starting dev app...");
8650
9385
  const app = await createDevApp({ rootDir, port: options?.port });
8651
9386
  await app.listen();
@@ -8654,7 +9389,7 @@ async function devCommand(options) {
8654
9389
  async function generateRouteArtifacts(rootDir, patterns, dist) {
8655
9390
  const { routes, wsRoutes } = await scanRoutes(rootDir, patterns, dist);
8656
9391
  const sorted = sortRoutes(routes);
8657
- const routesPath = path25.resolve(rootDir, dist, ROUTES_FILE2);
9392
+ const routesPath = path29.resolve(rootDir, dist, ROUTES_FILE2);
8658
9393
  const serialized = serializeRoutes(sorted, wsRoutes, rootDir, dist);
8659
9394
  await writeRoutesModule(serialized, routesPath);
8660
9395
  }
@@ -8666,6 +9401,13 @@ async function generateAgentArtifactsForDev(rootDir, dist) {
8666
9401
  const agents = await scanAgents(rootDir, DEFAULT_AGENT_PATTERNS);
8667
9402
  await generateAgentArtifacts(agents, rootDir, dist);
8668
9403
  }
9404
+ async function generateTaskArtifactsForDev(rootDir, dist) {
9405
+ const tasks = await scanTasks(rootDir, TASK_PATTERNS);
9406
+ for (const task of tasks) {
9407
+ await ensureCompiled(path29.resolve(rootDir, task.filePath), rootDir, dist);
9408
+ }
9409
+ await generateTaskArtifacts(tasks, rootDir, dist);
9410
+ }
8669
9411
  var DEV_DIST, ROUTES_FILE2;
8670
9412
  var init_devCommand = __esm({
8671
9413
  "src/cli/devCommand.ts"() {
@@ -8680,6 +9422,9 @@ var init_devCommand = __esm({
8680
9422
  init_scanAgents();
8681
9423
  init_scanAgents();
8682
9424
  init_generateAgentArtifacts();
9425
+ init_scanTasks();
9426
+ init_generateTaskArtifacts();
9427
+ init_compileOnDemand();
8683
9428
  init_loadConfig();
8684
9429
  init_loadEnv();
8685
9430
  init_watcher();
@@ -8707,15 +9452,15 @@ var buildCommand_exports = {};
8707
9452
  __export(buildCommand_exports, {
8708
9453
  buildCommand: () => buildCommand
8709
9454
  });
8710
- import path26 from "path";
8711
- import fs19 from "fs";
9455
+ import path30 from "path";
9456
+ import fs20 from "fs";
8712
9457
  async function buildCommand(options) {
8713
9458
  const rootDir = options?.rootDir ?? process.cwd();
8714
9459
  const outdir = options?.dist ?? DEFAULT_DIST2;
8715
- const absOut = path26.resolve(rootDir, outdir);
8716
- const realRoot = toRealPath(path26.resolve(rootDir));
9460
+ const absOut = path30.resolve(rootDir, outdir);
9461
+ const realRoot = toRealPath(path30.resolve(rootDir));
8717
9462
  if (isInsideDir(toRealPath(absOut), realRoot)) {
8718
- fs19.rmSync(absOut, { recursive: true, force: true });
9463
+ fs20.rmSync(absOut, { recursive: true, force: true });
8719
9464
  } else {
8720
9465
  console.warn(
8721
9466
  `! Output directory "${outdir}" is outside the project root, skipping clean (stale artifacts may remain)`
@@ -8723,10 +9468,10 @@ async function buildCommand(options) {
8723
9468
  }
8724
9469
  await compileConfig({ rootDir, dist: outdir });
8725
9470
  const _config = await loadConfig(rootDir, outdir);
8726
- const pkgPath = path26.resolve(rootDir, "package.json");
8727
- if (fs19.existsSync(pkgPath)) {
9471
+ const pkgPath = path30.resolve(rootDir, "package.json");
9472
+ if (fs20.existsSync(pkgPath)) {
8728
9473
  try {
8729
- const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf-8"));
9474
+ const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
8730
9475
  if (pkg.type !== "module") {
8731
9476
  console.warn(
8732
9477
  '! package.json is missing "type": "module" \u2014 build output is ESM and `node dist/main` will fail without it'
@@ -8754,7 +9499,7 @@ async function buildCommand(options) {
8754
9499
  if (localPluginSources.length > 0) {
8755
9500
  console.log("\n[2.5/8] Compiling local plugins...");
8756
9501
  const outside = localPluginSources.filter((p) => {
8757
- const rel = path26.relative(rootDir, p).replace(/\\/g, "/");
9502
+ const rel = path30.relative(rootDir, p).replace(/\\/g, "/");
8758
9503
  return !rel.startsWith("src/");
8759
9504
  });
8760
9505
  if (outside.length > 0) {
@@ -8778,9 +9523,9 @@ async function buildCommand(options) {
8778
9523
  }
8779
9524
  console.log("\n[4/8] Generating schema...");
8780
9525
  await generateSchemaFiles(sorted, rootDir, outdir);
8781
- console.log(` Schema: zod.js files under ${path26.resolve(rootDir, outdir)}`);
9526
+ console.log(` Schema: zod.js files under ${path30.resolve(rootDir, outdir)}`);
8782
9527
  console.log("\n[5/8] Generating routes manifest...");
8783
- const routesPath = path26.resolve(rootDir, outdir, "faapi-routes.js");
9528
+ const routesPath = path30.resolve(rootDir, outdir, "faapi-routes.js");
8784
9529
  const serialized = serializeRoutes(sorted, wsRoutes, rootDir, outdir);
8785
9530
  await writeRoutesModule(serialized, routesPath);
8786
9531
  console.log(` Written to ${routesPath}`);
@@ -8788,14 +9533,19 @@ async function buildCommand(options) {
8788
9533
  const tools = await scanTools(rootDir, TOOL_PATTERNS);
8789
9534
  const toolMeta = await generateToolArtifacts(tools, rootDir, outdir);
8790
9535
  console.log(` Found ${toolMeta.length} tool(s)`);
8791
- console.log(` Tool manifest: ${path26.resolve(rootDir, outdir, "faapi-tools.js")}`);
9536
+ console.log(` Tool manifest: ${path30.resolve(rootDir, outdir, "faapi-tools.js")}`);
8792
9537
  console.log("\n[7/8] Generating agent manifest...");
8793
9538
  const agents = await scanAgents(rootDir, DEFAULT_AGENT_PATTERNS);
8794
9539
  const agentMeta = await generateAgentArtifacts(agents, rootDir, outdir);
8795
9540
  console.log(` Found ${agentMeta.length} agent(s)`);
8796
- console.log(` Agent manifest: ${path26.resolve(rootDir, outdir, "faapi-agents.js")}`);
9541
+ console.log(` Agent manifest: ${path30.resolve(rootDir, outdir, "faapi-agents.js")}`);
9542
+ console.log("\n[7.5/8] Generating task manifest and schema...");
9543
+ const tasks = await scanTasks(rootDir, TASK_PATTERNS);
9544
+ const taskMeta = await generateTaskArtifacts(tasks, rootDir, outdir);
9545
+ console.log(` Found ${taskMeta.length} task(s)`);
9546
+ console.log(` Task manifest: ${path30.resolve(rootDir, outdir, "faapi-tasks.js")}`);
8797
9547
  console.log("\n[8/8] Generating entry file...");
8798
- const mainPath = path26.resolve(rootDir, outdir, "main.js");
9548
+ const mainPath = path30.resolve(rootDir, outdir, "main.js");
8799
9549
  const createProdAppArgs = options?.dist && options.dist !== DEFAULT_DIST2 ? `{ dist: ${JSON.stringify(outdir)} }` : "";
8800
9550
  const mainContent = `// \u7531 faapi build \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91
8801
9551
  import { createProdApp, loadEnv } from '@faapi/faapi';
@@ -8807,7 +9557,7 @@ loadEnv(process.cwd());
8807
9557
  const app = await createProdApp(${createProdAppArgs});
8808
9558
  await app.listen();
8809
9559
  `;
8810
- await fs19.promises.writeFile(mainPath, mainContent, "utf-8");
9560
+ await fs20.promises.writeFile(mainPath, mainContent, "utf-8");
8811
9561
  console.log(` Written to ${mainPath}`);
8812
9562
  console.log("\nfaapi build completed");
8813
9563
  }
@@ -8821,7 +9571,7 @@ function extractLocalPluginSources(declarations, rootDir) {
8821
9571
  else if (Array.isArray(decl)) specifier = decl[0];
8822
9572
  else if ("path" in decl) specifier = decl.path;
8823
9573
  if (!specifier) continue;
8824
- if (!specifier.startsWith("./") && !specifier.startsWith("../") && !path26.isAbsolute(specifier)) {
9574
+ if (!specifier.startsWith("./") && !specifier.startsWith("../") && !path30.isAbsolute(specifier)) {
8825
9575
  continue;
8826
9576
  }
8827
9577
  const source = resolveLocalPluginSource(specifier, rootDir);
@@ -8845,6 +9595,8 @@ var init_buildCommand = __esm({
8845
9595
  init_scanAgents();
8846
9596
  init_scanAgents();
8847
9597
  init_generateAgentArtifacts();
9598
+ init_scanTasks();
9599
+ init_generateTaskArtifacts();
8848
9600
  init_generateSchemaFiles();
8849
9601
  init_generateRoutes();
8850
9602
  init_compileBuildRoutes();