@faapi/faapi 6.2.0 → 6.4.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,503 @@ var init_generateAgentArtifacts = __esm({
3370
3372
  }
3371
3373
  });
3372
3374
 
3373
- // src/config/loadConfig.ts
3375
+ // src/task/scanTasks.ts
3376
+ import fg4 from "fast-glob";
3374
3377
  import path16 from "path";
3375
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
+ const timeoutMs = TIMEOUT_MS_RE.exec(source)?.[1];
3416
+ if (cron !== void 0) manifest.cron = cron;
3417
+ if (concurrency !== void 0) manifest.concurrency = Number(concurrency);
3418
+ if (retries !== void 0) manifest.retries = Number(retries);
3419
+ if (timeoutMs !== void 0) manifest.timeoutMs = Number(timeoutMs);
3420
+ tasks.push(manifest);
3421
+ }
3422
+ return tasks;
3423
+ }
3424
+ var TASK_PATTERNS, TASK_FILENAME, CRON_RE, CONCURRENCY_RE, RETRIES_RE, TIMEOUT_MS_RE;
3425
+ var init_scanTasks = __esm({
3426
+ "src/task/scanTasks.ts"() {
3427
+ "use strict";
3428
+ TASK_PATTERNS = ["src/tasks/**/task.ts"];
3429
+ TASK_FILENAME = "task.ts";
3430
+ CRON_RE = /(?:^|\n)\s*cron:\s*['"`]([^'"`\n]+)['"`]/;
3431
+ CONCURRENCY_RE = /(?:^|\n)\s*concurrency:\s*(\d+)/;
3432
+ RETRIES_RE = /(?:^|\n)\s*retries:\s*(\d+)/;
3433
+ TIMEOUT_MS_RE = /(?:^|\n)\s*timeoutMs:\s*(\d+)/;
3434
+ }
3435
+ });
3436
+
3437
+ // src/cli/generateTaskArtifacts.ts
3438
+ import path17 from "path";
3439
+ function serializeTasks(tasks, dist = "dist") {
3440
+ return tasks.map((t) => ({
3441
+ name: t.name,
3442
+ filePath: toProdFilePath(t.filePath, dist),
3443
+ ...t.cron !== void 0 ? { cron: t.cron } : {},
3444
+ ...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
3445
+ ...t.retries !== void 0 ? { retries: t.retries } : {},
3446
+ ...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {}
3447
+ }));
3448
+ }
3449
+ async function writeTasksModule(manifest, outputPath) {
3450
+ const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
3451
+ export const tasks = ${JSON.stringify(manifest, null, 2)};
3452
+ `;
3453
+ await atomicWriteFile(outputPath, content);
3454
+ }
3455
+ function hydrateTasks(manifest) {
3456
+ return manifest.map((t) => ({
3457
+ name: t.name,
3458
+ filePath: t.filePath,
3459
+ cron: t.cron ?? void 0,
3460
+ concurrency: t.concurrency ?? void 0,
3461
+ retries: t.retries ?? void 0,
3462
+ timeoutMs: t.timeoutMs ?? void 0
3463
+ }));
3464
+ }
3465
+ function collectTaskSchemaSources(tasks, rootDir) {
3466
+ const tasksByFile = /* @__PURE__ */ new Map();
3467
+ for (const task of tasks) {
3468
+ if (!task.inputTypeName) continue;
3469
+ const absPath = path17.resolve(rootDir, task.filePath);
3470
+ let list = tasksByFile.get(absPath);
3471
+ if (!list) {
3472
+ list = [];
3473
+ tasksByFile.set(absPath, list);
3474
+ }
3475
+ list.push(task);
3476
+ }
3477
+ const programByFile = createPrograms([...tasksByFile.keys()]);
3478
+ const resolversByFile = /* @__PURE__ */ new Map();
3479
+ for (const filePath of tasksByFile.keys()) {
3480
+ resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath), filePath));
3481
+ }
3482
+ const sources = [];
3483
+ for (const [filePath, fileTasks] of tasksByFile) {
3484
+ const program = programByFile.get(filePath);
3485
+ for (const task of fileTasks) {
3486
+ const typeInfo = extractTypeInfo(program, filePath, task.inputTypeName);
3487
+ sources.push({
3488
+ name: task.taskName,
3489
+ filePath,
3490
+ schemaName: task.inputTypeName,
3491
+ typeInfo
3492
+ });
3493
+ }
3494
+ }
3495
+ return { sources, resolversByFile };
3496
+ }
3497
+ function generateTaskSchemaFileSource(sources, resolveType, helpersImportPath) {
3498
+ const lines = ["import { z } from 'zod';"];
3499
+ const schemaBlocks = [];
3500
+ for (const source of sources) {
3501
+ if (!source.typeInfo) continue;
3502
+ const block = [`// task ${source.name} \u2192 ${source.schemaName}`];
3503
+ const schemaCode = generateZodSchemaSource(
3504
+ source.typeInfo,
3505
+ resolveType,
3506
+ source.schemaName,
3507
+ false
3508
+ ).replace(/^import \{ z \} from 'zod';\s*\n\s*\n/, "");
3509
+ block.push(schemaCode);
3510
+ block.push("");
3511
+ schemaBlocks.push(block.join("\n"));
3512
+ }
3513
+ const allSchemaCode = schemaBlocks.join("\n");
3514
+ if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
3515
+ lines.push(
3516
+ `import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
3517
+ );
3518
+ }
3519
+ lines.push("");
3520
+ lines.push(...schemaBlocks);
3521
+ return lines.join("\n").replace(/\n+$/, "\n");
3522
+ }
3523
+ async function generateTaskArtifacts(manifests, rootDir, dist) {
3524
+ const metadata = [];
3525
+ if (manifests.length > 0) {
3526
+ const programByFile = createPrograms(manifests.map((m) => path17.resolve(rootDir, m.filePath)));
3527
+ for (const manifest of manifests) {
3528
+ const absPath = path17.resolve(rootDir, manifest.filePath);
3529
+ const result = extractToolMetadata(programByFile.get(absPath), absPath, "run", {
3530
+ name: manifest.name,
3531
+ filePath: manifest.filePath
3532
+ });
3533
+ if (result) {
3534
+ metadata.push({ ...result, taskName: manifest.name });
3535
+ }
3536
+ }
3537
+ }
3538
+ const serialized = serializeTasks(manifests, dist);
3539
+ const tasksPath = path17.resolve(rootDir, dist, TASKS_FILE);
3540
+ await writeTasksModule(serialized, tasksPath);
3541
+ if (metadata.length === 0) {
3542
+ return hydrateTasks(serialized);
3543
+ }
3544
+ const { sources, resolversByFile } = collectTaskSchemaSources(metadata, rootDir);
3545
+ if (sources.length === 0) {
3546
+ return hydrateTasks(serialized);
3547
+ }
3548
+ const sourcesByFile = /* @__PURE__ */ new Map();
3549
+ for (const source of sources) {
3550
+ let list = sourcesByFile.get(source.filePath);
3551
+ if (!list) {
3552
+ list = [];
3553
+ sourcesByFile.set(source.filePath, list);
3554
+ }
3555
+ list.push(source);
3556
+ }
3557
+ const fileEntries = [];
3558
+ for (const [filePath, fileSources] of sourcesByFile) {
3559
+ const relFile = path17.relative(rootDir, filePath).replace(/\\/g, "/");
3560
+ const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
3561
+ const resolver = resolversByFile.get(filePath);
3562
+ let relForDir = relFile;
3563
+ if (relForDir.startsWith("src/")) {
3564
+ relForDir = relForDir.slice(4);
3565
+ }
3566
+ const dirIdx = relForDir.lastIndexOf("/");
3567
+ const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
3568
+ const helpersImportPath = getHelpersImportPath(zodRelDir);
3569
+ const source = generateTaskSchemaFileSource(
3570
+ fileSources,
3571
+ (name) => resolver?.resolve(name)?.runtimeType,
3572
+ helpersImportPath
3573
+ );
3574
+ fileEntries.push({ outputPath, source });
3575
+ }
3576
+ const allSourceCode = fileEntries.map((e) => e.source).join("\n");
3577
+ if (usesCoerceHelpers(allSourceCode)) {
3578
+ const helpersPath = path17.resolve(rootDir, dist, HELPERS_FILENAME);
3579
+ const { existsSync: existsSync2 } = await import("fs");
3580
+ if (!existsSync2(helpersPath)) {
3581
+ await atomicWriteFile(helpersPath, generateHelpersFileSource());
3582
+ }
3583
+ }
3584
+ await Promise.all(
3585
+ fileEntries.map(({ outputPath, source }) => atomicWriteFile(outputPath, source))
3586
+ );
3587
+ return hydrateTasks(serialized);
3588
+ }
3589
+ var TASKS_FILE;
3590
+ var init_generateTaskArtifacts = __esm({
3591
+ "src/cli/generateTaskArtifacts.ts"() {
3592
+ "use strict";
3593
+ init_extractToolMetadata();
3594
+ init_createProgram();
3595
+ init_prodPaths();
3596
+ init_atomicWrite();
3597
+ init_generateSchemaFiles();
3598
+ init_extractHandlerTypes();
3599
+ init_generateZodSchema();
3600
+ TASKS_FILE = "faapi-tasks.js";
3601
+ }
3602
+ });
3603
+
3604
+ // src/cli/compileSourceFiles.ts
3605
+ import path18 from "path";
3606
+ import fs13 from "fs";
3607
+ import fg5 from "fast-glob";
3608
+ async function compileSourceFiles(options) {
3609
+ const { rootDir, dist, files, logLevel = "silent", production, atomicWrite } = options;
3610
+ const entryPoints = files ?? await fg5([`${APP_DIR}/**/*.ts`], {
3611
+ cwd: rootDir,
3612
+ onlyFiles: true,
3613
+ absolute: true,
3614
+ ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
3615
+ });
3616
+ if (entryPoints.length === 0) {
3617
+ return { compiledFiles: [] };
3618
+ }
3619
+ const absDist = path18.resolve(rootDir, dist);
3620
+ await fs13.promises.mkdir(absDist, { recursive: true });
3621
+ const plugins = buildAliasPlugins(rootDir);
3622
+ const esbuild = await import("esbuild");
3623
+ const outbase = path18.resolve(rootDir, APP_DIR);
3624
+ const result = await esbuild.build({
3625
+ entryPoints,
3626
+ outdir: absDist,
3627
+ outbase,
3628
+ bundle: false,
3629
+ platform: "node",
3630
+ format: "esm",
3631
+ sourcemap: true,
3632
+ packages: "external",
3633
+ plugins,
3634
+ // build 语义:编译期 NODE_ENV 替换 + 死分支删除(见 AGENTS.md §5.3)
3635
+ ...production ? { define: { "process.env.NODE_ENV": '"production"' }, minifySyntax: true } : {},
3636
+ logLevel,
3637
+ // dev 语义:esbuild 返回内存内容,由下方原子写落盘
3638
+ ...atomicWrite ? { write: false } : {}
3639
+ });
3640
+ if (atomicWrite && result.outputFiles) {
3641
+ await Promise.all(
3642
+ result.outputFiles.map(async (file) => {
3643
+ await fs13.promises.mkdir(path18.dirname(file.path), { recursive: true });
3644
+ const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
3645
+ await fs13.promises.writeFile(tmp, file.contents);
3646
+ await fs13.promises.rename(tmp, file.path);
3647
+ })
3648
+ );
3649
+ }
3650
+ return { compiledFiles: entryPoints };
3651
+ }
3652
+ var init_compileSourceFiles = __esm({
3653
+ "src/cli/compileSourceFiles.ts"() {
3654
+ "use strict";
3655
+ init_aliasPlugin();
3656
+ init_prodPaths();
3657
+ }
3658
+ });
3659
+
3660
+ // src/cli/compileDevRoutes.ts
3661
+ async function compileDevRoutes(options) {
3662
+ return compileSourceFiles({ ...options, atomicWrite: true });
3663
+ }
3664
+ var init_compileDevRoutes = __esm({
3665
+ "src/cli/compileDevRoutes.ts"() {
3666
+ "use strict";
3667
+ init_compileSourceFiles();
3668
+ }
3669
+ });
3670
+
3671
+ // src/cli/compileOnDemand.ts
3672
+ import path19 from "path";
3673
+ import fs14 from "fs";
3674
+ function isProductFresh(sourceAbsPath, productAbsPath) {
3675
+ try {
3676
+ const srcStat = fs14.statSync(sourceAbsPath);
3677
+ const prodStat = fs14.statSync(productAbsPath);
3678
+ return prodStat.mtimeMs >= srcStat.mtimeMs;
3679
+ } catch {
3680
+ return false;
3681
+ }
3682
+ }
3683
+ function createDevOnDemandState() {
3684
+ return {
3685
+ enabled: false,
3686
+ distDir: void 0,
3687
+ compiledFiles: /* @__PURE__ */ new Set(),
3688
+ generatedSchemas: /* @__PURE__ */ new Set(),
3689
+ inFlightCompilations: /* @__PURE__ */ new Map(),
3690
+ inFlightSchemaGenerations: /* @__PURE__ */ new Map()
3691
+ };
3692
+ }
3693
+ function clearCompiledFiles() {
3694
+ state.compiledFiles.clear();
3695
+ state.inFlightCompilations.clear();
3696
+ sourcePathCache.clear();
3697
+ }
3698
+ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
3699
+ const inFlight2 = state.inFlightCompilations.get(sourceAbsPath);
3700
+ if (inFlight2) {
3701
+ await inFlight2.catch(() => {
3702
+ });
3703
+ return false;
3704
+ }
3705
+ if (state.compiledFiles.has(sourceAbsPath)) {
3706
+ return false;
3707
+ }
3708
+ if (!fs14.existsSync(sourceAbsPath)) {
3709
+ return false;
3710
+ }
3711
+ const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
3712
+ if (productPath && isProductFresh(sourceAbsPath, productPath)) {
3713
+ state.compiledFiles.add(sourceAbsPath);
3714
+ return false;
3715
+ }
3716
+ const compilePromise = (async () => {
3717
+ const { insideFiles } = await collectRelativeImports([sourceAbsPath], rootDir);
3718
+ const files = [sourceAbsPath];
3719
+ for (const dep of insideFiles) {
3720
+ if (state.compiledFiles.has(dep)) continue;
3721
+ const depProduct = prodSourcePathToProductPath(dep, rootDir, dist);
3722
+ if (depProduct && isProductFresh(dep, depProduct)) continue;
3723
+ files.push(dep);
3724
+ }
3725
+ await compileDevRoutes({
3726
+ rootDir,
3727
+ dist,
3728
+ files,
3729
+ logLevel: "silent"
3730
+ });
3731
+ for (const file of files) {
3732
+ state.compiledFiles.add(file);
3733
+ }
3734
+ state.compiledFiles.add(sourceAbsPath);
3735
+ })();
3736
+ state.inFlightCompilations.set(sourceAbsPath, compilePromise);
3737
+ try {
3738
+ await compilePromise;
3739
+ return true;
3740
+ } finally {
3741
+ state.inFlightCompilations.delete(sourceAbsPath);
3742
+ }
3743
+ }
3744
+ async function ensureMiddlewaresCompiled(middlewarePaths, rootDir) {
3745
+ if (!isDevOnDemandEnabled() || middlewarePaths.length === 0) return;
3746
+ const dist = getDevDist();
3747
+ if (!dist) return;
3748
+ for (const mwPath of middlewarePaths) {
3749
+ const sourcePath = prodPathToSourcePath(mwPath, rootDir, dist);
3750
+ try {
3751
+ await ensureCompiled(sourcePath, rootDir, dist);
3752
+ } catch (err) {
3753
+ console.error(`[faapi] Failed to compile middleware source ${sourcePath}:`, err);
3754
+ }
3755
+ }
3756
+ }
3757
+ function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
3758
+ const rel = path19.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
3759
+ if (!rel.startsWith("src/")) return null;
3760
+ const relWithoutSrc = rel.slice(4);
3761
+ const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
3762
+ return path19.resolve(rootDir, dist, jsRel);
3763
+ }
3764
+ function clearGeneratedSchemas() {
3765
+ state.generatedSchemas.clear();
3766
+ state.inFlightSchemaGenerations.clear();
3767
+ }
3768
+ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
3769
+ const inFlight2 = state.inFlightSchemaGenerations.get(schemaPath);
3770
+ if (inFlight2) {
3771
+ await inFlight2.catch(() => {
3772
+ });
3773
+ return false;
3774
+ }
3775
+ if (state.generatedSchemas.has(schemaPath)) {
3776
+ return false;
3777
+ }
3778
+ const prodAbsPath = path19.resolve(rootDir, routeFilePath);
3779
+ const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
3780
+ if (!fs14.existsSync(sourceAbsPath)) {
3781
+ return false;
3782
+ }
3783
+ if (isProductFresh(sourceAbsPath, schemaPath)) {
3784
+ state.generatedSchemas.add(schemaPath);
3785
+ return false;
3786
+ }
3787
+ const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
3788
+ if (fileRoutes.length === 0) {
3789
+ return false;
3790
+ }
3791
+ const sourceRelPath = path19.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
3792
+ const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
3793
+ const generatePromise = (async () => {
3794
+ await generateSchemaFiles(sourceRoutes, rootDir, dist);
3795
+ state.generatedSchemas.add(schemaPath);
3796
+ })();
3797
+ state.inFlightSchemaGenerations.set(schemaPath, generatePromise);
3798
+ try {
3799
+ await generatePromise;
3800
+ return true;
3801
+ } finally {
3802
+ state.inFlightSchemaGenerations.delete(schemaPath);
3803
+ }
3804
+ }
3805
+ async function deleteSchemaFiles(routes, rootDir, dist) {
3806
+ const deleted = /* @__PURE__ */ new Set();
3807
+ for (const route of routes) {
3808
+ const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
3809
+ if (deleted.has(schemaPath)) continue;
3810
+ deleted.add(schemaPath);
3811
+ try {
3812
+ await fs14.promises.unlink(schemaPath);
3813
+ } catch {
3814
+ }
3815
+ }
3816
+ }
3817
+ function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
3818
+ const cached = sourcePathCache.get(prodAbsPath);
3819
+ if (cached) return cached;
3820
+ const rel = path19.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
3821
+ let relWithoutDist = rel;
3822
+ if (relWithoutDist.startsWith(`${dist}/`)) {
3823
+ relWithoutDist = relWithoutDist.slice(dist.length + 1);
3824
+ }
3825
+ const srcRel = `src/${relWithoutDist}`;
3826
+ const tsRel = srcRel.replace(/\.js$/, ".ts");
3827
+ const tsAbs = path19.resolve(rootDir, tsRel);
3828
+ let result;
3829
+ if (fs14.existsSync(tsAbs)) {
3830
+ result = tsAbs;
3831
+ } else {
3832
+ result = path19.resolve(rootDir, srcRel);
3833
+ }
3834
+ sourcePathCache.set(prodAbsPath, result);
3835
+ return result;
3836
+ }
3837
+ function setDevOnDemandEnabled(enabled) {
3838
+ state.enabled = enabled;
3839
+ }
3840
+ function isDevOnDemandEnabled() {
3841
+ return state.enabled;
3842
+ }
3843
+ function setDevDist(dist) {
3844
+ state.distDir = dist;
3845
+ }
3846
+ function getDevDist() {
3847
+ return state.distDir;
3848
+ }
3849
+ var state, sourcePathCache;
3850
+ var init_compileOnDemand = __esm({
3851
+ "src/cli/compileOnDemand.ts"() {
3852
+ "use strict";
3853
+ init_compileDevRoutes();
3854
+ init_generateSchemaFiles();
3855
+ init_generateSchemaFiles();
3856
+ init_collectImports();
3857
+ state = createDevOnDemandState();
3858
+ sourcePathCache = /* @__PURE__ */ new Map();
3859
+ }
3860
+ });
3861
+
3862
+ // src/config/loadConfig.ts
3863
+ import path20 from "path";
3864
+ import fs15 from "fs";
3376
3865
  async function loadConfig(rootDir, dist) {
3377
- const configProductPath = path16.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
3378
- if (fs12.existsSync(configProductPath)) {
3866
+ const configProductPath = path20.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
3867
+ if (fs15.existsSync(configProductPath)) {
3379
3868
  const module = await importWithCacheBust(configProductPath);
3380
3869
  return module.default ?? {};
3381
3870
  }
3382
- const hasSourceConfig = fs12.existsSync(path16.join(rootDir, "faapi.config.ts")) || fs12.existsSync(path16.join(rootDir, "faapi.config.js"));
3871
+ const hasSourceConfig = fs15.existsSync(path20.join(rootDir, "faapi.config.ts")) || fs15.existsSync(path20.join(rootDir, "faapi.config.js"));
3383
3872
  if (hasSourceConfig) {
3384
3873
  throw new Error(
3385
3874
  `[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 +3886,8 @@ var init_loadConfig = __esm({
3397
3886
  });
3398
3887
 
3399
3888
  // src/cli/loadEnv.ts
3400
- import fs13 from "fs";
3401
- import path17 from "path";
3889
+ import fs16 from "fs";
3890
+ import path21 from "path";
3402
3891
  function resolveEnv() {
3403
3892
  return process.env.NODE_ENV || "development";
3404
3893
  }
@@ -3465,9 +3954,9 @@ function loadEnv(rootDir) {
3465
3954
  const files = getEnvFiles(env);
3466
3955
  const merged = {};
3467
3956
  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");
3957
+ const filePath = path21.join(rootDir, file);
3958
+ if (!fs16.existsSync(filePath)) continue;
3959
+ const content = fs16.readFileSync(filePath, "utf-8");
3471
3960
  const parsed = parseEnvFile(content, merged);
3472
3961
  Object.assign(merged, parsed);
3473
3962
  }
@@ -3573,7 +4062,7 @@ var init_esm = __esm({
3573
4062
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
3574
4063
  const statMethod = opts.lstat ? lstat : stat;
3575
4064
  if (wantBigintFsStats) {
3576
- this._stat = (path27) => statMethod(path27, { bigint: true });
4065
+ this._stat = (path31) => statMethod(path31, { bigint: true });
3577
4066
  } else {
3578
4067
  this._stat = statMethod;
3579
4068
  }
@@ -3598,8 +4087,8 @@ var init_esm = __esm({
3598
4087
  const par = this.parent;
3599
4088
  const fil = par && par.files;
3600
4089
  if (fil && fil.length > 0) {
3601
- const { path: path27, depth } = par;
3602
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path27));
4090
+ const { path: path31, depth } = par;
4091
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path31));
3603
4092
  const awaited = await Promise.all(slice);
3604
4093
  for (const entry of awaited) {
3605
4094
  if (!entry)
@@ -3639,20 +4128,20 @@ var init_esm = __esm({
3639
4128
  this.reading = false;
3640
4129
  }
3641
4130
  }
3642
- async _exploreDir(path27, depth) {
4131
+ async _exploreDir(path31, depth) {
3643
4132
  let files;
3644
4133
  try {
3645
- files = await readdir(path27, this._rdOptions);
4134
+ files = await readdir(path31, this._rdOptions);
3646
4135
  } catch (error) {
3647
4136
  this._onError(error);
3648
4137
  }
3649
- return { files, depth, path: path27 };
4138
+ return { files, depth, path: path31 };
3650
4139
  }
3651
- async _formatEntry(dirent, path27) {
4140
+ async _formatEntry(dirent, path31) {
3652
4141
  let entry;
3653
4142
  const basename3 = this._isDirent ? dirent.name : dirent;
3654
4143
  try {
3655
- const fullPath = presolve(pjoin(path27, basename3));
4144
+ const fullPath = presolve(pjoin(path31, basename3));
3656
4145
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
3657
4146
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
3658
4147
  } catch (err) {
@@ -3713,16 +4202,16 @@ import { watchFile, unwatchFile, watch as fs_watch } from "fs";
3713
4202
  import { open, stat as stat2, lstat as lstat2, realpath as fsrealpath } from "fs/promises";
3714
4203
  import * as sysPath from "path";
3715
4204
  import { type as osType } from "os";
3716
- function createFsWatchInstance(path27, options, listener, errHandler, emitRaw) {
4205
+ function createFsWatchInstance(path31, options, listener, errHandler, emitRaw) {
3717
4206
  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));
4207
+ listener(path31);
4208
+ emitRaw(rawEvent, evPath, { watchedPath: path31 });
4209
+ if (evPath && path31 !== evPath) {
4210
+ fsWatchBroadcast(sysPath.resolve(path31, evPath), KEY_LISTENERS, sysPath.join(path31, evPath));
3722
4211
  }
3723
4212
  };
3724
4213
  try {
3725
- return fs_watch(path27, {
4214
+ return fs_watch(path31, {
3726
4215
  persistent: options.persistent
3727
4216
  }, handleEvent);
3728
4217
  } catch (error) {
@@ -4067,12 +4556,12 @@ var init_handler = __esm({
4067
4556
  listener(val1, val2, val3);
4068
4557
  });
4069
4558
  };
4070
- setFsWatchListener = (path27, fullPath, options, handlers) => {
4559
+ setFsWatchListener = (path31, fullPath, options, handlers) => {
4071
4560
  const { listener, errHandler, rawEmitter } = handlers;
4072
4561
  let cont = FsWatchInstances.get(fullPath);
4073
4562
  let watcher;
4074
4563
  if (!options.persistent) {
4075
- watcher = createFsWatchInstance(path27, options, listener, errHandler, rawEmitter);
4564
+ watcher = createFsWatchInstance(path31, options, listener, errHandler, rawEmitter);
4076
4565
  if (!watcher)
4077
4566
  return;
4078
4567
  return watcher.close.bind(watcher);
@@ -4083,7 +4572,7 @@ var init_handler = __esm({
4083
4572
  addAndConvert(cont, KEY_RAW, rawEmitter);
4084
4573
  } else {
4085
4574
  watcher = createFsWatchInstance(
4086
- path27,
4575
+ path31,
4087
4576
  options,
4088
4577
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
4089
4578
  errHandler,
@@ -4098,7 +4587,7 @@ var init_handler = __esm({
4098
4587
  cont.watcherUnusable = true;
4099
4588
  if (isWindows && error.code === "EPERM") {
4100
4589
  try {
4101
- const fd = await open(path27, "r");
4590
+ const fd = await open(path31, "r");
4102
4591
  await fd.close();
4103
4592
  broadcastErr(error);
4104
4593
  } catch (err) {
@@ -4129,7 +4618,7 @@ var init_handler = __esm({
4129
4618
  };
4130
4619
  };
4131
4620
  FsWatchFileInstances = /* @__PURE__ */ new Map();
4132
- setFsWatchFileListener = (path27, fullPath, options, handlers) => {
4621
+ setFsWatchFileListener = (path31, fullPath, options, handlers) => {
4133
4622
  const { listener, rawEmitter } = handlers;
4134
4623
  let cont = FsWatchFileInstances.get(fullPath);
4135
4624
  const copts = cont && cont.options;
@@ -4151,7 +4640,7 @@ var init_handler = __esm({
4151
4640
  });
4152
4641
  const currmtime = curr.mtimeMs;
4153
4642
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
4154
- foreach(cont.listeners, (listener2) => listener2(path27, curr));
4643
+ foreach(cont.listeners, (listener2) => listener2(path31, curr));
4155
4644
  }
4156
4645
  })
4157
4646
  };
@@ -4179,13 +4668,13 @@ var init_handler = __esm({
4179
4668
  * @param listener on fs change
4180
4669
  * @returns closer for the watcher instance
4181
4670
  */
4182
- _watchWithNodeFs(path27, listener) {
4671
+ _watchWithNodeFs(path31, listener) {
4183
4672
  const opts = this.fsw.options;
4184
- const directory = sysPath.dirname(path27);
4185
- const basename3 = sysPath.basename(path27);
4673
+ const directory = sysPath.dirname(path31);
4674
+ const basename3 = sysPath.basename(path31);
4186
4675
  const parent = this.fsw._getWatchedDir(directory);
4187
4676
  parent.add(basename3);
4188
- const absolutePath = sysPath.resolve(path27);
4677
+ const absolutePath = sysPath.resolve(path31);
4189
4678
  const options = {
4190
4679
  persistent: opts.persistent
4191
4680
  };
@@ -4195,12 +4684,12 @@ var init_handler = __esm({
4195
4684
  if (opts.usePolling) {
4196
4685
  const enableBin = opts.interval !== opts.binaryInterval;
4197
4686
  options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
4198
- closer = setFsWatchFileListener(path27, absolutePath, options, {
4687
+ closer = setFsWatchFileListener(path31, absolutePath, options, {
4199
4688
  listener,
4200
4689
  rawEmitter: this.fsw._emitRaw
4201
4690
  });
4202
4691
  } else {
4203
- closer = setFsWatchListener(path27, absolutePath, options, {
4692
+ closer = setFsWatchListener(path31, absolutePath, options, {
4204
4693
  listener,
4205
4694
  errHandler: this._boundHandleError,
4206
4695
  rawEmitter: this.fsw._emitRaw
@@ -4222,7 +4711,7 @@ var init_handler = __esm({
4222
4711
  let prevStats = stats;
4223
4712
  if (parent.has(basename3))
4224
4713
  return;
4225
- const listener = async (path27, newStats) => {
4714
+ const listener = async (path31, newStats) => {
4226
4715
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
4227
4716
  return;
4228
4717
  if (!newStats || newStats.mtimeMs === 0) {
@@ -4236,11 +4725,11 @@ var init_handler = __esm({
4236
4725
  this.fsw._emit(EV.CHANGE, file, newStats2);
4237
4726
  }
4238
4727
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
4239
- this.fsw._closeFile(path27);
4728
+ this.fsw._closeFile(path31);
4240
4729
  prevStats = newStats2;
4241
4730
  const closer2 = this._watchWithNodeFs(file, listener);
4242
4731
  if (closer2)
4243
- this.fsw._addPathCloser(path27, closer2);
4732
+ this.fsw._addPathCloser(path31, closer2);
4244
4733
  } else {
4245
4734
  prevStats = newStats2;
4246
4735
  }
@@ -4272,7 +4761,7 @@ var init_handler = __esm({
4272
4761
  * @param item basename of this item
4273
4762
  * @returns true if no more processing is needed for this entry.
4274
4763
  */
4275
- async _handleSymlink(entry, directory, path27, item) {
4764
+ async _handleSymlink(entry, directory, path31, item) {
4276
4765
  if (this.fsw.closed) {
4277
4766
  return;
4278
4767
  }
@@ -4282,7 +4771,7 @@ var init_handler = __esm({
4282
4771
  this.fsw._incrReadyCount();
4283
4772
  let linkPath;
4284
4773
  try {
4285
- linkPath = await fsrealpath(path27);
4774
+ linkPath = await fsrealpath(path31);
4286
4775
  } catch (e) {
4287
4776
  this.fsw._emitReady();
4288
4777
  return true;
@@ -4292,12 +4781,12 @@ var init_handler = __esm({
4292
4781
  if (dir.has(item)) {
4293
4782
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
4294
4783
  this.fsw._symlinkPaths.set(full, linkPath);
4295
- this.fsw._emit(EV.CHANGE, path27, entry.stats);
4784
+ this.fsw._emit(EV.CHANGE, path31, entry.stats);
4296
4785
  }
4297
4786
  } else {
4298
4787
  dir.add(item);
4299
4788
  this.fsw._symlinkPaths.set(full, linkPath);
4300
- this.fsw._emit(EV.ADD, path27, entry.stats);
4789
+ this.fsw._emit(EV.ADD, path31, entry.stats);
4301
4790
  }
4302
4791
  this.fsw._emitReady();
4303
4792
  return true;
@@ -4326,9 +4815,9 @@ var init_handler = __esm({
4326
4815
  return;
4327
4816
  }
4328
4817
  const item = entry.path;
4329
- let path27 = sysPath.join(directory, item);
4818
+ let path31 = sysPath.join(directory, item);
4330
4819
  current.add(item);
4331
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path27, item)) {
4820
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path31, item)) {
4332
4821
  return;
4333
4822
  }
4334
4823
  if (this.fsw.closed) {
@@ -4337,8 +4826,8 @@ var init_handler = __esm({
4337
4826
  }
4338
4827
  if (item === target || !target && !previous.has(item)) {
4339
4828
  this.fsw._incrReadyCount();
4340
- path27 = sysPath.join(dir, sysPath.relative(dir, path27));
4341
- this._addToNodeFs(path27, initialAdd, wh, depth + 1);
4829
+ path31 = sysPath.join(dir, sysPath.relative(dir, path31));
4830
+ this._addToNodeFs(path31, initialAdd, wh, depth + 1);
4342
4831
  }
4343
4832
  }).on(EV.ERROR, this._boundHandleError);
4344
4833
  return new Promise((resolve3, reject) => {
@@ -4407,13 +4896,13 @@ var init_handler = __esm({
4407
4896
  * @param depth Child path actually targeted for watch
4408
4897
  * @param target Child path actually targeted for watch
4409
4898
  */
4410
- async _addToNodeFs(path27, initialAdd, priorWh, depth, target) {
4899
+ async _addToNodeFs(path31, initialAdd, priorWh, depth, target) {
4411
4900
  const ready = this.fsw._emitReady;
4412
- if (this.fsw._isIgnored(path27) || this.fsw.closed) {
4901
+ if (this.fsw._isIgnored(path31) || this.fsw.closed) {
4413
4902
  ready();
4414
4903
  return false;
4415
4904
  }
4416
- const wh = this.fsw._getWatchHelpers(path27);
4905
+ const wh = this.fsw._getWatchHelpers(path31);
4417
4906
  if (priorWh) {
4418
4907
  wh.filterPath = (entry) => priorWh.filterPath(entry);
4419
4908
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -4429,8 +4918,8 @@ var init_handler = __esm({
4429
4918
  const follow = this.fsw.options.followSymlinks;
4430
4919
  let closer;
4431
4920
  if (stats.isDirectory()) {
4432
- const absPath = sysPath.resolve(path27);
4433
- const targetPath = follow ? await fsrealpath(path27) : path27;
4921
+ const absPath = sysPath.resolve(path31);
4922
+ const targetPath = follow ? await fsrealpath(path31) : path31;
4434
4923
  if (this.fsw.closed)
4435
4924
  return;
4436
4925
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -4440,29 +4929,29 @@ var init_handler = __esm({
4440
4929
  this.fsw._symlinkPaths.set(absPath, targetPath);
4441
4930
  }
4442
4931
  } else if (stats.isSymbolicLink()) {
4443
- const targetPath = follow ? await fsrealpath(path27) : path27;
4932
+ const targetPath = follow ? await fsrealpath(path31) : path31;
4444
4933
  if (this.fsw.closed)
4445
4934
  return;
4446
4935
  const parent = sysPath.dirname(wh.watchPath);
4447
4936
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
4448
4937
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
4449
- closer = await this._handleDir(parent, stats, initialAdd, depth, path27, wh, targetPath);
4938
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path31, wh, targetPath);
4450
4939
  if (this.fsw.closed)
4451
4940
  return;
4452
4941
  if (targetPath !== void 0) {
4453
- this.fsw._symlinkPaths.set(sysPath.resolve(path27), targetPath);
4942
+ this.fsw._symlinkPaths.set(sysPath.resolve(path31), targetPath);
4454
4943
  }
4455
4944
  } else {
4456
4945
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
4457
4946
  }
4458
4947
  ready();
4459
4948
  if (closer)
4460
- this.fsw._addPathCloser(path27, closer);
4949
+ this.fsw._addPathCloser(path31, closer);
4461
4950
  return false;
4462
4951
  } catch (error) {
4463
4952
  if (this.fsw._handleError(error)) {
4464
4953
  ready();
4465
- return path27;
4954
+ return path31;
4466
4955
  }
4467
4956
  }
4468
4957
  }
@@ -4501,26 +4990,26 @@ function createPattern(matcher) {
4501
4990
  }
4502
4991
  return () => false;
4503
4992
  }
4504
- function normalizePath2(path27) {
4505
- if (typeof path27 !== "string")
4993
+ function normalizePath2(path31) {
4994
+ if (typeof path31 !== "string")
4506
4995
  throw new Error("string expected");
4507
- path27 = sysPath2.normalize(path27);
4508
- path27 = path27.replace(/\\/g, "/");
4996
+ path31 = sysPath2.normalize(path31);
4997
+ path31 = path31.replace(/\\/g, "/");
4509
4998
  let prepend = false;
4510
- if (path27.startsWith("//"))
4999
+ if (path31.startsWith("//"))
4511
5000
  prepend = true;
4512
5001
  const DOUBLE_SLASH_RE2 = /\/\//;
4513
- while (path27.match(DOUBLE_SLASH_RE2))
4514
- path27 = path27.replace(DOUBLE_SLASH_RE2, "/");
5002
+ while (path31.match(DOUBLE_SLASH_RE2))
5003
+ path31 = path31.replace(DOUBLE_SLASH_RE2, "/");
4515
5004
  if (prepend)
4516
- path27 = "/" + path27;
4517
- return path27;
5005
+ path31 = "/" + path31;
5006
+ return path31;
4518
5007
  }
4519
5008
  function matchPatterns(patterns, testString, stats) {
4520
- const path27 = normalizePath2(testString);
5009
+ const path31 = normalizePath2(testString);
4521
5010
  for (let index = 0; index < patterns.length; index++) {
4522
5011
  const pattern = patterns[index];
4523
- if (pattern(path27, stats)) {
5012
+ if (pattern(path31, stats)) {
4524
5013
  return true;
4525
5014
  }
4526
5015
  }
@@ -4581,19 +5070,19 @@ var init_esm2 = __esm({
4581
5070
  }
4582
5071
  return str;
4583
5072
  };
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));
5073
+ normalizePathToUnix = (path31) => toUnix(sysPath2.normalize(toUnix(path31)));
5074
+ normalizeIgnored = (cwd = "") => (path31) => {
5075
+ if (typeof path31 === "string") {
5076
+ return normalizePathToUnix(sysPath2.isAbsolute(path31) ? path31 : sysPath2.join(cwd, path31));
4588
5077
  } else {
4589
- return path27;
5078
+ return path31;
4590
5079
  }
4591
5080
  };
4592
- getAbsolutePath = (path27, cwd) => {
4593
- if (sysPath2.isAbsolute(path27)) {
4594
- return path27;
5081
+ getAbsolutePath = (path31, cwd) => {
5082
+ if (sysPath2.isAbsolute(path31)) {
5083
+ return path31;
4595
5084
  }
4596
- return sysPath2.join(cwd, path27);
5085
+ return sysPath2.join(cwd, path31);
4597
5086
  };
4598
5087
  EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
4599
5088
  DirEntry = class {
@@ -4648,10 +5137,10 @@ var init_esm2 = __esm({
4648
5137
  STAT_METHOD_F = "stat";
4649
5138
  STAT_METHOD_L = "lstat";
4650
5139
  WatchHelper = class {
4651
- constructor(path27, follow, fsw) {
5140
+ constructor(path31, follow, fsw) {
4652
5141
  this.fsw = fsw;
4653
- const watchPath = path27;
4654
- this.path = path27 = path27.replace(REPLACER_RE, "");
5142
+ const watchPath = path31;
5143
+ this.path = path31 = path31.replace(REPLACER_RE, "");
4655
5144
  this.watchPath = watchPath;
4656
5145
  this.fullWatchPath = sysPath2.resolve(watchPath);
4657
5146
  this.dirParts = [];
@@ -4773,20 +5262,20 @@ var init_esm2 = __esm({
4773
5262
  this._closePromise = void 0;
4774
5263
  let paths = unifyPaths(paths_);
4775
5264
  if (cwd) {
4776
- paths = paths.map((path27) => {
4777
- const absPath = getAbsolutePath(path27, cwd);
5265
+ paths = paths.map((path31) => {
5266
+ const absPath = getAbsolutePath(path31, cwd);
4778
5267
  return absPath;
4779
5268
  });
4780
5269
  }
4781
- paths.forEach((path27) => {
4782
- this._removeIgnoredPath(path27);
5270
+ paths.forEach((path31) => {
5271
+ this._removeIgnoredPath(path31);
4783
5272
  });
4784
5273
  this._userIgnored = void 0;
4785
5274
  if (!this._readyCount)
4786
5275
  this._readyCount = 0;
4787
5276
  this._readyCount += paths.length;
4788
- Promise.all(paths.map(async (path27) => {
4789
- const res = await this._nodeFsHandler._addToNodeFs(path27, !_internal, void 0, 0, _origAdd);
5277
+ Promise.all(paths.map(async (path31) => {
5278
+ const res = await this._nodeFsHandler._addToNodeFs(path31, !_internal, void 0, 0, _origAdd);
4790
5279
  if (res)
4791
5280
  this._emitReady();
4792
5281
  return res;
@@ -4808,17 +5297,17 @@ var init_esm2 = __esm({
4808
5297
  return this;
4809
5298
  const paths = unifyPaths(paths_);
4810
5299
  const { cwd } = this.options;
4811
- paths.forEach((path27) => {
4812
- if (!sysPath2.isAbsolute(path27) && !this._closers.has(path27)) {
5300
+ paths.forEach((path31) => {
5301
+ if (!sysPath2.isAbsolute(path31) && !this._closers.has(path31)) {
4813
5302
  if (cwd)
4814
- path27 = sysPath2.join(cwd, path27);
4815
- path27 = sysPath2.resolve(path27);
5303
+ path31 = sysPath2.join(cwd, path31);
5304
+ path31 = sysPath2.resolve(path31);
4816
5305
  }
4817
- this._closePath(path27);
4818
- this._addIgnoredPath(path27);
4819
- if (this._watched.has(path27)) {
5306
+ this._closePath(path31);
5307
+ this._addIgnoredPath(path31);
5308
+ if (this._watched.has(path31)) {
4820
5309
  this._addIgnoredPath({
4821
- path: path27,
5310
+ path: path31,
4822
5311
  recursive: true
4823
5312
  });
4824
5313
  }
@@ -4882,38 +5371,38 @@ var init_esm2 = __esm({
4882
5371
  * @param stats arguments to be passed with event
4883
5372
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
4884
5373
  */
4885
- async _emit(event, path27, stats) {
5374
+ async _emit(event, path31, stats) {
4886
5375
  if (this.closed)
4887
5376
  return;
4888
5377
  const opts = this.options;
4889
5378
  if (isWindows)
4890
- path27 = sysPath2.normalize(path27);
5379
+ path31 = sysPath2.normalize(path31);
4891
5380
  if (opts.cwd)
4892
- path27 = sysPath2.relative(opts.cwd, path27);
4893
- const args = [path27];
5381
+ path31 = sysPath2.relative(opts.cwd, path31);
5382
+ const args = [path31];
4894
5383
  if (stats != null)
4895
5384
  args.push(stats);
4896
5385
  const awf = opts.awaitWriteFinish;
4897
5386
  let pw;
4898
- if (awf && (pw = this._pendingWrites.get(path27))) {
5387
+ if (awf && (pw = this._pendingWrites.get(path31))) {
4899
5388
  pw.lastChange = /* @__PURE__ */ new Date();
4900
5389
  return this;
4901
5390
  }
4902
5391
  if (opts.atomic) {
4903
5392
  if (event === EVENTS.UNLINK) {
4904
- this._pendingUnlinks.set(path27, [event, ...args]);
5393
+ this._pendingUnlinks.set(path31, [event, ...args]);
4905
5394
  setTimeout(() => {
4906
- this._pendingUnlinks.forEach((entry, path28) => {
5395
+ this._pendingUnlinks.forEach((entry, path32) => {
4907
5396
  this.emit(...entry);
4908
5397
  this.emit(EVENTS.ALL, ...entry);
4909
- this._pendingUnlinks.delete(path28);
5398
+ this._pendingUnlinks.delete(path32);
4910
5399
  });
4911
5400
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
4912
5401
  return this;
4913
5402
  }
4914
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path27)) {
5403
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path31)) {
4915
5404
  event = EVENTS.CHANGE;
4916
- this._pendingUnlinks.delete(path27);
5405
+ this._pendingUnlinks.delete(path31);
4917
5406
  }
4918
5407
  }
4919
5408
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -4931,16 +5420,16 @@ var init_esm2 = __esm({
4931
5420
  this.emitWithAll(event, args);
4932
5421
  }
4933
5422
  };
4934
- this._awaitWriteFinish(path27, awf.stabilityThreshold, event, awfEmit);
5423
+ this._awaitWriteFinish(path31, awf.stabilityThreshold, event, awfEmit);
4935
5424
  return this;
4936
5425
  }
4937
5426
  if (event === EVENTS.CHANGE) {
4938
- const isThrottled = !this._throttle(EVENTS.CHANGE, path27, 50);
5427
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path31, 50);
4939
5428
  if (isThrottled)
4940
5429
  return this;
4941
5430
  }
4942
5431
  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;
5432
+ const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path31) : path31;
4944
5433
  let stats2;
4945
5434
  try {
4946
5435
  stats2 = await stat3(fullPath);
@@ -4971,23 +5460,23 @@ var init_esm2 = __esm({
4971
5460
  * @param timeout duration of time to suppress duplicate actions
4972
5461
  * @returns tracking object or false if action should be suppressed
4973
5462
  */
4974
- _throttle(actionType, path27, timeout) {
5463
+ _throttle(actionType, path31, timeout) {
4975
5464
  if (!this._throttled.has(actionType)) {
4976
5465
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
4977
5466
  }
4978
5467
  const action = this._throttled.get(actionType);
4979
5468
  if (!action)
4980
5469
  throw new Error("invalid throttle");
4981
- const actionPath = action.get(path27);
5470
+ const actionPath = action.get(path31);
4982
5471
  if (actionPath) {
4983
5472
  actionPath.count++;
4984
5473
  return false;
4985
5474
  }
4986
5475
  let timeoutObject;
4987
5476
  const clear = () => {
4988
- const item = action.get(path27);
5477
+ const item = action.get(path31);
4989
5478
  const count = item ? item.count : 0;
4990
- action.delete(path27);
5479
+ action.delete(path31);
4991
5480
  clearTimeout(timeoutObject);
4992
5481
  if (item)
4993
5482
  clearTimeout(item.timeoutObject);
@@ -4995,7 +5484,7 @@ var init_esm2 = __esm({
4995
5484
  };
4996
5485
  timeoutObject = setTimeout(clear, timeout);
4997
5486
  const thr = { timeoutObject, clear, count: 0 };
4998
- action.set(path27, thr);
5487
+ action.set(path31, thr);
4999
5488
  return thr;
5000
5489
  }
5001
5490
  _incrReadyCount() {
@@ -5009,44 +5498,44 @@ var init_esm2 = __esm({
5009
5498
  * @param event
5010
5499
  * @param awfEmit Callback to be called when ready for event to be emitted.
5011
5500
  */
5012
- _awaitWriteFinish(path27, threshold, event, awfEmit) {
5501
+ _awaitWriteFinish(path31, threshold, event, awfEmit) {
5013
5502
  const awf = this.options.awaitWriteFinish;
5014
5503
  if (typeof awf !== "object")
5015
5504
  return;
5016
5505
  const pollInterval = awf.pollInterval;
5017
5506
  let timeoutHandler;
5018
- let fullPath = path27;
5019
- if (this.options.cwd && !sysPath2.isAbsolute(path27)) {
5020
- fullPath = sysPath2.join(this.options.cwd, path27);
5507
+ let fullPath = path31;
5508
+ if (this.options.cwd && !sysPath2.isAbsolute(path31)) {
5509
+ fullPath = sysPath2.join(this.options.cwd, path31);
5021
5510
  }
5022
5511
  const now = /* @__PURE__ */ new Date();
5023
5512
  const writes = this._pendingWrites;
5024
5513
  function awaitWriteFinishFn(prevStat) {
5025
5514
  statcb(fullPath, (err, curStat) => {
5026
- if (err || !writes.has(path27)) {
5515
+ if (err || !writes.has(path31)) {
5027
5516
  if (err && err.code !== "ENOENT")
5028
5517
  awfEmit(err);
5029
5518
  return;
5030
5519
  }
5031
5520
  const now2 = Number(/* @__PURE__ */ new Date());
5032
5521
  if (prevStat && curStat.size !== prevStat.size) {
5033
- writes.get(path27).lastChange = now2;
5522
+ writes.get(path31).lastChange = now2;
5034
5523
  }
5035
- const pw = writes.get(path27);
5524
+ const pw = writes.get(path31);
5036
5525
  const df = now2 - pw.lastChange;
5037
5526
  if (df >= threshold) {
5038
- writes.delete(path27);
5527
+ writes.delete(path31);
5039
5528
  awfEmit(void 0, curStat);
5040
5529
  } else {
5041
5530
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
5042
5531
  }
5043
5532
  });
5044
5533
  }
5045
- if (!writes.has(path27)) {
5046
- writes.set(path27, {
5534
+ if (!writes.has(path31)) {
5535
+ writes.set(path31, {
5047
5536
  lastChange: now,
5048
5537
  cancelWait: () => {
5049
- writes.delete(path27);
5538
+ writes.delete(path31);
5050
5539
  clearTimeout(timeoutHandler);
5051
5540
  return event;
5052
5541
  }
@@ -5057,8 +5546,8 @@ var init_esm2 = __esm({
5057
5546
  /**
5058
5547
  * Determines whether user has asked to ignore this path.
5059
5548
  */
5060
- _isIgnored(path27, stats) {
5061
- if (this.options.atomic && DOT_RE.test(path27))
5549
+ _isIgnored(path31, stats) {
5550
+ if (this.options.atomic && DOT_RE.test(path31))
5062
5551
  return true;
5063
5552
  if (!this._userIgnored) {
5064
5553
  const { cwd } = this.options;
@@ -5068,17 +5557,17 @@ var init_esm2 = __esm({
5068
5557
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
5069
5558
  this._userIgnored = anymatch(list, void 0);
5070
5559
  }
5071
- return this._userIgnored(path27, stats);
5560
+ return this._userIgnored(path31, stats);
5072
5561
  }
5073
- _isntIgnored(path27, stat4) {
5074
- return !this._isIgnored(path27, stat4);
5562
+ _isntIgnored(path31, stat4) {
5563
+ return !this._isIgnored(path31, stat4);
5075
5564
  }
5076
5565
  /**
5077
5566
  * Provides a set of common helpers and properties relating to symlink handling.
5078
5567
  * @param path file or directory pattern being watched
5079
5568
  */
5080
- _getWatchHelpers(path27) {
5081
- return new WatchHelper(path27, this.options.followSymlinks, this);
5569
+ _getWatchHelpers(path31) {
5570
+ return new WatchHelper(path31, this.options.followSymlinks, this);
5082
5571
  }
5083
5572
  // Directory helpers
5084
5573
  // -----------------
@@ -5110,63 +5599,63 @@ var init_esm2 = __esm({
5110
5599
  * @param item base path of item/directory
5111
5600
  */
5112
5601
  _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))
5602
+ const path31 = sysPath2.join(directory, item);
5603
+ const fullPath = sysPath2.resolve(path31);
5604
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path31) || this._watched.has(fullPath);
5605
+ if (!this._throttle("remove", path31, 100))
5117
5606
  return;
5118
5607
  if (!isDirectory && this._watched.size === 1) {
5119
5608
  this.add(directory, item, true);
5120
5609
  }
5121
- const wp = this._getWatchedDir(path27);
5610
+ const wp = this._getWatchedDir(path31);
5122
5611
  const nestedDirectoryChildren = wp.getChildren();
5123
- nestedDirectoryChildren.forEach((nested) => this._remove(path27, nested));
5612
+ nestedDirectoryChildren.forEach((nested) => this._remove(path31, nested));
5124
5613
  const parent = this._getWatchedDir(directory);
5125
5614
  const wasTracked = parent.has(item);
5126
5615
  parent.remove(item);
5127
5616
  if (this._symlinkPaths.has(fullPath)) {
5128
5617
  this._symlinkPaths.delete(fullPath);
5129
5618
  }
5130
- let relPath = path27;
5619
+ let relPath = path31;
5131
5620
  if (this.options.cwd)
5132
- relPath = sysPath2.relative(this.options.cwd, path27);
5621
+ relPath = sysPath2.relative(this.options.cwd, path31);
5133
5622
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
5134
5623
  const event = this._pendingWrites.get(relPath).cancelWait();
5135
5624
  if (event === EVENTS.ADD)
5136
5625
  return;
5137
5626
  }
5138
- this._watched.delete(path27);
5627
+ this._watched.delete(path31);
5139
5628
  this._watched.delete(fullPath);
5140
5629
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
5141
- if (wasTracked && !this._isIgnored(path27))
5142
- this._emit(eventName, path27);
5143
- this._closePath(path27);
5630
+ if (wasTracked && !this._isIgnored(path31))
5631
+ this._emit(eventName, path31);
5632
+ this._closePath(path31);
5144
5633
  }
5145
5634
  /**
5146
5635
  * Closes all watchers for a path
5147
5636
  */
5148
- _closePath(path27) {
5149
- this._closeFile(path27);
5150
- const dir = sysPath2.dirname(path27);
5151
- this._getWatchedDir(dir).remove(sysPath2.basename(path27));
5637
+ _closePath(path31) {
5638
+ this._closeFile(path31);
5639
+ const dir = sysPath2.dirname(path31);
5640
+ this._getWatchedDir(dir).remove(sysPath2.basename(path31));
5152
5641
  }
5153
5642
  /**
5154
5643
  * Closes only file-specific watchers
5155
5644
  */
5156
- _closeFile(path27) {
5157
- const closers = this._closers.get(path27);
5645
+ _closeFile(path31) {
5646
+ const closers = this._closers.get(path31);
5158
5647
  if (!closers)
5159
5648
  return;
5160
5649
  closers.forEach((closer) => closer());
5161
- this._closers.delete(path27);
5650
+ this._closers.delete(path31);
5162
5651
  }
5163
- _addPathCloser(path27, closer) {
5652
+ _addPathCloser(path31, closer) {
5164
5653
  if (!closer)
5165
5654
  return;
5166
- let list = this._closers.get(path27);
5655
+ let list = this._closers.get(path31);
5167
5656
  if (!list) {
5168
5657
  list = [];
5169
- this._closers.set(path27, list);
5658
+ this._closers.set(path31, list);
5170
5659
  }
5171
5660
  list.push(closer);
5172
5661
  }
@@ -5192,73 +5681,6 @@ var init_esm2 = __esm({
5192
5681
  }
5193
5682
  });
5194
5683
 
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
5684
  // src/cli/rebuildScheduler.ts
5263
5685
  function createRebuildScheduler(options) {
5264
5686
  const { rebuild, debounceMs = 100, onError } = options;
@@ -5321,7 +5743,7 @@ var init_rebuildScheduler = __esm({
5321
5743
  });
5322
5744
 
5323
5745
  // src/cli/watcher.ts
5324
- import path19 from "path";
5746
+ import path22 from "path";
5325
5747
  function startWatcher(options) {
5326
5748
  const { rootDir, app, devDist } = options;
5327
5749
  async function rebuildRoutes(files) {
@@ -5336,6 +5758,7 @@ function startWatcher(options) {
5336
5758
  await app.reloadRoutes();
5337
5759
  await app.reloadTools();
5338
5760
  await app.reloadAgents();
5761
+ await app.reloadTasks();
5339
5762
  console.log(
5340
5763
  `- Routes rebuilt${files.length > 0 ? `, ${files.length} file(s) recompiled` : ""}`
5341
5764
  );
@@ -5356,9 +5779,9 @@ function startWatcher(options) {
5356
5779
  }
5357
5780
  });
5358
5781
  const CONFIG_FILES = ["faapi.config.ts", "faapi.config.js"];
5359
- const configAbsPaths = new Set(CONFIG_FILES.map((f) => path19.resolve(rootDir, f)));
5782
+ const configAbsPaths = new Set(CONFIG_FILES.map((f) => path22.resolve(rootDir, f)));
5360
5783
  const watchPaths = ["src", "tsconfig.json", ...CONFIG_FILES];
5361
- const scheduleOnly = /* @__PURE__ */ new Set([...configAbsPaths, path19.resolve(rootDir, "tsconfig.json")]);
5784
+ const scheduleOnly = /* @__PURE__ */ new Set([...configAbsPaths, path22.resolve(rootDir, "tsconfig.json")]);
5362
5785
  const watcher = esm_default.watch(watchPaths, {
5363
5786
  cwd: rootDir,
5364
5787
  ignoreInitial: true,
@@ -5375,7 +5798,7 @@ function startWatcher(options) {
5375
5798
  watcher.on("add", (file) => handleFileEvent(file));
5376
5799
  watcher.on("change", (file) => handleFileEvent(file));
5377
5800
  function handleFileEvent(file) {
5378
- const abs = path19.resolve(rootDir, file);
5801
+ const abs = path22.resolve(rootDir, file);
5379
5802
  if (scheduleOnly.has(abs)) {
5380
5803
  scheduler.schedule();
5381
5804
  return;
@@ -5477,18 +5900,18 @@ function getWsIndex(routes) {
5477
5900
  wsIndexCache.set(routes, index);
5478
5901
  return index;
5479
5902
  }
5480
- function matchRoute(routes, method, path27) {
5903
+ function matchRoute(routes, method, path31) {
5481
5904
  const index = getHttpIndex(routes);
5482
5905
  const upper = method.toUpperCase();
5483
- const hit = matchByMethod(index, upper, path27);
5906
+ const hit = matchByMethod(index, upper, path31);
5484
5907
  if (hit) return hit;
5485
5908
  if (upper === "HEAD") {
5486
- return matchByMethod(index, "GET", path27);
5909
+ return matchByMethod(index, "GET", path31);
5487
5910
  }
5488
5911
  return null;
5489
5912
  }
5490
- function matchByMethod(index, method, path27) {
5491
- const staticHit = index.static.get(`${method}|${path27}`);
5913
+ function matchByMethod(index, method, path31) {
5914
+ const staticHit = index.static.get(`${method}|${path31}`);
5492
5915
  if (staticHit) {
5493
5916
  return { route: staticHit, params: {} };
5494
5917
  }
@@ -5497,31 +5920,31 @@ function matchByMethod(index, method, path27) {
5497
5920
  if (route.method !== method) {
5498
5921
  continue;
5499
5922
  }
5500
- const params = matchSegments(entry.segments, path27, route.paramNames, route.isCatchAll);
5923
+ const params = matchSegments(entry.segments, path31, route.paramNames, route.isCatchAll);
5501
5924
  if (params !== null) {
5502
5925
  return { route, params };
5503
5926
  }
5504
5927
  }
5505
5928
  return null;
5506
5929
  }
5507
- function matchWsRoute(wsRoutes, path27) {
5930
+ function matchWsRoute(wsRoutes, path31) {
5508
5931
  const index = getWsIndex(wsRoutes);
5509
- const staticHit = index.static.get(path27);
5932
+ const staticHit = index.static.get(path31);
5510
5933
  if (staticHit) {
5511
5934
  return { route: staticHit, params: {} };
5512
5935
  }
5513
5936
  for (const route of index.dynamics) {
5514
- const params = matchDynamicPath(route.urlPath, path27, route.paramNames, route.isCatchAll);
5937
+ const params = matchDynamicPath(route.urlPath, path31, route.paramNames, route.isCatchAll);
5515
5938
  if (params !== null) {
5516
5939
  return { route, params };
5517
5940
  }
5518
5941
  }
5519
5942
  return null;
5520
5943
  }
5521
- function findAllowedMethods(routes, path27) {
5944
+ function findAllowedMethods(routes, path31) {
5522
5945
  const index = getHttpIndex(routes);
5523
5946
  const methods = /* @__PURE__ */ new Set();
5524
- const staticMethods = index.methodsByStaticPath.get(path27);
5947
+ const staticMethods = index.methodsByStaticPath.get(path31);
5525
5948
  if (staticMethods) {
5526
5949
  for (const method of staticMethods) {
5527
5950
  methods.add(method);
@@ -5530,7 +5953,7 @@ function findAllowedMethods(routes, path27) {
5530
5953
  for (const entry of index.dynamics) {
5531
5954
  const params = matchSegments(
5532
5955
  entry.segments,
5533
- path27,
5956
+ path31,
5534
5957
  entry.route.paramNames,
5535
5958
  entry.route.isCatchAll
5536
5959
  );
@@ -5543,11 +5966,11 @@ function findAllowedMethods(routes, path27) {
5543
5966
  }
5544
5967
  return Array.from(methods);
5545
5968
  }
5546
- function matchDynamicPath(pattern, path27, paramNames, isCatchAll) {
5547
- return matchSegments(pattern.split("/").filter(Boolean), path27, paramNames, isCatchAll);
5969
+ function matchDynamicPath(pattern, path31, paramNames, isCatchAll) {
5970
+ return matchSegments(pattern.split("/").filter(Boolean), path31, paramNames, isCatchAll);
5548
5971
  }
5549
- function matchSegments(patternSegments, path27, paramNames, isCatchAll) {
5550
- const pathSegments = path27.split("/").filter(Boolean);
5972
+ function matchSegments(patternSegments, path31, paramNames, isCatchAll) {
5973
+ const pathSegments = path31.split("/").filter(Boolean);
5551
5974
  if (isCatchAll) {
5552
5975
  const nonCatchAllCount = patternSegments.length - 1;
5553
5976
  if (pathSegments.length <= nonCatchAllCount) {
@@ -5634,197 +6057,6 @@ var init_validateRouteModule = __esm({
5634
6057
  }
5635
6058
  });
5636
6059
 
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);
5769
- }
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 {
5780
- }
5781
- }
5782
- }
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);
5790
- }
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);
5799
- }
5800
- sourcePathCache.set(prodAbsPath, result);
5801
- return result;
5802
- }
5803
- function setDevOnDemandEnabled(enabled) {
5804
- state.enabled = enabled;
5805
- }
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"() {
5818
- "use strict";
5819
- init_compileDevRoutes();
5820
- init_generateSchemaFiles();
5821
- init_generateSchemaFiles();
5822
- init_collectImports();
5823
- state = createDevOnDemandState();
5824
- sourcePathCache = /* @__PURE__ */ new Map();
5825
- }
5826
- });
5827
-
5828
6060
  // src/loader/loadRouteModule.ts
5829
6061
  async function loadRouteModule(filePath, method, rootDir) {
5830
6062
  if (isDevOnDemandEnabled() && rootDir) {
@@ -6011,14 +6243,14 @@ var init_httpErrors = __esm({
6011
6243
  issues;
6012
6244
  };
6013
6245
  RouteNotFoundError = class extends FaapiError {
6014
- constructor(path27) {
6015
- super("ROUTE_NOT_FOUND", `Route not found: ${path27}`, 404);
6246
+ constructor(path31) {
6247
+ super("ROUTE_NOT_FOUND", `Route not found: ${path31}`, 404);
6016
6248
  this.name = "RouteNotFoundError";
6017
6249
  }
6018
6250
  };
6019
6251
  MethodNotAllowedError = class extends FaapiError {
6020
- constructor(method, path27, allowedMethods) {
6021
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path27}`, 405);
6252
+ constructor(method, path31, allowedMethods) {
6253
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path31}`, 405);
6022
6254
  this.allowedMethods = allowedMethods;
6023
6255
  this.name = "MethodNotAllowedError";
6024
6256
  }
@@ -6262,6 +6494,7 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
6262
6494
  };
6263
6495
  if (registries) {
6264
6496
  ctx.registries = registries;
6497
+ ctx.tasks = registries.taskHandle.get(ctx);
6265
6498
  }
6266
6499
  const extend = config?.extendContext;
6267
6500
  if (typeof extend === "function") {
@@ -6551,11 +6784,39 @@ async function toResponse(value, meta) {
6551
6784
  headers
6552
6785
  });
6553
6786
  }
6554
- var init_toResponse = __esm({
6555
- "src/response/toResponse.ts"() {
6787
+ var init_toResponse = __esm({
6788
+ "src/response/toResponse.ts"() {
6789
+ "use strict";
6790
+ init_isPlainObject();
6791
+ init_pendingMeta();
6792
+ }
6793
+ });
6794
+
6795
+ // src/task/taskRegistry.ts
6796
+ function createTaskRegistry() {
6797
+ let registry = /* @__PURE__ */ new Map();
6798
+ return {
6799
+ hydrate(tasks) {
6800
+ const next = /* @__PURE__ */ new Map();
6801
+ for (const task of tasks) {
6802
+ next.set(task.name, task);
6803
+ }
6804
+ registry = next;
6805
+ },
6806
+ get(name) {
6807
+ return registry.get(name);
6808
+ },
6809
+ list() {
6810
+ return Array.from(registry.values());
6811
+ },
6812
+ clear() {
6813
+ registry = /* @__PURE__ */ new Map();
6814
+ }
6815
+ };
6816
+ }
6817
+ var init_taskRegistry = __esm({
6818
+ "src/task/taskRegistry.ts"() {
6556
6819
  "use strict";
6557
- init_isPlainObject();
6558
- init_pendingMeta();
6559
6820
  }
6560
6821
  });
6561
6822
 
@@ -6681,17 +6942,35 @@ function createAgentHandleStore() {
6681
6942
  }
6682
6943
  };
6683
6944
  }
6945
+ function createTaskHandleStore() {
6946
+ let currentFactory = null;
6947
+ return {
6948
+ register(factory) {
6949
+ currentFactory = factory;
6950
+ },
6951
+ get(ctx) {
6952
+ if (currentFactory === null) return void 0;
6953
+ return currentFactory(ctx);
6954
+ },
6955
+ clear() {
6956
+ currentFactory = null;
6957
+ }
6958
+ };
6959
+ }
6684
6960
  function createAppRegistries() {
6685
6961
  const tool = createToolRegistry();
6686
6962
  const agent = createAgentRegistry(tool);
6687
6963
  const skill = createSkillRegistry();
6964
+ const task = createTaskRegistry();
6688
6965
  const agentHandle = createAgentHandleStore();
6689
- return { tool, agent, skill, agentHandle };
6966
+ const taskHandle = createTaskHandleStore();
6967
+ return { tool, agent, skill, task, agentHandle, taskHandle };
6690
6968
  }
6691
6969
  var defaultRegistries;
6692
6970
  var init_registries = __esm({
6693
6971
  "src/injection/registries.ts"() {
6694
6972
  "use strict";
6973
+ init_taskRegistry();
6695
6974
  defaultRegistries = createAppRegistries();
6696
6975
  }
6697
6976
  });
@@ -6758,6 +7037,9 @@ function getBuiltinInjectionValue(type, ctx, body) {
6758
7037
  // Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle
6759
7038
  case "agent":
6760
7039
  return ctx.registries ? ctx.registries.agentHandle.get(ctx) : getAgentHandle(ctx);
7040
+ // 任务子系统:注入 TaskClient(入队/查询);未注册工厂(无 app 编排)时 undefined
7041
+ case "tasks":
7042
+ return ctx.registries ? ctx.registries.taskHandle.get(ctx) : void 0;
6761
7043
  default:
6762
7044
  return void 0;
6763
7045
  }
@@ -6974,9 +7256,9 @@ async function validateInput(schemaPath, method, inputType, input) {
6974
7256
  function mapZodIssues(error) {
6975
7257
  return error.issues.map((issue) => {
6976
7258
  const code = mapZodCode(issue);
6977
- const path27 = issue.path.map(String).join(".") || "";
7259
+ const path31 = issue.path.map(String).join(".") || "";
6978
7260
  return {
6979
- path: path27,
7261
+ path: path31,
6980
7262
  code,
6981
7263
  expected: issue.expected ?? mapExpectedFromMessage(issue.message),
6982
7264
  received: issue.received ?? mapReceivedFromMessage(issue.message),
@@ -7474,9 +7756,9 @@ var init_wsHandler = __esm({
7474
7756
  });
7475
7757
 
7476
7758
  // src/server/handleWsUpgrade.ts
7477
- import fs16 from "fs";
7759
+ import fs17 from "fs";
7478
7760
  import { WebSocketServer, WebSocket } from "ws";
7479
- import path21 from "path";
7761
+ import path23 from "path";
7480
7762
  function getPathname(req) {
7481
7763
  const url = req.url ?? "/";
7482
7764
  const idx = url.indexOf("?");
@@ -7487,7 +7769,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
7487
7769
  const dist = getDevDist();
7488
7770
  if (dist) {
7489
7771
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
7490
- if (sourcePath && fs16.existsSync(sourcePath)) {
7772
+ if (sourcePath && fs17.existsSync(sourcePath)) {
7491
7773
  await ensureCompiled(sourcePath, rootDir, dist);
7492
7774
  }
7493
7775
  }
@@ -7601,7 +7883,7 @@ function attachWebSocket(options) {
7601
7883
  const finalHandler = async () => {
7602
7884
  let handlers;
7603
7885
  try {
7604
- const absoluteFilePath = path21.resolve(rootDir, route.filePath);
7886
+ const absoluteFilePath = path23.resolve(rootDir, route.filePath);
7605
7887
  handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
7606
7888
  } catch (err) {
7607
7889
  const reason = err instanceof Error ? err.message : String(err);
@@ -7677,7 +7959,7 @@ import {
7677
7959
  import { createSecureServer as createHttp2SecureServer } from "http2";
7678
7960
  import { readFileSync } from "fs";
7679
7961
  import { Readable as Readable3 } from "stream";
7680
- import path22 from "path";
7962
+ import path24 from "path";
7681
7963
  function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT, requestSignal) {
7682
7964
  const forwardedProto = req.headers["x-forwarded-proto"];
7683
7965
  const protocol = Array.isArray(forwardedProto) ? forwardedProto[0]?.split(",")[0]?.trim() ?? "http" : forwardedProto?.split(",")[0]?.trim() ?? "http";
@@ -7873,7 +8155,7 @@ function getRoutePaths(route, rootDir, dist) {
7873
8155
  let cached = routePathCache.get(route);
7874
8156
  if (!cached) {
7875
8157
  cached = {
7876
- absFilePath: path22.resolve(rootDir, route.filePath),
8158
+ absFilePath: path24.resolve(rootDir, route.filePath),
7877
8159
  schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
7878
8160
  };
7879
8161
  routePathCache.set(route, cached);
@@ -8027,10 +8309,513 @@ var init_startServer = __esm({
8027
8309
  }
8028
8310
  });
8029
8311
 
8030
- // src/cli/loadPlugins.ts
8031
- import path23 from "path";
8032
- import fs17 from "fs";
8312
+ // src/task/taskWorker.ts
8313
+ import { Worker } from "worker_threads";
8033
8314
  import { pathToFileURL as pathToFileURL2 } from "url";
8315
+ function safeConfig(config) {
8316
+ if (config === void 0 || config === null) return config;
8317
+ try {
8318
+ structuredClone(config);
8319
+ return config;
8320
+ } catch {
8321
+ }
8322
+ try {
8323
+ return JSON.parse(JSON.stringify(config));
8324
+ } catch {
8325
+ return void 0;
8326
+ }
8327
+ }
8328
+ function buildWrapperSource(moduleUrl) {
8329
+ return `
8330
+ import { parentPort } from 'node:worker_threads';
8331
+ import * as mod from '${moduleUrl}';
8332
+
8333
+ const run = mod.run;
8334
+ if (typeof run !== 'function') {
8335
+ parentPort.postMessage({ type: 'error', message: 'Task module has no run export' });
8336
+ } else {
8337
+ let controller = null;
8338
+ parentPort.on('message', async (msg) => {
8339
+ if (msg?.type === 'abort') {
8340
+ controller?.abort(new Error(msg.reason));
8341
+ return;
8342
+ }
8343
+ if (msg?.type !== 'run') return;
8344
+ controller = new AbortController();
8345
+ const { payload, taskCtx } = msg;
8346
+ try {
8347
+ const result = await run(payload, { ...taskCtx, signal: controller.signal });
8348
+ parentPort.postMessage({ type: 'done', result });
8349
+ } catch (err) {
8350
+ parentPort.postMessage({
8351
+ type: 'error',
8352
+ message: err instanceof Error ? err.message : String(err),
8353
+ });
8354
+ }
8355
+ });
8356
+ }
8357
+ `;
8358
+ }
8359
+ async function runTaskInWorker(options) {
8360
+ const { taskModulePath, payload, taskCtx, timeoutMs, externalSignal } = options;
8361
+ const killGraceMs = options.killGraceMs ?? KILL_GRACE_MS;
8362
+ const moduleUrl = pathToFileURL2(taskModulePath).href;
8363
+ const wrapperUrl = new URL(
8364
+ `data:text/javascript,${encodeURIComponent(buildWrapperSource(moduleUrl))}`
8365
+ );
8366
+ return new Promise((resolve3, reject) => {
8367
+ let worker;
8368
+ try {
8369
+ worker = new Worker(wrapperUrl);
8370
+ } catch (err) {
8371
+ reject(err);
8372
+ return;
8373
+ }
8374
+ let phase = "running";
8375
+ let cancelReason = "";
8376
+ let graceTimer;
8377
+ const finish = (settle) => {
8378
+ if (phase === "settled") return;
8379
+ phase = "settled";
8380
+ clearTimeout(timeoutTimer);
8381
+ if (graceTimer) clearTimeout(graceTimer);
8382
+ externalSignal?.removeEventListener("abort", onExternalAbort);
8383
+ void worker.terminate();
8384
+ settle();
8385
+ };
8386
+ const startCancel = (reason) => {
8387
+ if (phase !== "running") return;
8388
+ phase = "grace";
8389
+ cancelReason = reason;
8390
+ worker.postMessage({ type: "abort", reason });
8391
+ graceTimer = setTimeout(() => {
8392
+ finish(
8393
+ () => reject(new TaskCancelledError(`Task "${taskCtx.job.name}" ${reason} and was terminated`))
8394
+ );
8395
+ }, killGraceMs);
8396
+ };
8397
+ const onExternalAbort = () => startCancel("cancelled (driver stop timeout)");
8398
+ if (externalSignal) {
8399
+ if (externalSignal.aborted) {
8400
+ onExternalAbort();
8401
+ } else {
8402
+ externalSignal.addEventListener("abort", onExternalAbort, { once: true });
8403
+ }
8404
+ }
8405
+ const timeoutTimer = setTimeout(
8406
+ () => startCancel(`timed out after ${timeoutMs}ms`),
8407
+ timeoutMs
8408
+ );
8409
+ worker.on("message", (msg) => {
8410
+ if (phase === "grace") {
8411
+ if (msg?.type === "done") {
8412
+ finish(
8413
+ () => reject(
8414
+ new TaskCancelledError(
8415
+ `Task "${taskCtx.job.name}" ${cancelReason} (task completed after the timeout)`
8416
+ )
8417
+ )
8418
+ );
8419
+ } else if (msg?.type === "error") {
8420
+ finish(() => reject(new TaskCancelledError(msg.message ?? cancelReason)));
8421
+ }
8422
+ return;
8423
+ }
8424
+ if (phase !== "running") return;
8425
+ if (msg?.type === "done") {
8426
+ finish(() => resolve3(msg.result));
8427
+ } else if (msg?.type === "error") {
8428
+ finish(() => reject(new Error(msg.message ?? "task worker error")));
8429
+ }
8430
+ });
8431
+ worker.on("error", (err) => {
8432
+ finish(() => reject(err));
8433
+ });
8434
+ worker.on("exit", (code) => {
8435
+ if (phase === "grace") {
8436
+ finish(() => reject(new TaskCancelledError(`Task "${taskCtx.job.name}" ${cancelReason}`)));
8437
+ } else if (phase === "running") {
8438
+ finish(() => reject(new Error(`Task worker exited unexpectedly (code ${code})`)));
8439
+ }
8440
+ });
8441
+ try {
8442
+ worker.postMessage({
8443
+ type: "run",
8444
+ payload,
8445
+ taskCtx: { config: safeConfig(taskCtx.config), job: taskCtx.job }
8446
+ });
8447
+ } catch (err) {
8448
+ finish(
8449
+ () => reject(
8450
+ new Error(`Task "${taskCtx.job.name}" payload/config is not cloneable: ${String(err)}`)
8451
+ )
8452
+ );
8453
+ }
8454
+ });
8455
+ }
8456
+ var KILL_GRACE_MS, TaskCancelledError;
8457
+ var init_taskWorker = __esm({
8458
+ "src/task/taskWorker.ts"() {
8459
+ "use strict";
8460
+ KILL_GRACE_MS = 5e3;
8461
+ TaskCancelledError = class extends Error {
8462
+ constructor(message) {
8463
+ super(message);
8464
+ this.name = "TaskCancelledError";
8465
+ }
8466
+ };
8467
+ }
8468
+ });
8469
+
8470
+ // src/task/taskQueue.ts
8471
+ import path25 from "path";
8472
+ function createTaskQueue(deps) {
8473
+ const { registry, rootDir, driver } = deps;
8474
+ const records = /* @__PURE__ */ new Map();
8475
+ const moduleCache2 = /* @__PURE__ */ new Map();
8476
+ const schemaCache = /* @__PURE__ */ new Map();
8477
+ let started = false;
8478
+ let stopped = false;
8479
+ const loadTaskModule = deps.loadTaskModule ?? (async (filePath) => await import(filePath));
8480
+ const loadPayloadSchema = deps.loadPayloadSchema ?? (async (filePath) => {
8481
+ const zodPath = path25.join(path25.dirname(filePath), "zod.js");
8482
+ try {
8483
+ const mod = await import(zodPath);
8484
+ const schemaKey = Object.keys(mod).find((k) => k.endsWith("Schema"));
8485
+ return schemaKey ? mod[schemaKey] : void 0;
8486
+ } catch {
8487
+ return void 0;
8488
+ }
8489
+ });
8490
+ function assertKnownTask(name) {
8491
+ const meta = registry.get(name);
8492
+ if (!meta) {
8493
+ const known = registry.list().map((t) => t.name).join(", ") || "(none)";
8494
+ throw new Error(`[faapi] Unknown task "${name}". Registered tasks: ${known}`);
8495
+ }
8496
+ }
8497
+ async function validatePayload(name, payload) {
8498
+ if (!schemaCache.has(name)) {
8499
+ const meta = registry.get(name);
8500
+ schemaCache.set(
8501
+ name,
8502
+ await loadPayloadSchema(path25.resolve(rootDir, meta.filePath)).catch(() => void 0)
8503
+ );
8504
+ }
8505
+ const schema = schemaCache.get(name);
8506
+ if (!schema || typeof schema.safeParse !== "function") {
8507
+ return payload;
8508
+ }
8509
+ const result = schema.safeParse(payload);
8510
+ if (!result.success) {
8511
+ throw new ValidationError(
8512
+ `Task payload validation failed for "${name}": ${JSON.stringify(result.error)}`,
8513
+ []
8514
+ );
8515
+ }
8516
+ return result.data;
8517
+ }
8518
+ async function runJob(job) {
8519
+ const meta = registry.get(job.name);
8520
+ if (!meta) throw new Error(`[faapi] Unknown task "${job.name}" at worker dispatch`);
8521
+ const record = records.get(job.id) ?? {
8522
+ id: job.id,
8523
+ name: job.name,
8524
+ payload: job.payload,
8525
+ status: "pending",
8526
+ attempts: 0,
8527
+ createdAt: Date.now()
8528
+ };
8529
+ record.attempts = job.attempt;
8530
+ record.status = "running";
8531
+ record.error = void 0;
8532
+ records.set(job.id, record);
8533
+ try {
8534
+ let result;
8535
+ if (meta.timeoutMs !== void 0 && meta.timeoutMs > 0) {
8536
+ result = await (deps.runIsolated ?? runTaskInWorker)({
8537
+ taskModulePath: path25.resolve(rootDir, meta.filePath),
8538
+ payload: job.payload,
8539
+ taskCtx: {
8540
+ config: deps.config,
8541
+ job: { id: job.id, name: job.name, attempt: job.attempt }
8542
+ },
8543
+ timeoutMs: meta.timeoutMs,
8544
+ externalSignal: job.signal
8545
+ });
8546
+ } else {
8547
+ let mod = moduleCache2.get(job.name);
8548
+ if (!mod) {
8549
+ mod = await loadTaskModule(path25.resolve(rootDir, meta.filePath));
8550
+ moduleCache2.set(job.name, mod);
8551
+ }
8552
+ if (typeof mod.run !== "function") {
8553
+ throw new Error(`Task "${job.name}" module has no run export`);
8554
+ }
8555
+ const taskCtx = {
8556
+ signal: job.signal,
8557
+ config: deps.config,
8558
+ job: { id: job.id, name: job.name, attempt: job.attempt }
8559
+ };
8560
+ result = await mod.run(job.payload, taskCtx);
8561
+ }
8562
+ record.status = "done";
8563
+ record.result = result;
8564
+ return result;
8565
+ } catch (err) {
8566
+ const cancelled = err instanceof TaskCancelledError || job.signal.aborted;
8567
+ record.status = cancelled ? "cancelled" : "failed";
8568
+ record.error = err instanceof Error ? err.message : String(err);
8569
+ if (deps.onFailed) {
8570
+ void Promise.resolve().then(
8571
+ () => deps.onFailed({
8572
+ task: job.name,
8573
+ jobId: job.id,
8574
+ attempt: job.attempt,
8575
+ willRetry: job.attempt <= (meta.retries ?? 0),
8576
+ cancelled,
8577
+ error: record.error
8578
+ })
8579
+ ).catch(() => {
8580
+ });
8581
+ }
8582
+ throw err;
8583
+ }
8584
+ }
8585
+ async function startWorkers() {
8586
+ for (const meta of registry.list()) {
8587
+ await driver.startWorker(meta.name, {
8588
+ concurrency: meta.concurrency ?? 1,
8589
+ process: runJob
8590
+ });
8591
+ }
8592
+ }
8593
+ const queue = {
8594
+ async enqueue(name, payload = {}, opts) {
8595
+ if (stopped) {
8596
+ throw new Error("[faapi] Task queue is stopped and no longer accepts jobs");
8597
+ }
8598
+ assertKnownTask(name);
8599
+ const data = await validatePayload(name, payload);
8600
+ const meta = registry.get(name);
8601
+ const id = await driver.enqueue(name, data, {
8602
+ delayMs: opts?.delayMs,
8603
+ retries: meta.retries ?? 0,
8604
+ dedupId: opts?.dedupId
8605
+ });
8606
+ if (!records.has(id)) {
8607
+ records.set(id, {
8608
+ id,
8609
+ name,
8610
+ payload: data,
8611
+ status: "pending",
8612
+ attempts: 0,
8613
+ createdAt: Date.now(),
8614
+ ...opts?.delayMs ? { runAt: Date.now() + opts.delayMs } : {}
8615
+ });
8616
+ }
8617
+ return { id };
8618
+ },
8619
+ list(name) {
8620
+ const snapshot = [];
8621
+ for (const job of records.values()) {
8622
+ if (name !== void 0 && job.name !== name) continue;
8623
+ snapshot.push({ ...job });
8624
+ }
8625
+ return snapshot;
8626
+ },
8627
+ async listQueued(name) {
8628
+ if (!driver.list) {
8629
+ throw new Error(
8630
+ "[faapi] Task driver does not support listing queued tasks (TaskDriver.list is not implemented). Use the queue system's own management tools, or see driverTypes.md for per-driver capability."
8631
+ );
8632
+ }
8633
+ const queued = await driver.list({ name, limit: 50 });
8634
+ const merged = /* @__PURE__ */ new Map();
8635
+ for (const record of queued) {
8636
+ if (name !== void 0 && record.name !== name) continue;
8637
+ merged.set(record.id, {
8638
+ id: record.id,
8639
+ name: record.name,
8640
+ payload: record.payload,
8641
+ status: record.status,
8642
+ attempts: record.attempts,
8643
+ createdAt: record.createdAt,
8644
+ ...record.result !== void 0 ? { result: record.result } : {},
8645
+ ...record.error !== void 0 ? { error: record.error } : {},
8646
+ ...record.runAt !== void 0 ? { runAt: record.runAt } : {}
8647
+ });
8648
+ }
8649
+ for (const local of records.values()) {
8650
+ if (name !== void 0 && local.name !== name) continue;
8651
+ merged.set(local.id, { ...local });
8652
+ }
8653
+ return [...merged.values()];
8654
+ },
8655
+ async cancel(name, id) {
8656
+ if (!driver.cancel) {
8657
+ throw new Error(
8658
+ "[faapi] Task driver does not support cancelling tasks (TaskDriver.cancel is not implemented)."
8659
+ );
8660
+ }
8661
+ await driver.cancel(name, id);
8662
+ const record = records.get(id);
8663
+ if (record) record.status = "cancelled";
8664
+ },
8665
+ async retry(name, id) {
8666
+ if (!driver.retry) {
8667
+ throw new Error(
8668
+ "[faapi] Task driver does not support retrying tasks (TaskDriver.retry is not implemented)."
8669
+ );
8670
+ }
8671
+ await driver.retry(name, id);
8672
+ const record = records.get(id);
8673
+ if (record) record.status = "pending";
8674
+ },
8675
+ async start() {
8676
+ if (stopped) {
8677
+ throw new Error("[faapi] Task queue is stopped and cannot be restarted");
8678
+ }
8679
+ if (started) return;
8680
+ started = true;
8681
+ await startWorkers();
8682
+ },
8683
+ async stop(timeoutMs = 1e4) {
8684
+ stopped = true;
8685
+ await driver.stop(timeoutMs);
8686
+ },
8687
+ async reload() {
8688
+ await driver.stopWorkers?.();
8689
+ await startWorkers();
8690
+ },
8691
+ invalidateModules() {
8692
+ moduleCache2.clear();
8693
+ },
8694
+ invalidateSchemas() {
8695
+ schemaCache.clear();
8696
+ }
8697
+ };
8698
+ return queue;
8699
+ }
8700
+ var init_taskQueue = __esm({
8701
+ "src/task/taskQueue.ts"() {
8702
+ "use strict";
8703
+ init_httpErrors();
8704
+ init_taskWorker();
8705
+ }
8706
+ });
8707
+
8708
+ // src/task/loadTaskDriver.ts
8709
+ async function loadTaskDriver(driver, driverOptions) {
8710
+ if (driver === void 0) {
8711
+ throw new Error(
8712
+ "[faapi] config.task.driver is required when tasks are defined. Install @faapi/task-pgboss or @faapi/task-bullmq and set `task.driver` to 'pgboss' or 'bullmq' (or pass a TaskDriver instance)."
8713
+ );
8714
+ }
8715
+ if (driver === "memory") {
8716
+ throw new Error(
8717
+ "[faapi] The built-in memory task driver has been removed. Migrate to a persistent driver: install @faapi/task-pgboss or @faapi/task-bullmq and set `task.driver` to 'pgboss' or 'bullmq'."
8718
+ );
8719
+ }
8720
+ if (typeof driver === "object") {
8721
+ return driver;
8722
+ }
8723
+ if (driver !== "pgboss" && driver !== "bullmq") {
8724
+ throw new Error(
8725
+ `[faapi] Unknown task driver "${driver}". Supported: 'pgboss', 'bullmq', or a TaskDriver instance.`
8726
+ );
8727
+ }
8728
+ const specifier = `@faapi/task-${driver}`;
8729
+ let mod;
8730
+ try {
8731
+ mod = await import(
8732
+ /* @vite-ignore */
8733
+ specifier
8734
+ );
8735
+ } catch {
8736
+ throw new Error(
8737
+ `[faapi] Task driver "${driver}" requires package "${specifier}" to be installed in your project.`
8738
+ );
8739
+ }
8740
+ const factoryName = driver === "pgboss" ? "createPgBossDriver" : "createBullMQDriver";
8741
+ const factory = mod[factoryName];
8742
+ if (typeof factory !== "function") {
8743
+ throw new Error(
8744
+ `[faapi] Package "${specifier}" does not export ${factoryName}() \u2014 check the package version.`
8745
+ );
8746
+ }
8747
+ return factory(driverOptions);
8748
+ }
8749
+ var init_loadTaskDriver = __esm({
8750
+ "src/task/loadTaskDriver.ts"() {
8751
+ "use strict";
8752
+ }
8753
+ });
8754
+
8755
+ // src/task/idleTaskDriver.ts
8756
+ function createIdleTaskDriver() {
8757
+ return {
8758
+ enqueue() {
8759
+ return Promise.reject(
8760
+ new Error(
8761
+ "[faapi] New task(s) were registered without a queue driver. Set config.task.driver to 'pgboss' or 'bullmq' (install the matching @faapi/task-* package) and restart the server."
8762
+ )
8763
+ );
8764
+ },
8765
+ startWorker() {
8766
+ },
8767
+ async stop() {
8768
+ },
8769
+ async stopWorkers() {
8770
+ }
8771
+ };
8772
+ }
8773
+ var init_idleTaskDriver = __esm({
8774
+ "src/task/idleTaskDriver.ts"() {
8775
+ "use strict";
8776
+ }
8777
+ });
8778
+
8779
+ // src/task/cronScheduler.ts
8780
+ import { Cron } from "croner";
8781
+ function createCronScheduler(registry, enqueue) {
8782
+ let schedules = [];
8783
+ return {
8784
+ start() {
8785
+ this.stop();
8786
+ for (const task of registry.list()) {
8787
+ if (!task.cron) continue;
8788
+ const schedule = new Cron(task.cron, () => {
8789
+ const runAt = schedule.currentRun();
8790
+ void Promise.resolve().then(
8791
+ () => enqueue(task.name, {
8792
+ dedupId: runAt ? `cron:${task.name}:${runAt.toISOString()}` : void 0
8793
+ })
8794
+ ).catch((err) => {
8795
+ console.error(`[faapi] Cron enqueue failed for task "${task.name}":`, err);
8796
+ });
8797
+ });
8798
+ schedules.push(schedule);
8799
+ }
8800
+ },
8801
+ stop() {
8802
+ for (const schedule of schedules) {
8803
+ schedule.stop();
8804
+ }
8805
+ schedules = [];
8806
+ }
8807
+ };
8808
+ }
8809
+ var init_cronScheduler = __esm({
8810
+ "src/task/cronScheduler.ts"() {
8811
+ "use strict";
8812
+ }
8813
+ });
8814
+
8815
+ // src/cli/loadPlugins.ts
8816
+ import path26 from "path";
8817
+ import fs18 from "fs";
8818
+ import { pathToFileURL as pathToFileURL3 } from "url";
8034
8819
  async function loadPlugins(declarations, ctx, rootDir, dist) {
8035
8820
  const handlerWrappers = [];
8036
8821
  const upgradeWrappers = [];
@@ -8092,38 +8877,38 @@ async function loadPlugins(declarations, ctx, rootDir, dist) {
8092
8877
  return { handlerWrappers, upgradeWrappers, failures };
8093
8878
  }
8094
8879
  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;
8880
+ const base = path26.isAbsolute(specifier) ? specifier : path26.resolve(baseDir, specifier);
8881
+ if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs18.existsSync(base)) return base;
8097
8882
  for (const ext of [".ts", ".js"]) {
8098
- if (fs17.existsSync(base + ext)) return base + ext;
8883
+ if (fs18.existsSync(base + ext)) return base + ext;
8099
8884
  }
8100
8885
  for (const indexExt of ["/index.ts", "/index.js"]) {
8101
- if (fs17.existsSync(base + indexExt)) return base + indexExt;
8886
+ if (fs18.existsSync(base + indexExt)) return base + indexExt;
8102
8887
  }
8103
8888
  return null;
8104
8889
  }
8105
8890
  function mirrorProductPath(sourcePath, baseDir, distDir) {
8106
- let rel = path23.relative(baseDir, sourcePath).replace(/\\/g, "/");
8891
+ let rel = path26.relative(baseDir, sourcePath).replace(/\\/g, "/");
8107
8892
  if (rel.startsWith("src/")) rel = rel.slice(4);
8108
- return path23.resolve(distDir, rel.replace(TS_EXTS, ".js"));
8893
+ return path26.resolve(distDir, rel.replace(TS_EXTS, ".js"));
8109
8894
  }
8110
8895
  function isSourceNewer(sourcePath, productPath) {
8111
8896
  try {
8112
- return fs17.statSync(sourcePath).mtimeMs > fs17.statSync(productPath).mtimeMs;
8897
+ return fs18.statSync(sourcePath).mtimeMs > fs18.statSync(productPath).mtimeMs;
8113
8898
  } catch {
8114
8899
  return true;
8115
8900
  }
8116
8901
  }
8117
8902
  async function importPluginModule(specifier, baseDir, dist) {
8118
8903
  const isRelative = specifier.startsWith("./") || specifier.startsWith("../");
8119
- if (!isRelative && !path23.isAbsolute(specifier)) {
8904
+ if (!isRelative && !path26.isAbsolute(specifier)) {
8120
8905
  return import(specifier);
8121
8906
  }
8122
- const distDir = dist ? path23.resolve(baseDir, dist) : null;
8907
+ const distDir = dist ? path26.resolve(baseDir, dist) : null;
8123
8908
  const sourcePath = resolveLocalPluginSource(specifier, baseDir);
8124
8909
  const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
8125
- if (productPath !== null && fs17.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
8126
- return import(pathToFileURL2(productPath).href);
8910
+ if (productPath !== null && fs18.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
8911
+ return import(pathToFileURL3(productPath).href);
8127
8912
  }
8128
8913
  if (sourcePath && TS_EXTS.test(sourcePath)) {
8129
8914
  if (!isDevOnDemandEnabled() || !distDir) {
@@ -8131,25 +8916,25 @@ async function importPluginModule(specifier, baseDir, dist) {
8131
8916
  `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
8917
  );
8133
8918
  }
8134
- const relFromRoot = path23.relative(baseDir, sourcePath).replace(/\\/g, "/");
8919
+ const relFromRoot = path26.relative(baseDir, sourcePath).replace(/\\/g, "/");
8135
8920
  if (relFromRoot.startsWith("src/")) {
8136
8921
  await ensureCompiled(sourcePath, baseDir, dist);
8137
8922
  } else {
8138
8923
  await compileProjectModules([sourcePath], baseDir, dist);
8139
8924
  }
8140
- if (productPath && fs17.existsSync(productPath)) {
8141
- return import(pathToFileURL2(productPath).href);
8925
+ if (productPath && fs18.existsSync(productPath)) {
8926
+ return import(pathToFileURL3(productPath).href);
8142
8927
  }
8143
8928
  }
8144
8929
  if (sourcePath && JS_EXTS.test(sourcePath)) {
8145
- return import(pathToFileURL2(sourcePath).href);
8930
+ return import(pathToFileURL3(sourcePath).href);
8146
8931
  }
8147
8932
  const candidates = [
8148
8933
  `${specifier}.ts`,
8149
8934
  `${specifier}/index.ts`,
8150
8935
  `${specifier}.js`,
8151
8936
  `${specifier}/index.js`
8152
- ].map((c) => path23.isAbsolute(c) ? c : path23.join(baseDir, c));
8937
+ ].map((c) => path26.isAbsolute(c) ? c : path26.join(baseDir, c));
8153
8938
  throw new Error(
8154
8939
  `Cannot find local plugin "${specifier}" (resolved from ${baseDir}). Expected one of:
8155
8940
  ${candidates.join("\n ")}
@@ -8184,12 +8969,12 @@ var init_loadPlugins = __esm({
8184
8969
  });
8185
8970
 
8186
8971
  // src/cli/createAppCore.ts
8187
- import fs18 from "fs";
8188
- import path24 from "path";
8972
+ import fs19 from "fs";
8973
+ import path27 from "path";
8189
8974
  import { PassThrough, Readable as Readable4 } from "stream";
8190
8975
  async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries) {
8191
- const toolsPath = path24.resolve(rootDir, dist, TOOLS_FILE2);
8192
- if (!fs18.existsSync(toolsPath)) {
8976
+ const toolsPath = path27.resolve(rootDir, dist, TOOLS_FILE2);
8977
+ if (!fs19.existsSync(toolsPath)) {
8193
8978
  return [];
8194
8979
  }
8195
8980
  const serialized = await importWithCacheBust(toolsPath);
@@ -8198,8 +8983,8 @@ async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries
8198
8983
  return hydrated;
8199
8984
  }
8200
8985
  async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistries) {
8201
- const agentsPath = path24.resolve(rootDir, dist, AGENTS_FILE2);
8202
- if (!fs18.existsSync(agentsPath)) {
8986
+ const agentsPath = path27.resolve(rootDir, dist, AGENTS_FILE2);
8987
+ if (!fs19.existsSync(agentsPath)) {
8203
8988
  return [];
8204
8989
  }
8205
8990
  const serialized = await importWithCacheBust(agentsPath);
@@ -8207,6 +8992,22 @@ async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistrie
8207
8992
  registries.agent.hydrate(hydrated);
8208
8993
  return hydrated;
8209
8994
  }
8995
+ function getTaskDriverOptions(config) {
8996
+ if (!config?.task) return void 0;
8997
+ if (config.task.driver === "pgboss") return config.task.pgboss;
8998
+ if (config.task.driver === "bullmq") return config.task.bullmq;
8999
+ return void 0;
9000
+ }
9001
+ async function loadAndHydrateTasks(rootDir, dist, registries = defaultRegistries) {
9002
+ const tasksPath = path27.resolve(rootDir, dist, TASKS_FILE);
9003
+ if (!fs19.existsSync(tasksPath)) {
9004
+ return [];
9005
+ }
9006
+ const serialized = await importWithCacheBust(tasksPath);
9007
+ const hydrated = hydrateTasks(serialized.tasks ?? []);
9008
+ registries.task.hydrate(hydrated);
9009
+ return hydrated;
9010
+ }
8210
9011
  function getCurrentApp() {
8211
9012
  return globalThis[APP_INSTANCE_KEY] ?? null;
8212
9013
  }
@@ -8239,8 +9040,8 @@ function isFaapiConfigKey(key) {
8239
9040
  async function createAppBase(options) {
8240
9041
  const rootDir = options?.rootDir ?? process.cwd();
8241
9042
  const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
8242
- const routesPath = path24.resolve(rootDir, dist, ROUTES_FILE);
8243
- if (!fs18.existsSync(routesPath)) {
9043
+ const routesPath = path27.resolve(rootDir, dist, ROUTES_FILE);
9044
+ if (!fs19.existsSync(routesPath)) {
8244
9045
  throw new Error(
8245
9046
  `[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
9047
  );
@@ -8262,6 +9063,29 @@ async function createAppBase(options) {
8262
9063
  const registries = createAppRegistries();
8263
9064
  const tools = await loadAndHydrateTools(rootDir, dist, registries);
8264
9065
  const agents = await loadAndHydrateAgents(rootDir, dist, registries);
9066
+ const taskMetas = await loadAndHydrateTasks(rootDir, dist, registries);
9067
+ const taskDriver = taskMetas.length ? await loadTaskDriver(config?.task?.driver, getTaskDriverOptions(config)) : createIdleTaskDriver();
9068
+ const taskQueue = createTaskQueue({
9069
+ registry: registries.task,
9070
+ rootDir,
9071
+ config,
9072
+ driver: taskDriver,
9073
+ onFailed: config?.task?.onFailed
9074
+ });
9075
+ const cronScheduler = createCronScheduler(
9076
+ registries.task,
9077
+ (name, opts) => taskQueue.enqueue(name, void 0, opts)
9078
+ );
9079
+ const taskEnabled = process.env.FAAPI_TASKS_DISABLED === "1" ? false : config?.task?.enabled ?? true;
9080
+ const stopTaskRuntime = async () => {
9081
+ cronScheduler.stop();
9082
+ await taskQueue.stop(config?.task?.shutdownTimeoutMs ?? 1e4);
9083
+ };
9084
+ if (taskEnabled) {
9085
+ taskQueue.start();
9086
+ cronScheduler.start();
9087
+ }
9088
+ registries.taskHandle.register(() => taskQueue);
8265
9089
  const pluginConfig = config ? Object.fromEntries(Object.entries(config).filter(([k]) => !isFaapiConfigKey(k))) : {};
8266
9090
  const { server, routesRef } = createServer({
8267
9091
  routes: sorted,
@@ -8303,14 +9127,22 @@ async function createAppBase(options) {
8303
9127
  routes: sorted,
8304
9128
  wsRoutes,
8305
9129
  rootDir,
9130
+ tasks: taskQueue,
8306
9131
  async listen(listenPort) {
8307
9132
  const envPort = process.env.PORT ? Number(process.env.PORT) : void 0;
8308
9133
  const actualPort = listenPort ?? options?.port ?? envPort ?? DEFAULT_PORT;
8309
9134
  if (config?.lifecycle?.onBoot) {
8310
9135
  try {
8311
- await config.lifecycle.onBoot({ rootDir, routes: sorted, server, registries });
9136
+ await config.lifecycle.onBoot({
9137
+ rootDir,
9138
+ routes: sorted,
9139
+ server,
9140
+ registries,
9141
+ tasks: taskQueue
9142
+ });
8312
9143
  console.log("- onBoot hook executed");
8313
9144
  } catch (err) {
9145
+ await stopTaskRuntime();
8314
9146
  const message = err instanceof Error ? err.message : String(err);
8315
9147
  console.error(`[faapi] onBoot hook failed: ${message}`);
8316
9148
  throw err;
@@ -8358,9 +9190,22 @@ async function createAppBase(options) {
8358
9190
  console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
8359
9191
  }
8360
9192
  }
9193
+ if (taskMetas.length > 0) {
9194
+ console.log(`- Loaded ${taskMetas.length} task(s):`);
9195
+ for (const taskMeta of taskMetas) {
9196
+ const schedule = taskMeta.cron ? ` cron=${taskMeta.cron}` : "";
9197
+ console.log(` ${taskMeta.name}${schedule} ${taskMeta.filePath}`);
9198
+ }
9199
+ }
8361
9200
  registerDefaultShutdownHandlers();
8362
9201
  if (config?.lifecycle?.onReady) {
8363
- await config.lifecycle.onReady({ rootDir, routes: sorted, server, registries });
9202
+ await config.lifecycle.onReady({
9203
+ rootDir,
9204
+ routes: sorted,
9205
+ server,
9206
+ registries,
9207
+ tasks: taskQueue
9208
+ });
8364
9209
  console.log("- onReady hook executed");
8365
9210
  }
8366
9211
  app.server = server;
@@ -8453,13 +9298,22 @@ async function createAppBase(options) {
8453
9298
  closed = true;
8454
9299
  const s = server;
8455
9300
  s.closeIdleConnections?.();
9301
+ await stopTaskRuntime();
8456
9302
  if (config?.lifecycle?.onClose) {
8457
- await config.lifecycle.onClose({ rootDir, routes: sorted, server, registries });
9303
+ await config.lifecycle.onClose({
9304
+ rootDir,
9305
+ routes: sorted,
9306
+ server,
9307
+ registries,
9308
+ tasks: taskQueue
9309
+ });
8458
9310
  }
8459
9311
  registries.tool.clear();
8460
9312
  registries.agent.clear();
8461
9313
  registries.skill.clear();
9314
+ registries.task.clear();
8462
9315
  registries.agentHandle.clear();
9316
+ registries.taskHandle.clear();
8463
9317
  if (!server.listening) {
8464
9318
  app.server = null;
8465
9319
  if (getCurrentApp() === app) setCurrentApp(null);
@@ -8496,6 +9350,7 @@ async function createAppBase(options) {
8496
9350
  registries,
8497
9351
  dist,
8498
9352
  patterns: ROUTE_PATTERNS,
9353
+ taskQueue,
8499
9354
  server,
8500
9355
  routesRef,
8501
9356
  config,
@@ -8522,6 +9377,11 @@ var init_createAppCore = __esm({
8522
9377
  init_generateRoutes();
8523
9378
  init_generateToolArtifacts();
8524
9379
  init_generateAgentArtifacts();
9380
+ init_generateTaskArtifacts();
9381
+ init_taskQueue();
9382
+ init_loadTaskDriver();
9383
+ init_idleTaskDriver();
9384
+ init_cronScheduler();
8525
9385
  init_loadPlugins();
8526
9386
  init_importWithCacheBust();
8527
9387
  init_registries();
@@ -8547,12 +9407,14 @@ var init_createAppCore = __esm({
8547
9407
  "logger",
8548
9408
  "http2",
8549
9409
  "trustedProxy",
8550
- "response"
9410
+ "response",
9411
+ "task"
8551
9412
  ]);
8552
9413
  }
8553
9414
  });
8554
9415
 
8555
9416
  // src/cli/createDevApp.ts
9417
+ import path28 from "path";
8556
9418
  async function createDevApp(options) {
8557
9419
  const { app, ctx } = await createAppBase(options);
8558
9420
  const devApp = app;
@@ -8597,6 +9459,19 @@ async function createDevApp(options) {
8597
9459
  await generateAgentArtifacts(agents, ctx.rootDir, ctx.dist);
8598
9460
  await loadAndHydrateAgents(ctx.rootDir, ctx.dist, ctx.registries);
8599
9461
  };
9462
+ devApp.reloadTasks = async () => {
9463
+ setLoadTimestamp(Date.now());
9464
+ invalidateProgramCache();
9465
+ const tasks = await scanTasks(ctx.rootDir, TASK_PATTERNS);
9466
+ for (const task of tasks) {
9467
+ await ensureCompiled(path28.resolve(ctx.rootDir, task.filePath), ctx.rootDir, ctx.dist);
9468
+ }
9469
+ await generateTaskArtifacts(tasks, ctx.rootDir, ctx.dist);
9470
+ ctx.taskQueue.invalidateModules();
9471
+ ctx.taskQueue.invalidateSchemas();
9472
+ await ctx.taskQueue.reload();
9473
+ await loadAndHydrateTasks(ctx.rootDir, ctx.dist, ctx.registries);
9474
+ };
8600
9475
  return devApp;
8601
9476
  }
8602
9477
  var init_createDevApp = __esm({
@@ -8611,6 +9486,9 @@ var init_createDevApp = __esm({
8611
9486
  init_scanAgents();
8612
9487
  init_scanAgents();
8613
9488
  init_generateAgentArtifacts();
9489
+ init_scanTasks();
9490
+ init_generateTaskArtifacts();
9491
+ init_compileOnDemand();
8614
9492
  init_loadMiddlewares();
8615
9493
  init_createProgram();
8616
9494
  init_validateInput();
@@ -8625,9 +9503,10 @@ __export(devCommand_exports, {
8625
9503
  devCommand: () => devCommand,
8626
9504
  generateAgentArtifactsForDev: () => generateAgentArtifactsForDev,
8627
9505
  generateRouteArtifacts: () => generateRouteArtifacts,
9506
+ generateTaskArtifactsForDev: () => generateTaskArtifactsForDev,
8628
9507
  generateToolArtifactsForDev: () => generateToolArtifactsForDev
8629
9508
  });
8630
- import path25 from "path";
9509
+ import path29 from "path";
8631
9510
  async function devCommand(options) {
8632
9511
  const rootDir = process.cwd();
8633
9512
  if (!process.env.NODE_ENV) process.env.NODE_ENV = "development";
@@ -8646,6 +9525,8 @@ async function devCommand(options) {
8646
9525
  await generateToolArtifactsForDev(rootDir, devDist);
8647
9526
  console.log("- Generating agent manifest...");
8648
9527
  await generateAgentArtifactsForDev(rootDir, devDist);
9528
+ console.log("- Generating task artifacts...");
9529
+ await generateTaskArtifactsForDev(rootDir, devDist);
8649
9530
  console.log("- Starting dev app...");
8650
9531
  const app = await createDevApp({ rootDir, port: options?.port });
8651
9532
  await app.listen();
@@ -8654,7 +9535,7 @@ async function devCommand(options) {
8654
9535
  async function generateRouteArtifacts(rootDir, patterns, dist) {
8655
9536
  const { routes, wsRoutes } = await scanRoutes(rootDir, patterns, dist);
8656
9537
  const sorted = sortRoutes(routes);
8657
- const routesPath = path25.resolve(rootDir, dist, ROUTES_FILE2);
9538
+ const routesPath = path29.resolve(rootDir, dist, ROUTES_FILE2);
8658
9539
  const serialized = serializeRoutes(sorted, wsRoutes, rootDir, dist);
8659
9540
  await writeRoutesModule(serialized, routesPath);
8660
9541
  }
@@ -8666,6 +9547,13 @@ async function generateAgentArtifactsForDev(rootDir, dist) {
8666
9547
  const agents = await scanAgents(rootDir, DEFAULT_AGENT_PATTERNS);
8667
9548
  await generateAgentArtifacts(agents, rootDir, dist);
8668
9549
  }
9550
+ async function generateTaskArtifactsForDev(rootDir, dist) {
9551
+ const tasks = await scanTasks(rootDir, TASK_PATTERNS);
9552
+ for (const task of tasks) {
9553
+ await ensureCompiled(path29.resolve(rootDir, task.filePath), rootDir, dist);
9554
+ }
9555
+ await generateTaskArtifacts(tasks, rootDir, dist);
9556
+ }
8669
9557
  var DEV_DIST, ROUTES_FILE2;
8670
9558
  var init_devCommand = __esm({
8671
9559
  "src/cli/devCommand.ts"() {
@@ -8680,6 +9568,9 @@ var init_devCommand = __esm({
8680
9568
  init_scanAgents();
8681
9569
  init_scanAgents();
8682
9570
  init_generateAgentArtifacts();
9571
+ init_scanTasks();
9572
+ init_generateTaskArtifacts();
9573
+ init_compileOnDemand();
8683
9574
  init_loadConfig();
8684
9575
  init_loadEnv();
8685
9576
  init_watcher();
@@ -8707,15 +9598,15 @@ var buildCommand_exports = {};
8707
9598
  __export(buildCommand_exports, {
8708
9599
  buildCommand: () => buildCommand
8709
9600
  });
8710
- import path26 from "path";
8711
- import fs19 from "fs";
9601
+ import path30 from "path";
9602
+ import fs20 from "fs";
8712
9603
  async function buildCommand(options) {
8713
9604
  const rootDir = options?.rootDir ?? process.cwd();
8714
9605
  const outdir = options?.dist ?? DEFAULT_DIST2;
8715
- const absOut = path26.resolve(rootDir, outdir);
8716
- const realRoot = toRealPath(path26.resolve(rootDir));
9606
+ const absOut = path30.resolve(rootDir, outdir);
9607
+ const realRoot = toRealPath(path30.resolve(rootDir));
8717
9608
  if (isInsideDir(toRealPath(absOut), realRoot)) {
8718
- fs19.rmSync(absOut, { recursive: true, force: true });
9609
+ fs20.rmSync(absOut, { recursive: true, force: true });
8719
9610
  } else {
8720
9611
  console.warn(
8721
9612
  `! Output directory "${outdir}" is outside the project root, skipping clean (stale artifacts may remain)`
@@ -8723,10 +9614,10 @@ async function buildCommand(options) {
8723
9614
  }
8724
9615
  await compileConfig({ rootDir, dist: outdir });
8725
9616
  const _config = await loadConfig(rootDir, outdir);
8726
- const pkgPath = path26.resolve(rootDir, "package.json");
8727
- if (fs19.existsSync(pkgPath)) {
9617
+ const pkgPath = path30.resolve(rootDir, "package.json");
9618
+ if (fs20.existsSync(pkgPath)) {
8728
9619
  try {
8729
- const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf-8"));
9620
+ const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
8730
9621
  if (pkg.type !== "module") {
8731
9622
  console.warn(
8732
9623
  '! package.json is missing "type": "module" \u2014 build output is ESM and `node dist/main` will fail without it'
@@ -8754,7 +9645,7 @@ async function buildCommand(options) {
8754
9645
  if (localPluginSources.length > 0) {
8755
9646
  console.log("\n[2.5/8] Compiling local plugins...");
8756
9647
  const outside = localPluginSources.filter((p) => {
8757
- const rel = path26.relative(rootDir, p).replace(/\\/g, "/");
9648
+ const rel = path30.relative(rootDir, p).replace(/\\/g, "/");
8758
9649
  return !rel.startsWith("src/");
8759
9650
  });
8760
9651
  if (outside.length > 0) {
@@ -8778,9 +9669,9 @@ async function buildCommand(options) {
8778
9669
  }
8779
9670
  console.log("\n[4/8] Generating schema...");
8780
9671
  await generateSchemaFiles(sorted, rootDir, outdir);
8781
- console.log(` Schema: zod.js files under ${path26.resolve(rootDir, outdir)}`);
9672
+ console.log(` Schema: zod.js files under ${path30.resolve(rootDir, outdir)}`);
8782
9673
  console.log("\n[5/8] Generating routes manifest...");
8783
- const routesPath = path26.resolve(rootDir, outdir, "faapi-routes.js");
9674
+ const routesPath = path30.resolve(rootDir, outdir, "faapi-routes.js");
8784
9675
  const serialized = serializeRoutes(sorted, wsRoutes, rootDir, outdir);
8785
9676
  await writeRoutesModule(serialized, routesPath);
8786
9677
  console.log(` Written to ${routesPath}`);
@@ -8788,14 +9679,19 @@ async function buildCommand(options) {
8788
9679
  const tools = await scanTools(rootDir, TOOL_PATTERNS);
8789
9680
  const toolMeta = await generateToolArtifacts(tools, rootDir, outdir);
8790
9681
  console.log(` Found ${toolMeta.length} tool(s)`);
8791
- console.log(` Tool manifest: ${path26.resolve(rootDir, outdir, "faapi-tools.js")}`);
9682
+ console.log(` Tool manifest: ${path30.resolve(rootDir, outdir, "faapi-tools.js")}`);
8792
9683
  console.log("\n[7/8] Generating agent manifest...");
8793
9684
  const agents = await scanAgents(rootDir, DEFAULT_AGENT_PATTERNS);
8794
9685
  const agentMeta = await generateAgentArtifacts(agents, rootDir, outdir);
8795
9686
  console.log(` Found ${agentMeta.length} agent(s)`);
8796
- console.log(` Agent manifest: ${path26.resolve(rootDir, outdir, "faapi-agents.js")}`);
9687
+ console.log(` Agent manifest: ${path30.resolve(rootDir, outdir, "faapi-agents.js")}`);
9688
+ console.log("\n[7.5/8] Generating task manifest and schema...");
9689
+ const tasks = await scanTasks(rootDir, TASK_PATTERNS);
9690
+ const taskMeta = await generateTaskArtifacts(tasks, rootDir, outdir);
9691
+ console.log(` Found ${taskMeta.length} task(s)`);
9692
+ console.log(` Task manifest: ${path30.resolve(rootDir, outdir, "faapi-tasks.js")}`);
8797
9693
  console.log("\n[8/8] Generating entry file...");
8798
- const mainPath = path26.resolve(rootDir, outdir, "main.js");
9694
+ const mainPath = path30.resolve(rootDir, outdir, "main.js");
8799
9695
  const createProdAppArgs = options?.dist && options.dist !== DEFAULT_DIST2 ? `{ dist: ${JSON.stringify(outdir)} }` : "";
8800
9696
  const mainContent = `// \u7531 faapi build \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91
8801
9697
  import { createProdApp, loadEnv } from '@faapi/faapi';
@@ -8807,7 +9703,7 @@ loadEnv(process.cwd());
8807
9703
  const app = await createProdApp(${createProdAppArgs});
8808
9704
  await app.listen();
8809
9705
  `;
8810
- await fs19.promises.writeFile(mainPath, mainContent, "utf-8");
9706
+ await fs20.promises.writeFile(mainPath, mainContent, "utf-8");
8811
9707
  console.log(` Written to ${mainPath}`);
8812
9708
  console.log("\nfaapi build completed");
8813
9709
  }
@@ -8821,7 +9717,7 @@ function extractLocalPluginSources(declarations, rootDir) {
8821
9717
  else if (Array.isArray(decl)) specifier = decl[0];
8822
9718
  else if ("path" in decl) specifier = decl.path;
8823
9719
  if (!specifier) continue;
8824
- if (!specifier.startsWith("./") && !specifier.startsWith("../") && !path26.isAbsolute(specifier)) {
9720
+ if (!specifier.startsWith("./") && !specifier.startsWith("../") && !path30.isAbsolute(specifier)) {
8825
9721
  continue;
8826
9722
  }
8827
9723
  const source = resolveLocalPluginSource(specifier, rootDir);
@@ -8845,6 +9741,8 @@ var init_buildCommand = __esm({
8845
9741
  init_scanAgents();
8846
9742
  init_scanAgents();
8847
9743
  init_generateAgentArtifacts();
9744
+ init_scanTasks();
9745
+ init_generateTaskArtifacts();
8848
9746
  init_generateSchemaFiles();
8849
9747
  init_generateRoutes();
8850
9748
  init_compileBuildRoutes();