@faapi/faapi 6.2.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1120,8 +1120,10 @@ var init_resolveInjection = __esm({
1120
1120
  fields: "fields",
1121
1121
  agent: "agent",
1122
1122
  // Phase 2.3
1123
- agents: "agents"
1123
+ agents: "agents",
1124
1124
  // Phase 2.3
1125
+ tasks: "tasks"
1126
+ // 任务子系统:TaskClient(入队/查询)
1125
1127
  };
1126
1128
  injectionCache = /* @__PURE__ */ new WeakMap();
1127
1129
  }
@@ -1231,13 +1233,13 @@ var init_collectRouteSchemaSources = __esm({
1231
1233
  });
1232
1234
 
1233
1235
  // src/utils/atomicWrite.ts
1234
- import path7 from "path";
1235
- import fs6 from "fs";
1236
+ import path9 from "path";
1237
+ import fs7 from "fs";
1236
1238
  async function atomicWriteFile(outputPath, content) {
1237
- await fs6.promises.mkdir(path7.dirname(outputPath), { recursive: true });
1239
+ await fs7.promises.mkdir(path9.dirname(outputPath), { recursive: true });
1238
1240
  const tmp = `${outputPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
1239
- await fs6.promises.writeFile(tmp, content, "utf-8");
1240
- await fs6.promises.rename(tmp, outputPath);
1241
+ await fs7.promises.writeFile(tmp, content, "utf-8");
1242
+ await fs7.promises.rename(tmp, outputPath);
1241
1243
  }
1242
1244
  var init_atomicWrite = __esm({
1243
1245
  "src/utils/atomicWrite.ts"() {
@@ -1584,7 +1586,7 @@ __export(generateSchemaFiles_exports, {
1584
1586
  getRuntimeSchemaPath: () => getRuntimeSchemaPath,
1585
1587
  getSchemaOutputPath: () => getSchemaOutputPath
1586
1588
  });
1587
- import path8 from "path";
1589
+ import path10 from "path";
1588
1590
  function getSchemaOutputPath(sourceFile, dist, rootDir) {
1589
1591
  let rel = sourceFile.replace(/\\/g, "/");
1590
1592
  if (rel.startsWith("src/")) {
@@ -1592,7 +1594,7 @@ function getSchemaOutputPath(sourceFile, dist, rootDir) {
1592
1594
  }
1593
1595
  const idx = rel.lastIndexOf("/");
1594
1596
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
1595
- return path8.resolve(rootDir, dist, relDir, "zod.js");
1597
+ return path10.resolve(rootDir, dist, relDir, "zod.js");
1596
1598
  }
1597
1599
  function getRuntimeSchemaPath(filePath, dist, rootDir) {
1598
1600
  let rel = filePath.replace(/\\/g, "/");
@@ -1603,7 +1605,7 @@ function getRuntimeSchemaPath(filePath, dist, rootDir) {
1603
1605
  }
1604
1606
  const idx = rel.lastIndexOf("/");
1605
1607
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
1606
- return path8.resolve(rootDir, dist, relDir, "zod.js");
1608
+ return path10.resolve(rootDir, dist, relDir, "zod.js");
1607
1609
  }
1608
1610
  function getHelpersImportPath(relDir) {
1609
1611
  if (!relDir) return `./${HELPERS_FILENAME}`;
@@ -1662,7 +1664,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
1662
1664
  }
1663
1665
  const fileEntries = [];
1664
1666
  for (const [filePath, fileSources] of sourcesByFile) {
1665
- const relFile = path8.relative(rootDir, filePath).replace(/\\/g, "/");
1667
+ const relFile = path10.relative(rootDir, filePath).replace(/\\/g, "/");
1666
1668
  const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
1667
1669
  let relForDir = relFile;
1668
1670
  if (relForDir.startsWith("src/")) {
@@ -1681,7 +1683,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
1681
1683
  }
1682
1684
  const allSourceCode = fileEntries.map((e) => e.source).join("\n");
1683
1685
  if (usesCoerceHelpers(allSourceCode)) {
1684
- const helpersPath = path8.resolve(rootDir, dist, HELPERS_FILENAME);
1686
+ const helpersPath = path10.resolve(rootDir, dist, HELPERS_FILENAME);
1685
1687
  await writeSchemaFile(helpersPath, generateHelpersFileSource());
1686
1688
  }
1687
1689
  await Promise.all(
@@ -1707,6 +1709,29 @@ init_resolveTypeNode();
1707
1709
  init_inputType();
1708
1710
  init_collectRouteSchemaSources();
1709
1711
 
1712
+ // src/task/taskRegistry.ts
1713
+ function createTaskRegistry() {
1714
+ let registry = /* @__PURE__ */ new Map();
1715
+ return {
1716
+ hydrate(tasks) {
1717
+ const next = /* @__PURE__ */ new Map();
1718
+ for (const task of tasks) {
1719
+ next.set(task.name, task);
1720
+ }
1721
+ registry = next;
1722
+ },
1723
+ get(name) {
1724
+ return registry.get(name);
1725
+ },
1726
+ list() {
1727
+ return Array.from(registry.values());
1728
+ },
1729
+ clear() {
1730
+ registry = /* @__PURE__ */ new Map();
1731
+ }
1732
+ };
1733
+ }
1734
+
1710
1735
  // src/injection/registries.ts
1711
1736
  function createToolRegistry() {
1712
1737
  let registry = /* @__PURE__ */ new Map();
@@ -1829,15 +1854,483 @@ function createAgentHandleStore() {
1829
1854
  }
1830
1855
  };
1831
1856
  }
1857
+ function createTaskHandleStore() {
1858
+ let currentFactory = null;
1859
+ return {
1860
+ register(factory) {
1861
+ currentFactory = factory;
1862
+ },
1863
+ get(ctx) {
1864
+ if (currentFactory === null) return void 0;
1865
+ return currentFactory(ctx);
1866
+ },
1867
+ clear() {
1868
+ currentFactory = null;
1869
+ }
1870
+ };
1871
+ }
1832
1872
  function createAppRegistries() {
1833
1873
  const tool = createToolRegistry();
1834
1874
  const agent = createAgentRegistry(tool);
1835
1875
  const skill = createSkillRegistry();
1876
+ const task = createTaskRegistry();
1836
1877
  const agentHandle = createAgentHandleStore();
1837
- return { tool, agent, skill, agentHandle };
1878
+ const taskHandle = createTaskHandleStore();
1879
+ return { tool, agent, skill, task, agentHandle, taskHandle };
1838
1880
  }
1839
1881
  var defaultRegistries = createAppRegistries();
1840
1882
 
1883
+ // src/task/taskQueue.ts
1884
+ import path3 from "path";
1885
+
1886
+ // src/errors/FaapiError.ts
1887
+ var FaapiError = class extends Error {
1888
+ constructor(code, message, statusCode) {
1889
+ super(message);
1890
+ this.code = code;
1891
+ this.statusCode = statusCode;
1892
+ this.name = "FaapiError";
1893
+ }
1894
+ code;
1895
+ statusCode;
1896
+ };
1897
+
1898
+ // src/errors/httpErrors.ts
1899
+ function deriveStatusCode(issues) {
1900
+ const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
1901
+ return has400 ? 400 : 422;
1902
+ }
1903
+ var ValidationError = class extends FaapiError {
1904
+ constructor(message, issues) {
1905
+ super("VALIDATION_ERROR", message, deriveStatusCode(issues));
1906
+ this.issues = issues;
1907
+ this.name = "ValidationError";
1908
+ }
1909
+ issues;
1910
+ };
1911
+ var RouteNotFoundError = class extends FaapiError {
1912
+ constructor(path28) {
1913
+ super("ROUTE_NOT_FOUND", `Route not found: ${path28}`, 404);
1914
+ this.name = "RouteNotFoundError";
1915
+ }
1916
+ };
1917
+ var MethodNotAllowedError = class extends FaapiError {
1918
+ constructor(method, path28, allowedMethods) {
1919
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path28}`, 405);
1920
+ this.allowedMethods = allowedMethods;
1921
+ this.name = "MethodNotAllowedError";
1922
+ }
1923
+ allowedMethods;
1924
+ };
1925
+ var InternalError = class extends FaapiError {
1926
+ constructor(message) {
1927
+ super("INTERNAL_ERROR", message, 500);
1928
+ this.name = "InternalError";
1929
+ }
1930
+ };
1931
+ var ModuleLoadError = class extends FaapiError {
1932
+ constructor(filePath, reason) {
1933
+ super("MODULE_LOAD_ERROR", `Failed to load module ${filePath}: ${reason}`, 500);
1934
+ this.name = "ModuleLoadError";
1935
+ }
1936
+ };
1937
+ var PayloadTooLargeError = class extends FaapiError {
1938
+ constructor(maxSize) {
1939
+ super("PAYLOAD_TOO_LARGE", `Request body exceeds size limit of ${maxSize} bytes`, 413);
1940
+ this.name = "PayloadTooLargeError";
1941
+ }
1942
+ };
1943
+
1944
+ // src/task/memoryDriver.ts
1945
+ import { randomUUID } from "crypto";
1946
+ var RETRY_BASE_DELAY_MS = 500;
1947
+ var RETRY_MAX_DELAY_MS = 3e4;
1948
+ function createMemoryDriver() {
1949
+ const jobs = /* @__PURE__ */ new Map();
1950
+ const pendingByTask = /* @__PURE__ */ new Map();
1951
+ const runningCount = /* @__PURE__ */ new Map();
1952
+ const workers = /* @__PURE__ */ new Map();
1953
+ let stopped = false;
1954
+ function pump() {
1955
+ if (stopped) return;
1956
+ for (const [name, worker] of workers) {
1957
+ const queue = pendingByTask.get(name);
1958
+ if (!queue || queue.length === 0) continue;
1959
+ let running = runningCount.get(name) ?? 0;
1960
+ while (running < worker.concurrency && queue.length > 0) {
1961
+ const head = jobs.get(queue[0]);
1962
+ if (!head) {
1963
+ queue.shift();
1964
+ continue;
1965
+ }
1966
+ if (head.runAt > Date.now()) break;
1967
+ queue.shift();
1968
+ running += 1;
1969
+ runningCount.set(name, running);
1970
+ void dispatch(name, worker, head);
1971
+ }
1972
+ if (running === 0) runningCount.delete(name);
1973
+ else runningCount.set(name, running);
1974
+ }
1975
+ }
1976
+ async function dispatch(name, worker, job) {
1977
+ job.attempts += 1;
1978
+ const driverJob = {
1979
+ id: job.id,
1980
+ name,
1981
+ payload: job.payload,
1982
+ attempt: job.attempts,
1983
+ signal: job.controller.signal
1984
+ };
1985
+ try {
1986
+ await worker.process(driverJob);
1987
+ } catch (err) {
1988
+ if (job.attempts <= job.retries) {
1989
+ const delay = Math.min(RETRY_BASE_DELAY_MS * 2 ** (job.attempts - 1), RETRY_MAX_DELAY_MS);
1990
+ job.runAt = Date.now() + delay;
1991
+ job.timer = setTimeout(() => {
1992
+ job.timer = void 0;
1993
+ pendingByTask.get(name)?.push(job.id);
1994
+ pump();
1995
+ }, delay);
1996
+ }
1997
+ void err;
1998
+ } finally {
1999
+ const running = (runningCount.get(name) ?? 1) - 1;
2000
+ if (running <= 0) runningCount.delete(name);
2001
+ else runningCount.set(name, running);
2002
+ pump();
2003
+ }
2004
+ }
2005
+ return {
2006
+ async enqueue(name, payload, opts) {
2007
+ if (stopped) {
2008
+ throw new Error("[faapi] Task queue is stopped and no longer accepts jobs");
2009
+ }
2010
+ const id = randomUUID();
2011
+ const job = {
2012
+ id,
2013
+ payload,
2014
+ retries: opts?.retries ?? 0,
2015
+ runAt: opts?.delayMs ? Date.now() + opts.delayMs : Date.now(),
2016
+ attempts: 0,
2017
+ controller: new AbortController()
2018
+ };
2019
+ jobs.set(id, job);
2020
+ if (job.runAt > Date.now()) {
2021
+ job.timer = setTimeout(() => {
2022
+ job.timer = void 0;
2023
+ pendingByTask.get(name)?.push(id);
2024
+ pump();
2025
+ }, job.runAt - Date.now());
2026
+ } else {
2027
+ let q = pendingByTask.get(name);
2028
+ if (!q) {
2029
+ q = [];
2030
+ pendingByTask.set(name, q);
2031
+ }
2032
+ q.push(id);
2033
+ pump();
2034
+ }
2035
+ return id;
2036
+ },
2037
+ startWorker(name, opts) {
2038
+ workers.set(name, opts);
2039
+ pump();
2040
+ },
2041
+ async stop(timeoutMs = 1e4) {
2042
+ stopped = true;
2043
+ for (const job of jobs.values()) {
2044
+ if (job.timer) {
2045
+ clearTimeout(job.timer);
2046
+ job.timer = void 0;
2047
+ }
2048
+ }
2049
+ const start = Date.now();
2050
+ while (Date.now() - start < timeoutMs) {
2051
+ const total = Array.from(runningCount.values()).reduce((sum, n) => sum + n, 0);
2052
+ if (total === 0) return;
2053
+ await new Promise((r) => setTimeout(r, 10));
2054
+ }
2055
+ for (const job of jobs.values()) {
2056
+ job.controller.abort();
2057
+ }
2058
+ },
2059
+ async stopWorkers() {
2060
+ workers.clear();
2061
+ }
2062
+ };
2063
+ }
2064
+
2065
+ // src/task/taskQueue.ts
2066
+ function createTaskQueue(deps) {
2067
+ const { registry, rootDir } = deps;
2068
+ const driver = deps.driver ?? createMemoryDriver();
2069
+ const records = /* @__PURE__ */ new Map();
2070
+ const moduleCache2 = /* @__PURE__ */ new Map();
2071
+ const schemaCache = /* @__PURE__ */ new Map();
2072
+ let started = false;
2073
+ let stopped = false;
2074
+ const loadTaskModule = deps.loadTaskModule ?? (async (filePath) => await import(filePath));
2075
+ const loadPayloadSchema = deps.loadPayloadSchema ?? (async (filePath) => {
2076
+ const zodPath = path3.join(path3.dirname(filePath), "zod.js");
2077
+ try {
2078
+ const mod = await import(zodPath);
2079
+ const schemaKey = Object.keys(mod).find((k) => k.endsWith("Schema"));
2080
+ return schemaKey ? mod[schemaKey] : void 0;
2081
+ } catch {
2082
+ return void 0;
2083
+ }
2084
+ });
2085
+ function assertKnownTask(name) {
2086
+ const meta = registry.get(name);
2087
+ if (!meta) {
2088
+ const known = registry.list().map((t) => t.name).join(", ") || "(none)";
2089
+ throw new Error(`[faapi] Unknown task "${name}". Registered tasks: ${known}`);
2090
+ }
2091
+ }
2092
+ async function validatePayload(name, payload) {
2093
+ if (!schemaCache.has(name)) {
2094
+ const meta = registry.get(name);
2095
+ schemaCache.set(
2096
+ name,
2097
+ await loadPayloadSchema(path3.resolve(rootDir, meta.filePath)).catch(() => void 0)
2098
+ );
2099
+ }
2100
+ const schema = schemaCache.get(name);
2101
+ if (!schema || typeof schema.safeParse !== "function") {
2102
+ return payload;
2103
+ }
2104
+ const result = schema.safeParse(payload);
2105
+ if (!result.success) {
2106
+ throw new ValidationError(
2107
+ `Task payload validation failed for "${name}": ${JSON.stringify(result.error)}`,
2108
+ []
2109
+ );
2110
+ }
2111
+ return result.data;
2112
+ }
2113
+ async function runJob(job) {
2114
+ const meta = registry.get(job.name);
2115
+ if (!meta) throw new Error(`[faapi] Unknown task "${job.name}" at worker dispatch`);
2116
+ const record = records.get(job.id) ?? {
2117
+ id: job.id,
2118
+ name: job.name,
2119
+ payload: job.payload,
2120
+ status: "pending",
2121
+ attempts: 0,
2122
+ createdAt: Date.now()
2123
+ };
2124
+ record.attempts = job.attempt;
2125
+ record.status = "running";
2126
+ record.error = void 0;
2127
+ records.set(job.id, record);
2128
+ try {
2129
+ let mod = moduleCache2.get(job.name);
2130
+ if (!mod) {
2131
+ mod = await loadTaskModule(path3.resolve(rootDir, meta.filePath));
2132
+ moduleCache2.set(job.name, mod);
2133
+ }
2134
+ if (typeof mod.run !== "function") {
2135
+ throw new Error(`Task "${job.name}" module has no run export`);
2136
+ }
2137
+ const taskCtx = {
2138
+ signal: job.signal,
2139
+ config: deps.config,
2140
+ job: { id: job.id, name: job.name, attempt: job.attempt }
2141
+ };
2142
+ const result = await mod.run(job.payload, taskCtx);
2143
+ record.status = "done";
2144
+ record.result = result;
2145
+ return result;
2146
+ } catch (err) {
2147
+ record.status = "failed";
2148
+ record.error = err instanceof Error ? err.message : String(err);
2149
+ throw err;
2150
+ }
2151
+ }
2152
+ async function startWorkers() {
2153
+ for (const meta of registry.list()) {
2154
+ await driver.startWorker(meta.name, {
2155
+ concurrency: meta.concurrency ?? 1,
2156
+ process: runJob
2157
+ });
2158
+ }
2159
+ }
2160
+ const queue = {
2161
+ async enqueue(name, payload = {}, opts) {
2162
+ if (stopped) {
2163
+ throw new Error("[faapi] Task queue is stopped and no longer accepts jobs");
2164
+ }
2165
+ assertKnownTask(name);
2166
+ const data = await validatePayload(name, payload);
2167
+ const meta = registry.get(name);
2168
+ const id = await driver.enqueue(name, data, {
2169
+ delayMs: opts?.delayMs,
2170
+ retries: meta.retries ?? 0
2171
+ });
2172
+ if (!records.has(id)) {
2173
+ records.set(id, {
2174
+ id,
2175
+ name,
2176
+ payload: data,
2177
+ status: "pending",
2178
+ attempts: 0,
2179
+ createdAt: Date.now(),
2180
+ ...opts?.delayMs ? { runAt: Date.now() + opts.delayMs } : {}
2181
+ });
2182
+ }
2183
+ return { id };
2184
+ },
2185
+ list(name) {
2186
+ const snapshot = [];
2187
+ for (const job of records.values()) {
2188
+ if (name !== void 0 && job.name !== name) continue;
2189
+ snapshot.push({ ...job });
2190
+ }
2191
+ return snapshot;
2192
+ },
2193
+ async start() {
2194
+ if (stopped) {
2195
+ throw new Error("[faapi] Task queue is stopped and cannot be restarted");
2196
+ }
2197
+ if (started) return;
2198
+ started = true;
2199
+ await startWorkers();
2200
+ },
2201
+ async stop(timeoutMs = 1e4) {
2202
+ stopped = true;
2203
+ await driver.stop(timeoutMs);
2204
+ },
2205
+ async reload() {
2206
+ await driver.stopWorkers?.();
2207
+ await startWorkers();
2208
+ },
2209
+ invalidateModules() {
2210
+ moduleCache2.clear();
2211
+ },
2212
+ invalidateSchemas() {
2213
+ schemaCache.clear();
2214
+ }
2215
+ };
2216
+ return queue;
2217
+ }
2218
+
2219
+ // src/task/loadTaskDriver.ts
2220
+ async function loadTaskDriver(driver, driverOptions) {
2221
+ if (driver === void 0 || driver === "memory") {
2222
+ return createMemoryDriver();
2223
+ }
2224
+ if (typeof driver === "object") {
2225
+ return driver;
2226
+ }
2227
+ if (driver !== "pgboss" && driver !== "bullmq") {
2228
+ throw new Error(
2229
+ `[faapi] Unknown task driver "${driver}". Supported: 'memory' (default), 'pgboss', 'bullmq', or a TaskDriver instance.`
2230
+ );
2231
+ }
2232
+ const specifier = `@faapi/task-${driver}`;
2233
+ let mod;
2234
+ try {
2235
+ mod = await import(
2236
+ /* @vite-ignore */
2237
+ specifier
2238
+ );
2239
+ } catch {
2240
+ throw new Error(
2241
+ `[faapi] Task driver "${driver}" requires package "${specifier}" to be installed in your project.`
2242
+ );
2243
+ }
2244
+ const factoryName = driver === "pgboss" ? "createPgBossDriver" : "createBullMQDriver";
2245
+ const factory = mod[factoryName];
2246
+ if (typeof factory !== "function") {
2247
+ throw new Error(
2248
+ `[faapi] Package "${specifier}" does not export ${factoryName}() \u2014 check the package version.`
2249
+ );
2250
+ }
2251
+ return factory(driverOptions);
2252
+ }
2253
+
2254
+ // src/task/cronScheduler.ts
2255
+ import { Cron } from "croner";
2256
+ function createCronScheduler(registry, enqueue) {
2257
+ let schedules = [];
2258
+ return {
2259
+ start() {
2260
+ this.stop();
2261
+ for (const task of registry.list()) {
2262
+ if (!task.cron) continue;
2263
+ schedules.push(
2264
+ new Cron(task.cron, () => {
2265
+ void Promise.resolve().then(() => enqueue(task.name)).catch((err) => {
2266
+ console.error(`[faapi] Cron enqueue failed for task "${task.name}":`, err);
2267
+ });
2268
+ })
2269
+ );
2270
+ }
2271
+ },
2272
+ stop() {
2273
+ for (const schedule of schedules) {
2274
+ schedule.stop();
2275
+ }
2276
+ schedules = [];
2277
+ }
2278
+ };
2279
+ }
2280
+
2281
+ // src/task/scanTasks.ts
2282
+ import fg from "fast-glob";
2283
+ import path4 from "path";
2284
+ import fs2 from "fs";
2285
+ var TASK_PATTERNS = ["src/tasks/**/task.ts"];
2286
+ var TASK_FILENAME = "task.ts";
2287
+ var CRON_RE = /(?:^|\n)\s*cron:\s*['"`]([^'"`\n]+)['"`]/;
2288
+ var CONCURRENCY_RE = /(?:^|\n)\s*concurrency:\s*(\d+)/;
2289
+ var RETRIES_RE = /(?:^|\n)\s*retries:\s*(\d+)/;
2290
+ function filePathToTaskName(filePath) {
2291
+ const normalized = filePath.replace(/\\/g, "/");
2292
+ const match = normalized.match(/(?:^|\/)tasks\/(.+)\/task\.ts$/);
2293
+ if (!match) {
2294
+ throw new Error(`[faapi] Invalid task file path: ${filePath}`);
2295
+ }
2296
+ return match[1].split("/").join(".");
2297
+ }
2298
+ async function scanTasks(rootDir, patterns) {
2299
+ const files = await fg(patterns, {
2300
+ cwd: rootDir,
2301
+ onlyFiles: true,
2302
+ absolute: false
2303
+ });
2304
+ const tasks = [];
2305
+ const seen = /* @__PURE__ */ new Map();
2306
+ for (const file of files) {
2307
+ const normalizedFile = file.replace(/\\/g, "/");
2308
+ const fileName = path4.posix.basename(normalizedFile);
2309
+ if (fileName !== TASK_FILENAME) {
2310
+ continue;
2311
+ }
2312
+ const absPath = path4.resolve(rootDir, normalizedFile);
2313
+ const source = await fs2.promises.readFile(absPath, "utf8").catch(() => "");
2314
+ const name = filePathToTaskName(normalizedFile);
2315
+ const prevFile = seen.get(name);
2316
+ if (prevFile) {
2317
+ throw new Error(
2318
+ `Task conflict: "${name}" declared in both ${prevFile} and ${normalizedFile}`
2319
+ );
2320
+ }
2321
+ seen.set(name, normalizedFile);
2322
+ const manifest = { name, filePath: normalizedFile };
2323
+ const cron = CRON_RE.exec(source)?.[1];
2324
+ const concurrency = CONCURRENCY_RE.exec(source)?.[1];
2325
+ const retries = RETRIES_RE.exec(source)?.[1];
2326
+ if (cron !== void 0) manifest.cron = cron;
2327
+ if (concurrency !== void 0) manifest.concurrency = Number(concurrency);
2328
+ if (retries !== void 0) manifest.retries = Number(retries);
2329
+ tasks.push(manifest);
2330
+ }
2331
+ return tasks;
2332
+ }
2333
+
1841
2334
  // src/injection/agentRegistry.ts
1842
2335
  function getAgent(name) {
1843
2336
  return defaultRegistries.agent.getAgent(name);
@@ -1878,7 +2371,7 @@ function listSkills() {
1878
2371
  }
1879
2372
 
1880
2373
  // src/loader/loadAgentModule.ts
1881
- import fs9 from "fs";
2374
+ import fs10 from "fs";
1882
2375
 
1883
2376
  // src/loader/resolveExports.ts
1884
2377
  function resolveExport(module, exportName) {
@@ -1924,18 +2417,18 @@ async function importWithCacheBust(filePath, bustViteCache = false) {
1924
2417
  }
1925
2418
 
1926
2419
  // src/cli/compileOnDemand.ts
1927
- import path10 from "path";
1928
- import fs8 from "fs";
2420
+ import path12 from "path";
2421
+ import fs9 from "fs";
1929
2422
 
1930
2423
  // src/cli/compileSourceFiles.ts
1931
- import path6 from "path";
1932
- import fs5 from "fs";
1933
- import fg from "fast-glob";
2424
+ import path8 from "path";
2425
+ import fs6 from "fs";
2426
+ import fg2 from "fast-glob";
1934
2427
 
1935
2428
  // src/cli/aliasPlugin.ts
1936
2429
  import ts7 from "typescript";
1937
- import path5 from "path";
1938
- import fs4 from "fs";
2430
+ import path7 from "path";
2431
+ import fs5 from "fs";
1939
2432
 
1940
2433
  // src/utils/resolveAlias.ts
1941
2434
  function resolveAlias(specifier, config) {
@@ -1961,8 +2454,8 @@ function resolveAlias(specifier, config) {
1961
2454
  }
1962
2455
 
1963
2456
  // src/utils/prodPaths.ts
1964
- import path3 from "path";
1965
- import fs2 from "fs";
2457
+ import path5 from "path";
2458
+ import fs3 from "fs";
1966
2459
  var APP_DIR = "src";
1967
2460
  var ROUTE_PATTERNS = ["src/api/**/*.ts"];
1968
2461
  function toProdFilePath(filePath, dist) {
@@ -1981,27 +2474,27 @@ function toProdExtension(filePath) {
1981
2474
  }
1982
2475
  function toRealPath(p) {
1983
2476
  try {
1984
- return fs2.realpathSync(p);
2477
+ return fs3.realpathSync(p);
1985
2478
  } catch {
1986
2479
  return p;
1987
2480
  }
1988
2481
  }
1989
2482
  function isInsideDir(filePath, dir) {
1990
- const rel = path3.relative(dir, filePath);
1991
- return rel !== "" && !rel.startsWith("..") && !path3.isAbsolute(rel);
2483
+ const rel = path5.relative(dir, filePath);
2484
+ return rel !== "" && !rel.startsWith("..") && !path5.isAbsolute(rel);
1992
2485
  }
1993
2486
 
1994
2487
  // src/utils/readTsconfig.ts
1995
2488
  import ts6 from "typescript";
1996
- import path4 from "path";
1997
- import fs3 from "fs";
2489
+ import path6 from "path";
2490
+ import fs4 from "fs";
1998
2491
  var tsconfigCache = /* @__PURE__ */ new Map();
1999
2492
  function readTsconfig(rootDir) {
2000
- const tsconfigPath = path4.resolve(rootDir, "tsconfig.json");
2001
- if (!fs3.existsSync(tsconfigPath)) return null;
2493
+ const tsconfigPath = path6.resolve(rootDir, "tsconfig.json");
2494
+ if (!fs4.existsSync(tsconfigPath)) return null;
2002
2495
  let mtimeMs;
2003
2496
  try {
2004
- mtimeMs = fs3.statSync(tsconfigPath).mtimeMs;
2497
+ mtimeMs = fs4.statSync(tsconfigPath).mtimeMs;
2005
2498
  } catch {
2006
2499
  return null;
2007
2500
  }
@@ -2017,7 +2510,7 @@ function readTsconfig(rootDir) {
2017
2510
  const config = rawPaths ? (() => {
2018
2511
  const paths = {};
2019
2512
  for (const [pattern, targets] of Object.entries(rawPaths)) {
2020
- paths[pattern] = targets.map((t) => path4.resolve(baseUrl, t));
2513
+ paths[pattern] = targets.map((t) => path6.resolve(baseUrl, t));
2021
2514
  }
2022
2515
  return { baseUrl, paths };
2023
2516
  })() : null;
@@ -2027,26 +2520,26 @@ function readTsconfig(rootDir) {
2027
2520
 
2028
2521
  // src/cli/aliasPlugin.ts
2029
2522
  function toProdImportPath(sourceFile, importer) {
2030
- const importerDir = path5.dirname(importer);
2031
- let rel = path5.relative(importerDir, sourceFile);
2032
- rel = rel.split(path5.sep).join("/");
2523
+ const importerDir = path7.dirname(importer);
2524
+ let rel = path7.relative(importerDir, sourceFile);
2525
+ rel = rel.split(path7.sep).join("/");
2033
2526
  if (!rel.startsWith(".")) rel = "./" + rel;
2034
2527
  return toProdExtension(rel);
2035
2528
  }
2036
2529
  function toStrippedProdImportPath(sourceFile, rootDir) {
2037
- const appDirAbs = toRealPath(path5.resolve(rootDir, APP_DIR));
2530
+ const appDirAbs = toRealPath(path7.resolve(rootDir, APP_DIR));
2038
2531
  const sourceReal = toRealPath(sourceFile);
2039
- let rel = path5.relative(appDirAbs, sourceReal);
2040
- rel = rel.split(path5.sep).join("/");
2532
+ let rel = path7.relative(appDirAbs, sourceReal);
2533
+ rel = rel.split(path7.sep).join("/");
2041
2534
  if (!rel.startsWith(".")) rel = "./" + rel;
2042
2535
  return toProdExtension(rel);
2043
2536
  }
2044
2537
  function toProdImportFromImporter(importer, rootDir, relFromDist) {
2045
- const importerRel = path5.relative(toRealPath(path5.resolve(rootDir)), toRealPath(importer)).split(path5.sep).join("/");
2046
- const importerProdDir = path5.posix.dirname(toProdExtension(importerRel));
2538
+ const importerRel = path7.relative(toRealPath(path7.resolve(rootDir)), toRealPath(importer)).split(path7.sep).join("/");
2539
+ const importerProdDir = path7.posix.dirname(toProdExtension(importerRel));
2047
2540
  if (importerProdDir === ".") return relFromDist;
2048
2541
  const target = relFromDist.replace(/^\.\//, "");
2049
- let rel = path5.posix.relative(importerProdDir, target);
2542
+ let rel = path7.posix.relative(importerProdDir, target);
2050
2543
  if (!rel.startsWith(".")) rel = "./" + rel;
2051
2544
  return rel;
2052
2545
  }
@@ -2061,41 +2554,41 @@ var INDEX_EXTS = [
2061
2554
  "/index.cjs"
2062
2555
  ];
2063
2556
  function resolveRelativeSpecifier(importer, specifier) {
2064
- const importerDir = path5.dirname(importer);
2065
- const base = path5.resolve(importerDir, specifier);
2557
+ const importerDir = path7.dirname(importer);
2558
+ const base = path7.resolve(importerDir, specifier);
2066
2559
  if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
2067
- if (fs4.existsSync(base)) return base;
2560
+ if (fs5.existsSync(base)) return base;
2068
2561
  if (base.endsWith(".js")) {
2069
2562
  for (const ext of [".ts", ".tsx", ".jsx"]) {
2070
2563
  const file = base.slice(0, -3) + ext;
2071
- if (fs4.existsSync(file)) return file;
2564
+ if (fs5.existsSync(file)) return file;
2072
2565
  }
2073
2566
  }
2074
2567
  return null;
2075
2568
  }
2076
2569
  if (/\.(ts|tsx|jsx)$/.test(specifier)) {
2077
- return fs4.existsSync(base) ? base : null;
2570
+ return fs5.existsSync(base) ? base : null;
2078
2571
  }
2079
2572
  for (const ext of SOURCE_EXTS) {
2080
2573
  const file = base + ext;
2081
- if (fs4.existsSync(file)) return file;
2574
+ if (fs5.existsSync(file)) return file;
2082
2575
  }
2083
2576
  for (const indexExt of INDEX_EXTS) {
2084
2577
  const file = base + indexExt;
2085
- if (fs4.existsSync(file)) return file;
2578
+ if (fs5.existsSync(file)) return file;
2086
2579
  }
2087
2580
  return null;
2088
2581
  }
2089
2582
  function createAliasPlugin(config, options) {
2090
- const appDirAbs = options?.rootDir ? toRealPath(path5.resolve(options.rootDir, APP_DIR)) : null;
2583
+ const appDirAbs = options?.rootDir ? toRealPath(path7.resolve(options.rootDir, APP_DIR)) : null;
2091
2584
  const probeCandidateFile = (candidate) => {
2092
2585
  for (const ext of SOURCE_EXTS) {
2093
2586
  const file = candidate + ext;
2094
- if (fs4.existsSync(file)) return file;
2587
+ if (fs5.existsSync(file)) return file;
2095
2588
  }
2096
2589
  for (const indexExt of INDEX_EXTS) {
2097
2590
  const file = candidate + indexExt;
2098
- if (fs4.existsSync(file)) return file;
2591
+ if (fs5.existsSync(file)) return file;
2099
2592
  }
2100
2593
  return null;
2101
2594
  };
@@ -2105,7 +2598,7 @@ function createAliasPlugin(config, options) {
2105
2598
  build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
2106
2599
  let source;
2107
2600
  try {
2108
- source = fs4.readFileSync(args.path, "utf8");
2601
+ source = fs5.readFileSync(args.path, "utf8");
2109
2602
  } catch {
2110
2603
  return void 0;
2111
2604
  }
@@ -2221,7 +2714,7 @@ function buildAliasPlugins(rootDir) {
2221
2714
  // src/cli/compileSourceFiles.ts
2222
2715
  async function compileSourceFiles(options) {
2223
2716
  const { rootDir, dist, files, logLevel = "silent", production, atomicWrite } = options;
2224
- const entryPoints = files ?? await fg([`${APP_DIR}/**/*.ts`], {
2717
+ const entryPoints = files ?? await fg2([`${APP_DIR}/**/*.ts`], {
2225
2718
  cwd: rootDir,
2226
2719
  onlyFiles: true,
2227
2720
  absolute: true,
@@ -2230,11 +2723,11 @@ async function compileSourceFiles(options) {
2230
2723
  if (entryPoints.length === 0) {
2231
2724
  return { compiledFiles: [] };
2232
2725
  }
2233
- const absDist = path6.resolve(rootDir, dist);
2234
- await fs5.promises.mkdir(absDist, { recursive: true });
2726
+ const absDist = path8.resolve(rootDir, dist);
2727
+ await fs6.promises.mkdir(absDist, { recursive: true });
2235
2728
  const plugins = buildAliasPlugins(rootDir);
2236
2729
  const esbuild = await import("esbuild");
2237
- const outbase = path6.resolve(rootDir, APP_DIR);
2730
+ const outbase = path8.resolve(rootDir, APP_DIR);
2238
2731
  const result = await esbuild.build({
2239
2732
  entryPoints,
2240
2733
  outdir: absDist,
@@ -2254,10 +2747,10 @@ async function compileSourceFiles(options) {
2254
2747
  if (atomicWrite && result.outputFiles) {
2255
2748
  await Promise.all(
2256
2749
  result.outputFiles.map(async (file) => {
2257
- await fs5.promises.mkdir(path6.dirname(file.path), { recursive: true });
2750
+ await fs6.promises.mkdir(path8.dirname(file.path), { recursive: true });
2258
2751
  const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
2259
- await fs5.promises.writeFile(tmp, file.contents);
2260
- await fs5.promises.rename(tmp, file.path);
2752
+ await fs6.promises.writeFile(tmp, file.contents);
2753
+ await fs6.promises.rename(tmp, file.path);
2261
2754
  })
2262
2755
  );
2263
2756
  }
@@ -2274,8 +2767,8 @@ init_generateSchemaFiles();
2274
2767
  init_generateSchemaFiles();
2275
2768
 
2276
2769
  // src/cli/collectImports.ts
2277
- import path9 from "path";
2278
- import fs7 from "fs";
2770
+ import path11 from "path";
2771
+ import fs8 from "fs";
2279
2772
  var SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
2280
2773
  function extractImportSpecifiers(source) {
2281
2774
  const specifiers = [];
@@ -2287,10 +2780,10 @@ function extractImportSpecifiers(source) {
2287
2780
  return specifiers;
2288
2781
  }
2289
2782
  function resolveSpecifierFromDir(dir, specifier) {
2290
- return resolveRelativeSpecifier(path9.join(dir, "__faapi_probe__.ts"), specifier);
2783
+ return resolveRelativeSpecifier(path11.join(dir, "__faapi_probe__.ts"), specifier);
2291
2784
  }
2292
2785
  async function collectRelativeImports(entryFiles, rootDir) {
2293
- const appDirAbs = toRealPath(path9.resolve(rootDir, "src"));
2786
+ const appDirAbs = toRealPath(path11.resolve(rootDir, "src"));
2294
2787
  const tsconfig = readTsconfig(rootDir);
2295
2788
  const visited = /* @__PURE__ */ new Set();
2296
2789
  const insideFiles = /* @__PURE__ */ new Set();
@@ -2300,7 +2793,7 @@ async function collectRelativeImports(entryFiles, rootDir) {
2300
2793
  visited.add(filePath);
2301
2794
  let source;
2302
2795
  try {
2303
- source = await fs7.promises.readFile(filePath, "utf8");
2796
+ source = await fs8.promises.readFile(filePath, "utf8");
2304
2797
  } catch {
2305
2798
  return;
2306
2799
  }
@@ -2322,7 +2815,7 @@ async function collectRelativeImports(entryFiles, rootDir) {
2322
2815
  resolved = null;
2323
2816
  }
2324
2817
  if (!resolved) continue;
2325
- if (!isInsideDir(toRealPath(resolved), toRealPath(path9.resolve(rootDir)))) continue;
2818
+ if (!isInsideDir(toRealPath(resolved), toRealPath(path11.resolve(rootDir)))) continue;
2326
2819
  if (isInsideDir(toRealPath(resolved), appDirAbs)) {
2327
2820
  insideFiles.add(resolved);
2328
2821
  } else {
@@ -2343,8 +2836,8 @@ async function collectRelativeImports(entryFiles, rootDir) {
2343
2836
  // src/cli/compileOnDemand.ts
2344
2837
  function isProductFresh(sourceAbsPath, productAbsPath) {
2345
2838
  try {
2346
- const srcStat = fs8.statSync(sourceAbsPath);
2347
- const prodStat = fs8.statSync(productAbsPath);
2839
+ const srcStat = fs9.statSync(sourceAbsPath);
2840
+ const prodStat = fs9.statSync(productAbsPath);
2348
2841
  return prodStat.mtimeMs >= srcStat.mtimeMs;
2349
2842
  } catch {
2350
2843
  return false;
@@ -2376,7 +2869,7 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
2376
2869
  if (state.compiledFiles.has(sourceAbsPath)) {
2377
2870
  return false;
2378
2871
  }
2379
- if (!fs8.existsSync(sourceAbsPath)) {
2872
+ if (!fs9.existsSync(sourceAbsPath)) {
2380
2873
  return false;
2381
2874
  }
2382
2875
  const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
@@ -2426,11 +2919,11 @@ async function ensureMiddlewaresCompiled(middlewarePaths, rootDir) {
2426
2919
  }
2427
2920
  }
2428
2921
  function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
2429
- const rel = path10.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
2922
+ const rel = path12.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
2430
2923
  if (!rel.startsWith("src/")) return null;
2431
2924
  const relWithoutSrc = rel.slice(4);
2432
2925
  const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
2433
- return path10.resolve(rootDir, dist, jsRel);
2926
+ return path12.resolve(rootDir, dist, jsRel);
2434
2927
  }
2435
2928
  function clearGeneratedSchemas() {
2436
2929
  state.generatedSchemas.clear();
@@ -2446,9 +2939,9 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
2446
2939
  if (state.generatedSchemas.has(schemaPath)) {
2447
2940
  return false;
2448
2941
  }
2449
- const prodAbsPath = path10.resolve(rootDir, routeFilePath);
2942
+ const prodAbsPath = path12.resolve(rootDir, routeFilePath);
2450
2943
  const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
2451
- if (!fs8.existsSync(sourceAbsPath)) {
2944
+ if (!fs9.existsSync(sourceAbsPath)) {
2452
2945
  return false;
2453
2946
  }
2454
2947
  if (isProductFresh(sourceAbsPath, schemaPath)) {
@@ -2459,7 +2952,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
2459
2952
  if (fileRoutes.length === 0) {
2460
2953
  return false;
2461
2954
  }
2462
- const sourceRelPath = path10.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
2955
+ const sourceRelPath = path12.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
2463
2956
  const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
2464
2957
  const generatePromise = (async () => {
2465
2958
  await generateSchemaFiles(sourceRoutes, rootDir, dist);
@@ -2480,7 +2973,7 @@ async function deleteSchemaFiles(routes, rootDir, dist) {
2480
2973
  if (deleted.has(schemaPath)) continue;
2481
2974
  deleted.add(schemaPath);
2482
2975
  try {
2483
- await fs8.promises.unlink(schemaPath);
2976
+ await fs9.promises.unlink(schemaPath);
2484
2977
  } catch {
2485
2978
  }
2486
2979
  }
@@ -2489,19 +2982,19 @@ var sourcePathCache = /* @__PURE__ */ new Map();
2489
2982
  function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
2490
2983
  const cached = sourcePathCache.get(prodAbsPath);
2491
2984
  if (cached) return cached;
2492
- const rel = path10.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
2985
+ const rel = path12.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
2493
2986
  let relWithoutDist = rel;
2494
2987
  if (relWithoutDist.startsWith(`${dist}/`)) {
2495
2988
  relWithoutDist = relWithoutDist.slice(dist.length + 1);
2496
2989
  }
2497
2990
  const srcRel = `src/${relWithoutDist}`;
2498
2991
  const tsRel = srcRel.replace(/\.js$/, ".ts");
2499
- const tsAbs = path10.resolve(rootDir, tsRel);
2992
+ const tsAbs = path12.resolve(rootDir, tsRel);
2500
2993
  let result;
2501
- if (fs8.existsSync(tsAbs)) {
2994
+ if (fs9.existsSync(tsAbs)) {
2502
2995
  result = tsAbs;
2503
2996
  } else {
2504
- result = path10.resolve(rootDir, srcRel);
2997
+ result = path12.resolve(rootDir, srcRel);
2505
2998
  }
2506
2999
  sourcePathCache.set(prodAbsPath, result);
2507
3000
  return result;
@@ -2519,7 +3012,7 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
2519
3012
  const dist = getDevDist();
2520
3013
  if (dist) {
2521
3014
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
2522
- if (sourcePath && fs9.existsSync(sourcePath)) {
3015
+ if (sourcePath && fs10.existsSync(sourcePath)) {
2523
3016
  try {
2524
3017
  await ensureCompiled(sourcePath, rootDir, dist);
2525
3018
  } catch (compileErr) {
@@ -2552,13 +3045,13 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
2552
3045
  }
2553
3046
 
2554
3047
  // src/loader/loadToolModule.ts
2555
- import fs10 from "fs";
3048
+ import fs11 from "fs";
2556
3049
  async function loadToolModule(filePath, functionName, rootDir) {
2557
3050
  if (isDevOnDemandEnabled() && rootDir) {
2558
3051
  const dist = getDevDist();
2559
3052
  if (dist) {
2560
3053
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
2561
- if (sourcePath && fs10.existsSync(sourcePath)) {
3054
+ if (sourcePath && fs11.existsSync(sourcePath)) {
2562
3055
  try {
2563
3056
  await ensureCompiled(sourcePath, rootDir, dist);
2564
3057
  } catch (compileErr) {
@@ -2590,7 +3083,7 @@ async function loadToolModule(filePath, functionName, rootDir) {
2590
3083
  import { existsSync as existsSync2 } from "fs";
2591
3084
 
2592
3085
  // src/cli/generateToolArtifacts.ts
2593
- import path11 from "path";
3086
+ import path13 from "path";
2594
3087
  import { existsSync } from "fs";
2595
3088
 
2596
3089
  // src/ast/extractToolMetadata.ts
@@ -2714,7 +3207,7 @@ function collectToolSchemaSources(tools, rootDir) {
2714
3207
  const toolsByFile = /* @__PURE__ */ new Map();
2715
3208
  for (const tool of tools) {
2716
3209
  if (!tool.inputTypeName) continue;
2717
- const absPath = path11.resolve(rootDir, tool.filePath);
3210
+ const absPath = path13.resolve(rootDir, tool.filePath);
2718
3211
  let list = toolsByFile.get(absPath);
2719
3212
  if (!list) {
2720
3213
  list = [];
@@ -2774,15 +3267,15 @@ function generateToolSchemaFileSource(sources, resolveType, helpersImportPath) {
2774
3267
  }
2775
3268
  async function maybeGenerateHelpers(allSourceCode, distDir) {
2776
3269
  if (!usesCoerceHelpers(allSourceCode)) return;
2777
- const helpersPath = path11.resolve(distDir, HELPERS_FILENAME);
3270
+ const helpersPath = path13.resolve(distDir, HELPERS_FILENAME);
2778
3271
  if (existsSync(helpersPath)) return;
2779
3272
  await atomicWriteFile(helpersPath, generateHelpersFileSource());
2780
3273
  }
2781
3274
  async function generateToolArtifacts(tools, rootDir, dist, options) {
2782
3275
  const metadata = [];
2783
- const programByFile = createPrograms(tools.map((m) => path11.resolve(rootDir, m.filePath)));
3276
+ const programByFile = createPrograms(tools.map((m) => path13.resolve(rootDir, m.filePath)));
2784
3277
  for (const manifest of tools) {
2785
- const absPath = path11.resolve(rootDir, manifest.filePath);
3278
+ const absPath = path13.resolve(rootDir, manifest.filePath);
2786
3279
  const program = programByFile.get(absPath);
2787
3280
  const result = extractToolMetadata(program, absPath, manifest.functionName, {
2788
3281
  name: manifest.name,
@@ -2793,7 +3286,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
2793
3286
  }
2794
3287
  }
2795
3288
  const serialized = serializeTools(metadata, dist);
2796
- const toolsPath = path11.resolve(rootDir, dist, TOOLS_FILE);
3289
+ const toolsPath = path13.resolve(rootDir, dist, TOOLS_FILE);
2797
3290
  await writeToolsModule(serialized, toolsPath);
2798
3291
  if (options?.skipSchema) {
2799
3292
  return metadata;
@@ -2816,7 +3309,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
2816
3309
  }
2817
3310
  const fileEntries = [];
2818
3311
  for (const [filePath, fileSources] of sourcesByFile) {
2819
- const relFile = path11.relative(rootDir, filePath).replace(/\\/g, "/");
3312
+ const relFile = path13.relative(rootDir, filePath).replace(/\\/g, "/");
2820
3313
  const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
2821
3314
  const resolver = resolversByFile.get(filePath);
2822
3315
  let relForDir = relFile;
@@ -2834,7 +3327,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
2834
3327
  fileEntries.push({ outputPath, source });
2835
3328
  }
2836
3329
  const allSourceCode = fileEntries.map((e) => e.source).join("\n");
2837
- const distDir = path11.resolve(rootDir, dist);
3330
+ const distDir = path13.resolve(rootDir, dist);
2838
3331
  await maybeGenerateHelpers(allSourceCode, distDir);
2839
3332
  await Promise.all(
2840
3333
  fileEntries.map(({ outputPath, source }) => writeToolSchemaFile(outputPath, source))
@@ -3043,16 +3536,16 @@ function helmet(options = {}) {
3043
3536
  }
3044
3537
 
3045
3538
  // src/config/loadConfig.ts
3046
- import path12 from "path";
3047
- import fs11 from "fs";
3539
+ import path14 from "path";
3540
+ import fs12 from "fs";
3048
3541
  var CONFIG_PRODUCT_FILE = "faapi-config.js";
3049
3542
  async function loadConfig(rootDir, dist) {
3050
- const configProductPath = path12.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
3051
- if (fs11.existsSync(configProductPath)) {
3543
+ const configProductPath = path14.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
3544
+ if (fs12.existsSync(configProductPath)) {
3052
3545
  const module = await importWithCacheBust(configProductPath);
3053
3546
  return module.default ?? {};
3054
3547
  }
3055
- const hasSourceConfig = fs11.existsSync(path12.join(rootDir, "faapi.config.ts")) || fs11.existsSync(path12.join(rootDir, "faapi.config.js"));
3548
+ const hasSourceConfig = fs12.existsSync(path14.join(rootDir, "faapi.config.ts")) || fs12.existsSync(path14.join(rootDir, "faapi.config.js"));
3056
3549
  if (hasSourceConfig) {
3057
3550
  throw new Error(
3058
3551
  `[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`
@@ -3062,8 +3555,8 @@ async function loadConfig(rootDir, dist) {
3062
3555
  }
3063
3556
 
3064
3557
  // src/cli/loadEnv.ts
3065
- import fs12 from "fs";
3066
- import path13 from "path";
3558
+ import fs13 from "fs";
3559
+ import path15 from "path";
3067
3560
  function resolveEnv() {
3068
3561
  return process.env.NODE_ENV || "development";
3069
3562
  }
@@ -3130,9 +3623,9 @@ function loadEnv(rootDir) {
3130
3623
  const files = getEnvFiles(env);
3131
3624
  const merged = {};
3132
3625
  for (const file of files) {
3133
- const filePath = path13.join(rootDir, file);
3134
- if (!fs12.existsSync(filePath)) continue;
3135
- const content = fs12.readFileSync(filePath, "utf-8");
3626
+ const filePath = path15.join(rootDir, file);
3627
+ if (!fs13.existsSync(filePath)) continue;
3628
+ const content = fs13.readFileSync(filePath, "utf-8");
3136
3629
  const parsed = parseEnvFile(content, merged);
3137
3630
  Object.assign(merged, parsed);
3138
3631
  }
@@ -3143,67 +3636,9 @@ function loadEnv(rootDir) {
3143
3636
  }
3144
3637
  }
3145
3638
 
3146
- // src/errors/FaapiError.ts
3147
- var FaapiError = class extends Error {
3148
- constructor(code, message, statusCode) {
3149
- super(message);
3150
- this.code = code;
3151
- this.statusCode = statusCode;
3152
- this.name = "FaapiError";
3153
- }
3154
- code;
3155
- statusCode;
3156
- };
3157
-
3158
- // src/errors/httpErrors.ts
3159
- function deriveStatusCode(issues) {
3160
- const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
3161
- return has400 ? 400 : 422;
3162
- }
3163
- var ValidationError = class extends FaapiError {
3164
- constructor(message, issues) {
3165
- super("VALIDATION_ERROR", message, deriveStatusCode(issues));
3166
- this.issues = issues;
3167
- this.name = "ValidationError";
3168
- }
3169
- issues;
3170
- };
3171
- var RouteNotFoundError = class extends FaapiError {
3172
- constructor(path24) {
3173
- super("ROUTE_NOT_FOUND", `Route not found: ${path24}`, 404);
3174
- this.name = "RouteNotFoundError";
3175
- }
3176
- };
3177
- var MethodNotAllowedError = class extends FaapiError {
3178
- constructor(method, path24, allowedMethods) {
3179
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path24}`, 405);
3180
- this.allowedMethods = allowedMethods;
3181
- this.name = "MethodNotAllowedError";
3182
- }
3183
- allowedMethods;
3184
- };
3185
- var InternalError = class extends FaapiError {
3186
- constructor(message) {
3187
- super("INTERNAL_ERROR", message, 500);
3188
- this.name = "InternalError";
3189
- }
3190
- };
3191
- var ModuleLoadError = class extends FaapiError {
3192
- constructor(filePath, reason) {
3193
- super("MODULE_LOAD_ERROR", `Failed to load module ${filePath}: ${reason}`, 500);
3194
- this.name = "ModuleLoadError";
3195
- }
3196
- };
3197
- var PayloadTooLargeError = class extends FaapiError {
3198
- constructor(maxSize) {
3199
- super("PAYLOAD_TOO_LARGE", `Request body exceeds size limit of ${maxSize} bytes`, 413);
3200
- this.name = "PayloadTooLargeError";
3201
- }
3202
- };
3203
-
3204
3639
  // src/cli/createAppCore.ts
3205
- import fs17 from "fs";
3206
- import path20 from "path";
3640
+ import fs18 from "fs";
3641
+ import path23 from "path";
3207
3642
  import { PassThrough, Readable as Readable3 } from "stream";
3208
3643
 
3209
3644
  // src/router/sortRoutes.ts
@@ -3256,7 +3691,7 @@ import {
3256
3691
  import { createSecureServer as createHttp2SecureServer } from "http2";
3257
3692
  import { readFileSync } from "fs";
3258
3693
  import { Readable as Readable2 } from "stream";
3259
- import path15 from "path";
3694
+ import path17 from "path";
3260
3695
 
3261
3696
  // src/router/matchRoute.ts
3262
3697
  var httpIndexCache = /* @__PURE__ */ new WeakMap();
@@ -3298,18 +3733,18 @@ function getWsIndex(routes) {
3298
3733
  wsIndexCache.set(routes, index);
3299
3734
  return index;
3300
3735
  }
3301
- function matchRoute(routes, method, path24) {
3736
+ function matchRoute(routes, method, path28) {
3302
3737
  const index = getHttpIndex(routes);
3303
3738
  const upper = method.toUpperCase();
3304
- const hit = matchByMethod(index, upper, path24);
3739
+ const hit = matchByMethod(index, upper, path28);
3305
3740
  if (hit) return hit;
3306
3741
  if (upper === "HEAD") {
3307
- return matchByMethod(index, "GET", path24);
3742
+ return matchByMethod(index, "GET", path28);
3308
3743
  }
3309
3744
  return null;
3310
3745
  }
3311
- function matchByMethod(index, method, path24) {
3312
- const staticHit = index.static.get(`${method}|${path24}`);
3746
+ function matchByMethod(index, method, path28) {
3747
+ const staticHit = index.static.get(`${method}|${path28}`);
3313
3748
  if (staticHit) {
3314
3749
  return { route: staticHit, params: {} };
3315
3750
  }
@@ -3318,31 +3753,31 @@ function matchByMethod(index, method, path24) {
3318
3753
  if (route.method !== method) {
3319
3754
  continue;
3320
3755
  }
3321
- const params = matchSegments(entry.segments, path24, route.paramNames, route.isCatchAll);
3756
+ const params = matchSegments(entry.segments, path28, route.paramNames, route.isCatchAll);
3322
3757
  if (params !== null) {
3323
3758
  return { route, params };
3324
3759
  }
3325
3760
  }
3326
3761
  return null;
3327
3762
  }
3328
- function matchWsRoute(wsRoutes, path24) {
3763
+ function matchWsRoute(wsRoutes, path28) {
3329
3764
  const index = getWsIndex(wsRoutes);
3330
- const staticHit = index.static.get(path24);
3765
+ const staticHit = index.static.get(path28);
3331
3766
  if (staticHit) {
3332
3767
  return { route: staticHit, params: {} };
3333
3768
  }
3334
3769
  for (const route of index.dynamics) {
3335
- const params = matchDynamicPath(route.urlPath, path24, route.paramNames, route.isCatchAll);
3770
+ const params = matchDynamicPath(route.urlPath, path28, route.paramNames, route.isCatchAll);
3336
3771
  if (params !== null) {
3337
3772
  return { route, params };
3338
3773
  }
3339
3774
  }
3340
3775
  return null;
3341
3776
  }
3342
- function findAllowedMethods(routes, path24) {
3777
+ function findAllowedMethods(routes, path28) {
3343
3778
  const index = getHttpIndex(routes);
3344
3779
  const methods = /* @__PURE__ */ new Set();
3345
- const staticMethods = index.methodsByStaticPath.get(path24);
3780
+ const staticMethods = index.methodsByStaticPath.get(path28);
3346
3781
  if (staticMethods) {
3347
3782
  for (const method of staticMethods) {
3348
3783
  methods.add(method);
@@ -3351,7 +3786,7 @@ function findAllowedMethods(routes, path24) {
3351
3786
  for (const entry of index.dynamics) {
3352
3787
  const params = matchSegments(
3353
3788
  entry.segments,
3354
- path24,
3789
+ path28,
3355
3790
  entry.route.paramNames,
3356
3791
  entry.route.isCatchAll
3357
3792
  );
@@ -3364,11 +3799,11 @@ function findAllowedMethods(routes, path24) {
3364
3799
  }
3365
3800
  return Array.from(methods);
3366
3801
  }
3367
- function matchDynamicPath(pattern, path24, paramNames, isCatchAll) {
3368
- return matchSegments(pattern.split("/").filter(Boolean), path24, paramNames, isCatchAll);
3802
+ function matchDynamicPath(pattern, path28, paramNames, isCatchAll) {
3803
+ return matchSegments(pattern.split("/").filter(Boolean), path28, paramNames, isCatchAll);
3369
3804
  }
3370
- function matchSegments(patternSegments, path24, paramNames, isCatchAll) {
3371
- const pathSegments = path24.split("/").filter(Boolean);
3805
+ function matchSegments(patternSegments, path28, paramNames, isCatchAll) {
3806
+ const pathSegments = path28.split("/").filter(Boolean);
3372
3807
  if (isCatchAll) {
3373
3808
  const nonCatchAllCount = patternSegments.length - 1;
3374
3809
  if (pathSegments.length <= nonCatchAllCount) {
@@ -3773,6 +4208,7 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
3773
4208
  };
3774
4209
  if (registries) {
3775
4210
  ctx.registries = registries;
4211
+ ctx.tasks = registries.taskHandle.get(ctx);
3776
4212
  }
3777
4213
  const extend = config?.extendContext;
3778
4214
  if (typeof extend === "function") {
@@ -4062,6 +4498,9 @@ function getBuiltinInjectionValue(type, ctx, body) {
4062
4498
  // Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle
4063
4499
  case "agent":
4064
4500
  return ctx.registries ? ctx.registries.agentHandle.get(ctx) : getAgentHandle(ctx);
4501
+ // 任务子系统:注入 TaskClient(入队/查询);未注册工厂(无 app 编排)时 undefined
4502
+ case "tasks":
4503
+ return ctx.registries ? ctx.registries.taskHandle.get(ctx) : void 0;
4065
4504
  default:
4066
4505
  return void 0;
4067
4506
  }
@@ -4256,9 +4695,9 @@ async function validateInput(schemaPath, method, inputType, input) {
4256
4695
  function mapZodIssues(error) {
4257
4696
  return error.issues.map((issue) => {
4258
4697
  const code = mapZodCode(issue);
4259
- const path24 = issue.path.map(String).join(".") || "";
4698
+ const path28 = issue.path.map(String).join(".") || "";
4260
4699
  return {
4261
- path: path24,
4700
+ path: path28,
4262
4701
  code,
4263
4702
  expected: issue.expected ?? mapExpectedFromMessage(issue.message),
4264
4703
  received: issue.received ?? mapReceivedFromMessage(issue.message),
@@ -4490,9 +4929,9 @@ function etag(options = {}) {
4490
4929
  }
4491
4930
 
4492
4931
  // src/server/handleWsUpgrade.ts
4493
- import fs13 from "fs";
4932
+ import fs14 from "fs";
4494
4933
  import { WebSocketServer, WebSocket } from "ws";
4495
- import path14 from "path";
4934
+ import path16 from "path";
4496
4935
 
4497
4936
  // src/server/serverUtils.ts
4498
4937
  function nodeHttpToWebHeaders(req) {
@@ -4627,7 +5066,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
4627
5066
  const dist = getDevDist();
4628
5067
  if (dist) {
4629
5068
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
4630
- if (sourcePath && fs13.existsSync(sourcePath)) {
5069
+ if (sourcePath && fs14.existsSync(sourcePath)) {
4631
5070
  await ensureCompiled(sourcePath, rootDir, dist);
4632
5071
  }
4633
5072
  }
@@ -4741,7 +5180,7 @@ function attachWebSocket(options) {
4741
5180
  const finalHandler = async () => {
4742
5181
  let handlers;
4743
5182
  try {
4744
- const absoluteFilePath = path14.resolve(rootDir, route.filePath);
5183
+ const absoluteFilePath = path16.resolve(rootDir, route.filePath);
4745
5184
  handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
4746
5185
  } catch (err) {
4747
5186
  const reason = err instanceof Error ? err.message : String(err);
@@ -4994,7 +5433,7 @@ function getRoutePaths(route, rootDir, dist) {
4994
5433
  let cached = routePathCache.get(route);
4995
5434
  if (!cached) {
4996
5435
  cached = {
4997
- absFilePath: path15.resolve(rootDir, route.filePath),
5436
+ absFilePath: path17.resolve(rootDir, route.filePath),
4998
5437
  schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
4999
5438
  };
5000
5439
  routePathCache.set(route, cached);
@@ -5116,8 +5555,8 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
5116
5555
  }
5117
5556
 
5118
5557
  // src/cli/generateRoutes.ts
5119
- import fs14 from "fs";
5120
- import path16 from "path";
5558
+ import fs15 from "fs";
5559
+ import path18 from "path";
5121
5560
  async function hydrateRoutes(manifest) {
5122
5561
  const hydrateRoute = (serialized) => ({
5123
5562
  method: serialized.method,
@@ -5142,7 +5581,7 @@ async function hydrateRoutes(manifest) {
5142
5581
  }
5143
5582
 
5144
5583
  // src/cli/generateAgentArtifacts.ts
5145
- import path17 from "path";
5584
+ import path19 from "path";
5146
5585
 
5147
5586
  // src/ast/extractAgentMetadata.ts
5148
5587
  import ts10 from "typescript";
@@ -5415,9 +5854,9 @@ function validateAgentList(metadata) {
5415
5854
  }
5416
5855
  async function generateAgentArtifacts(agents, rootDir, dist) {
5417
5856
  const metadata = [];
5418
- const programByFile = createPrograms(agents.map((m) => path17.resolve(rootDir, m.filePath)));
5857
+ const programByFile = createPrograms(agents.map((m) => path19.resolve(rootDir, m.filePath)));
5419
5858
  for (const manifest of agents) {
5420
- const absPath = path17.resolve(rootDir, manifest.filePath);
5859
+ const absPath = path19.resolve(rootDir, manifest.filePath);
5421
5860
  const program = programByFile.get(absPath);
5422
5861
  const result = extractAgentMetadata(program, absPath, {
5423
5862
  name: manifest.name,
@@ -5433,24 +5872,181 @@ async function generateAgentArtifacts(agents, rootDir, dist) {
5433
5872
  }
5434
5873
  validateAgentList(metadata);
5435
5874
  const serialized = serializeAgents(metadata, dist);
5436
- const agentsPath = path17.resolve(rootDir, dist, AGENTS_FILE);
5875
+ const agentsPath = path19.resolve(rootDir, dist, AGENTS_FILE);
5437
5876
  await writeAgentsModule(serialized, agentsPath);
5438
5877
  return metadata;
5439
5878
  }
5440
5879
 
5880
+ // src/cli/generateTaskArtifacts.ts
5881
+ import path20 from "path";
5882
+ init_createProgram();
5883
+ init_atomicWrite();
5884
+ init_generateSchemaFiles();
5885
+ init_extractHandlerTypes();
5886
+ init_generateZodSchema();
5887
+ var TASKS_FILE = "faapi-tasks.js";
5888
+ function serializeTasks(tasks, dist = "dist") {
5889
+ return tasks.map((t) => ({
5890
+ name: t.name,
5891
+ filePath: toProdFilePath(t.filePath, dist),
5892
+ ...t.cron !== void 0 ? { cron: t.cron } : {},
5893
+ ...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
5894
+ ...t.retries !== void 0 ? { retries: t.retries } : {}
5895
+ }));
5896
+ }
5897
+ async function writeTasksModule(manifest, outputPath) {
5898
+ const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
5899
+ export const tasks = ${JSON.stringify(manifest, null, 2)};
5900
+ `;
5901
+ await atomicWriteFile(outputPath, content);
5902
+ }
5903
+ function hydrateTasks(manifest) {
5904
+ return manifest.map((t) => ({
5905
+ name: t.name,
5906
+ filePath: t.filePath,
5907
+ cron: t.cron ?? void 0,
5908
+ concurrency: t.concurrency ?? void 0,
5909
+ retries: t.retries ?? void 0
5910
+ }));
5911
+ }
5912
+ function collectTaskSchemaSources(tasks, rootDir) {
5913
+ const tasksByFile = /* @__PURE__ */ new Map();
5914
+ for (const task of tasks) {
5915
+ if (!task.inputTypeName) continue;
5916
+ const absPath = path20.resolve(rootDir, task.filePath);
5917
+ let list = tasksByFile.get(absPath);
5918
+ if (!list) {
5919
+ list = [];
5920
+ tasksByFile.set(absPath, list);
5921
+ }
5922
+ list.push(task);
5923
+ }
5924
+ const programByFile = createPrograms([...tasksByFile.keys()]);
5925
+ const resolversByFile = /* @__PURE__ */ new Map();
5926
+ for (const filePath of tasksByFile.keys()) {
5927
+ resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath), filePath));
5928
+ }
5929
+ const sources = [];
5930
+ for (const [filePath, fileTasks] of tasksByFile) {
5931
+ const program = programByFile.get(filePath);
5932
+ for (const task of fileTasks) {
5933
+ const typeInfo = extractTypeInfo(program, filePath, task.inputTypeName);
5934
+ sources.push({
5935
+ name: task.taskName,
5936
+ filePath,
5937
+ schemaName: task.inputTypeName,
5938
+ typeInfo
5939
+ });
5940
+ }
5941
+ }
5942
+ return { sources, resolversByFile };
5943
+ }
5944
+ function generateTaskSchemaFileSource(sources, resolveType, helpersImportPath) {
5945
+ const lines = ["import { z } from 'zod';"];
5946
+ const schemaBlocks = [];
5947
+ for (const source of sources) {
5948
+ if (!source.typeInfo) continue;
5949
+ const block = [`// task ${source.name} \u2192 ${source.schemaName}`];
5950
+ const schemaCode = generateZodSchemaSource(
5951
+ source.typeInfo,
5952
+ resolveType,
5953
+ source.schemaName,
5954
+ false
5955
+ ).replace(/^import \{ z \} from 'zod';\s*\n\s*\n/, "");
5956
+ block.push(schemaCode);
5957
+ block.push("");
5958
+ schemaBlocks.push(block.join("\n"));
5959
+ }
5960
+ const allSchemaCode = schemaBlocks.join("\n");
5961
+ if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
5962
+ lines.push(
5963
+ `import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
5964
+ );
5965
+ }
5966
+ lines.push("");
5967
+ lines.push(...schemaBlocks);
5968
+ return lines.join("\n").replace(/\n+$/, "\n");
5969
+ }
5970
+ async function generateTaskArtifacts(manifests, rootDir, dist) {
5971
+ const metadata = [];
5972
+ if (manifests.length > 0) {
5973
+ const programByFile = createPrograms(manifests.map((m) => path20.resolve(rootDir, m.filePath)));
5974
+ for (const manifest of manifests) {
5975
+ const absPath = path20.resolve(rootDir, manifest.filePath);
5976
+ const result = extractToolMetadata(programByFile.get(absPath), absPath, "run", {
5977
+ name: manifest.name,
5978
+ filePath: manifest.filePath
5979
+ });
5980
+ if (result) {
5981
+ metadata.push({ ...result, taskName: manifest.name });
5982
+ }
5983
+ }
5984
+ }
5985
+ const serialized = serializeTasks(manifests, dist);
5986
+ const tasksPath = path20.resolve(rootDir, dist, TASKS_FILE);
5987
+ await writeTasksModule(serialized, tasksPath);
5988
+ if (metadata.length === 0) {
5989
+ return hydrateTasks(serialized);
5990
+ }
5991
+ const { sources, resolversByFile } = collectTaskSchemaSources(metadata, rootDir);
5992
+ if (sources.length === 0) {
5993
+ return hydrateTasks(serialized);
5994
+ }
5995
+ const sourcesByFile = /* @__PURE__ */ new Map();
5996
+ for (const source of sources) {
5997
+ let list = sourcesByFile.get(source.filePath);
5998
+ if (!list) {
5999
+ list = [];
6000
+ sourcesByFile.set(source.filePath, list);
6001
+ }
6002
+ list.push(source);
6003
+ }
6004
+ const fileEntries = [];
6005
+ for (const [filePath, fileSources] of sourcesByFile) {
6006
+ const relFile = path20.relative(rootDir, filePath).replace(/\\/g, "/");
6007
+ const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
6008
+ const resolver = resolversByFile.get(filePath);
6009
+ let relForDir = relFile;
6010
+ if (relForDir.startsWith("src/")) {
6011
+ relForDir = relForDir.slice(4);
6012
+ }
6013
+ const dirIdx = relForDir.lastIndexOf("/");
6014
+ const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
6015
+ const helpersImportPath = getHelpersImportPath(zodRelDir);
6016
+ const source = generateTaskSchemaFileSource(
6017
+ fileSources,
6018
+ (name) => resolver?.resolve(name)?.runtimeType,
6019
+ helpersImportPath
6020
+ );
6021
+ fileEntries.push({ outputPath, source });
6022
+ }
6023
+ const allSourceCode = fileEntries.map((e) => e.source).join("\n");
6024
+ if (usesCoerceHelpers(allSourceCode)) {
6025
+ const helpersPath = path20.resolve(rootDir, dist, HELPERS_FILENAME);
6026
+ const { existsSync: existsSync3 } = await import("fs");
6027
+ if (!existsSync3(helpersPath)) {
6028
+ await atomicWriteFile(helpersPath, generateHelpersFileSource());
6029
+ }
6030
+ }
6031
+ await Promise.all(
6032
+ fileEntries.map(({ outputPath, source }) => atomicWriteFile(outputPath, source))
6033
+ );
6034
+ return hydrateTasks(serialized);
6035
+ }
6036
+
5441
6037
  // src/cli/loadPlugins.ts
5442
- import path19 from "path";
5443
- import fs16 from "fs";
6038
+ import path22 from "path";
6039
+ import fs17 from "fs";
5444
6040
  import { pathToFileURL as pathToFileURL2 } from "url";
5445
6041
 
5446
6042
  // src/cli/compileConfig.ts
5447
- import path18 from "path";
5448
- import fs15 from "fs";
6043
+ import path21 from "path";
6044
+ import fs16 from "fs";
5449
6045
  async function compileProjectModules(entryPoints, rootDir, dist) {
5450
6046
  const { insideFiles, outsideFiles } = await collectRelativeImports(entryPoints, rootDir);
5451
6047
  const esbuild = await import("esbuild");
5452
6048
  const aliasPlugins = buildAliasPlugins(rootDir);
5453
- const absDist = path18.resolve(rootDir, dist);
6049
+ const absDist = path21.resolve(rootDir, dist);
5454
6050
  await esbuild.build({
5455
6051
  entryPoints: [...entryPoints, ...outsideFiles],
5456
6052
  outdir: absDist,
@@ -5464,7 +6060,7 @@ async function compileProjectModules(entryPoints, rootDir, dist) {
5464
6060
  logLevel: "silent"
5465
6061
  });
5466
6062
  if (insideFiles.length > 0) {
5467
- const appOutbase = path18.resolve(rootDir, "src");
6063
+ const appOutbase = path21.resolve(rootDir, "src");
5468
6064
  await esbuild.build({
5469
6065
  entryPoints: insideFiles,
5470
6066
  outdir: absDist,
@@ -5545,37 +6141,37 @@ async function loadPlugins(declarations, ctx, rootDir, dist) {
5545
6141
  var TS_EXTS = /\.(ts|tsx|mts|cts)$/;
5546
6142
  var JS_EXTS = /\.(js|mjs|cjs|jsx)$/;
5547
6143
  function resolveLocalPluginSource(specifier, baseDir) {
5548
- const base = path19.isAbsolute(specifier) ? specifier : path19.resolve(baseDir, specifier);
5549
- if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs16.existsSync(base)) return base;
6144
+ const base = path22.isAbsolute(specifier) ? specifier : path22.resolve(baseDir, specifier);
6145
+ if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs17.existsSync(base)) return base;
5550
6146
  for (const ext of [".ts", ".js"]) {
5551
- if (fs16.existsSync(base + ext)) return base + ext;
6147
+ if (fs17.existsSync(base + ext)) return base + ext;
5552
6148
  }
5553
6149
  for (const indexExt of ["/index.ts", "/index.js"]) {
5554
- if (fs16.existsSync(base + indexExt)) return base + indexExt;
6150
+ if (fs17.existsSync(base + indexExt)) return base + indexExt;
5555
6151
  }
5556
6152
  return null;
5557
6153
  }
5558
6154
  function mirrorProductPath(sourcePath, baseDir, distDir) {
5559
- let rel = path19.relative(baseDir, sourcePath).replace(/\\/g, "/");
6155
+ let rel = path22.relative(baseDir, sourcePath).replace(/\\/g, "/");
5560
6156
  if (rel.startsWith("src/")) rel = rel.slice(4);
5561
- return path19.resolve(distDir, rel.replace(TS_EXTS, ".js"));
6157
+ return path22.resolve(distDir, rel.replace(TS_EXTS, ".js"));
5562
6158
  }
5563
6159
  function isSourceNewer(sourcePath, productPath) {
5564
6160
  try {
5565
- return fs16.statSync(sourcePath).mtimeMs > fs16.statSync(productPath).mtimeMs;
6161
+ return fs17.statSync(sourcePath).mtimeMs > fs17.statSync(productPath).mtimeMs;
5566
6162
  } catch {
5567
6163
  return true;
5568
6164
  }
5569
6165
  }
5570
6166
  async function importPluginModule(specifier, baseDir, dist) {
5571
6167
  const isRelative = specifier.startsWith("./") || specifier.startsWith("../");
5572
- if (!isRelative && !path19.isAbsolute(specifier)) {
6168
+ if (!isRelative && !path22.isAbsolute(specifier)) {
5573
6169
  return import(specifier);
5574
6170
  }
5575
- const distDir = dist ? path19.resolve(baseDir, dist) : null;
6171
+ const distDir = dist ? path22.resolve(baseDir, dist) : null;
5576
6172
  const sourcePath = resolveLocalPluginSource(specifier, baseDir);
5577
6173
  const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
5578
- if (productPath !== null && fs16.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
6174
+ if (productPath !== null && fs17.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
5579
6175
  return import(pathToFileURL2(productPath).href);
5580
6176
  }
5581
6177
  if (sourcePath && TS_EXTS.test(sourcePath)) {
@@ -5584,13 +6180,13 @@ async function importPluginModule(specifier, baseDir, dist) {
5584
6180
  `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.`
5585
6181
  );
5586
6182
  }
5587
- const relFromRoot = path19.relative(baseDir, sourcePath).replace(/\\/g, "/");
6183
+ const relFromRoot = path22.relative(baseDir, sourcePath).replace(/\\/g, "/");
5588
6184
  if (relFromRoot.startsWith("src/")) {
5589
6185
  await ensureCompiled(sourcePath, baseDir, dist);
5590
6186
  } else {
5591
6187
  await compileProjectModules([sourcePath], baseDir, dist);
5592
6188
  }
5593
- if (productPath && fs16.existsSync(productPath)) {
6189
+ if (productPath && fs17.existsSync(productPath)) {
5594
6190
  return import(pathToFileURL2(productPath).href);
5595
6191
  }
5596
6192
  }
@@ -5602,7 +6198,7 @@ async function importPluginModule(specifier, baseDir, dist) {
5602
6198
  `${specifier}/index.ts`,
5603
6199
  `${specifier}.js`,
5604
6200
  `${specifier}/index.js`
5605
- ].map((c) => path19.isAbsolute(c) ? c : path19.join(baseDir, c));
6201
+ ].map((c) => path22.isAbsolute(c) ? c : path22.join(baseDir, c));
5606
6202
  throw new Error(
5607
6203
  `Cannot find local plugin "${specifier}" (resolved from ${baseDir}). Expected one of:
5608
6204
  ${candidates.join("\n ")}
@@ -5633,8 +6229,8 @@ var ROUTES_FILE = "faapi-routes.js";
5633
6229
  var TOOLS_FILE2 = "faapi-tools.js";
5634
6230
  var AGENTS_FILE2 = "faapi-agents.js";
5635
6231
  async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries) {
5636
- const toolsPath = path20.resolve(rootDir, dist, TOOLS_FILE2);
5637
- if (!fs17.existsSync(toolsPath)) {
6232
+ const toolsPath = path23.resolve(rootDir, dist, TOOLS_FILE2);
6233
+ if (!fs18.existsSync(toolsPath)) {
5638
6234
  return [];
5639
6235
  }
5640
6236
  const serialized = await importWithCacheBust(toolsPath);
@@ -5643,8 +6239,8 @@ async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries
5643
6239
  return hydrated;
5644
6240
  }
5645
6241
  async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistries) {
5646
- const agentsPath = path20.resolve(rootDir, dist, AGENTS_FILE2);
5647
- if (!fs17.existsSync(agentsPath)) {
6242
+ const agentsPath = path23.resolve(rootDir, dist, AGENTS_FILE2);
6243
+ if (!fs18.existsSync(agentsPath)) {
5648
6244
  return [];
5649
6245
  }
5650
6246
  const serialized = await importWithCacheBust(agentsPath);
@@ -5652,6 +6248,24 @@ async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistrie
5652
6248
  registries.agent.hydrate(hydrated);
5653
6249
  return hydrated;
5654
6250
  }
6251
+ function getTaskDriverOptions(config) {
6252
+ if (!config?.task) return void 0;
6253
+ const taskConfig = config.task;
6254
+ const driver = taskConfig.driver;
6255
+ if (driver === "pgboss") return taskConfig.pgboss;
6256
+ if (driver === "bullmq") return taskConfig.bullmq;
6257
+ return void 0;
6258
+ }
6259
+ async function loadAndHydrateTasks(rootDir, dist, registries = defaultRegistries) {
6260
+ const tasksPath = path23.resolve(rootDir, dist, TASKS_FILE);
6261
+ if (!fs18.existsSync(tasksPath)) {
6262
+ return [];
6263
+ }
6264
+ const serialized = await importWithCacheBust(tasksPath);
6265
+ const hydrated = hydrateTasks(serialized.tasks ?? []);
6266
+ registries.task.hydrate(hydrated);
6267
+ return hydrated;
6268
+ }
5655
6269
  var APP_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("faapi.app.instance");
5656
6270
  function getCurrentApp() {
5657
6271
  return globalThis[APP_INSTANCE_KEY] ?? null;
@@ -5703,7 +6317,8 @@ var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
5703
6317
  "logger",
5704
6318
  "http2",
5705
6319
  "trustedProxy",
5706
- "response"
6320
+ "response",
6321
+ "task"
5707
6322
  ]);
5708
6323
  function isFaapiConfigKey(key) {
5709
6324
  return FAAPI_CONFIG_KEYS.has(key);
@@ -5711,8 +6326,8 @@ function isFaapiConfigKey(key) {
5711
6326
  async function createAppBase(options) {
5712
6327
  const rootDir = options?.rootDir ?? process.cwd();
5713
6328
  const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
5714
- const routesPath = path20.resolve(rootDir, dist, ROUTES_FILE);
5715
- if (!fs17.existsSync(routesPath)) {
6329
+ const routesPath = path23.resolve(rootDir, dist, ROUTES_FILE);
6330
+ if (!fs18.existsSync(routesPath)) {
5716
6331
  throw new Error(
5717
6332
  `[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
5718
6333
  );
@@ -5734,6 +6349,28 @@ async function createAppBase(options) {
5734
6349
  const registries = createAppRegistries();
5735
6350
  const tools = await loadAndHydrateTools(rootDir, dist, registries);
5736
6351
  const agents = await loadAndHydrateAgents(rootDir, dist, registries);
6352
+ const taskMetas = await loadAndHydrateTasks(rootDir, dist, registries);
6353
+ const taskDriver = await loadTaskDriver(config?.task?.driver, getTaskDriverOptions(config));
6354
+ const taskQueue = createTaskQueue({
6355
+ registry: registries.task,
6356
+ rootDir,
6357
+ config,
6358
+ driver: taskDriver
6359
+ });
6360
+ const cronScheduler = createCronScheduler(
6361
+ registries.task,
6362
+ (name) => taskQueue.enqueue(name)
6363
+ );
6364
+ const taskEnabled = process.env.FAAPI_TASKS_DISABLED === "1" ? false : config?.task?.enabled ?? true;
6365
+ const stopTaskRuntime = async () => {
6366
+ cronScheduler.stop();
6367
+ await taskQueue.stop(config?.task?.shutdownTimeoutMs ?? 1e4);
6368
+ };
6369
+ if (taskEnabled) {
6370
+ taskQueue.start();
6371
+ cronScheduler.start();
6372
+ }
6373
+ registries.taskHandle.register(() => taskQueue);
5737
6374
  const pluginConfig = config ? Object.fromEntries(Object.entries(config).filter(([k]) => !isFaapiConfigKey(k))) : {};
5738
6375
  const { server, routesRef } = createServer({
5739
6376
  routes: sorted,
@@ -5775,14 +6412,22 @@ async function createAppBase(options) {
5775
6412
  routes: sorted,
5776
6413
  wsRoutes,
5777
6414
  rootDir,
6415
+ tasks: taskQueue,
5778
6416
  async listen(listenPort) {
5779
6417
  const envPort = process.env.PORT ? Number(process.env.PORT) : void 0;
5780
6418
  const actualPort = listenPort ?? options?.port ?? envPort ?? DEFAULT_PORT;
5781
6419
  if (config?.lifecycle?.onBoot) {
5782
6420
  try {
5783
- await config.lifecycle.onBoot({ rootDir, routes: sorted, server, registries });
6421
+ await config.lifecycle.onBoot({
6422
+ rootDir,
6423
+ routes: sorted,
6424
+ server,
6425
+ registries,
6426
+ tasks: taskQueue
6427
+ });
5784
6428
  console.log("- onBoot hook executed");
5785
6429
  } catch (err) {
6430
+ await stopTaskRuntime();
5786
6431
  const message = err instanceof Error ? err.message : String(err);
5787
6432
  console.error(`[faapi] onBoot hook failed: ${message}`);
5788
6433
  throw err;
@@ -5830,9 +6475,22 @@ async function createAppBase(options) {
5830
6475
  console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
5831
6476
  }
5832
6477
  }
6478
+ if (taskMetas.length > 0) {
6479
+ console.log(`- Loaded ${taskMetas.length} task(s):`);
6480
+ for (const taskMeta of taskMetas) {
6481
+ const schedule = taskMeta.cron ? ` cron=${taskMeta.cron}` : "";
6482
+ console.log(` ${taskMeta.name}${schedule} ${taskMeta.filePath}`);
6483
+ }
6484
+ }
5833
6485
  registerDefaultShutdownHandlers();
5834
6486
  if (config?.lifecycle?.onReady) {
5835
- await config.lifecycle.onReady({ rootDir, routes: sorted, server, registries });
6487
+ await config.lifecycle.onReady({
6488
+ rootDir,
6489
+ routes: sorted,
6490
+ server,
6491
+ registries,
6492
+ tasks: taskQueue
6493
+ });
5836
6494
  console.log("- onReady hook executed");
5837
6495
  }
5838
6496
  app.server = server;
@@ -5925,13 +6583,22 @@ async function createAppBase(options) {
5925
6583
  closed = true;
5926
6584
  const s = server;
5927
6585
  s.closeIdleConnections?.();
6586
+ await stopTaskRuntime();
5928
6587
  if (config?.lifecycle?.onClose) {
5929
- await config.lifecycle.onClose({ rootDir, routes: sorted, server, registries });
6588
+ await config.lifecycle.onClose({
6589
+ rootDir,
6590
+ routes: sorted,
6591
+ server,
6592
+ registries,
6593
+ tasks: taskQueue
6594
+ });
5930
6595
  }
5931
6596
  registries.tool.clear();
5932
6597
  registries.agent.clear();
5933
6598
  registries.skill.clear();
6599
+ registries.task.clear();
5934
6600
  registries.agentHandle.clear();
6601
+ registries.taskHandle.clear();
5935
6602
  if (!server.listening) {
5936
6603
  app.server = null;
5937
6604
  if (getCurrentApp() === app) setCurrentApp(null);
@@ -5968,6 +6635,7 @@ async function createAppBase(options) {
5968
6635
  registries,
5969
6636
  dist,
5970
6637
  patterns: ROUTE_PATTERNS,
6638
+ taskQueue,
5971
6639
  server,
5972
6640
  routesRef,
5973
6641
  config,
@@ -5983,19 +6651,22 @@ async function createAppBase(options) {
5983
6651
  return { app, ctx };
5984
6652
  }
5985
6653
 
6654
+ // src/cli/createDevApp.ts
6655
+ import path27 from "path";
6656
+
5986
6657
  // src/router/scanRoutes.ts
5987
- import fg2 from "fast-glob";
5988
- import path21 from "path";
5989
- import fs18 from "fs";
6658
+ import fg3 from "fast-glob";
6659
+ import path24 from "path";
6660
+ import fs19 from "fs";
5990
6661
 
5991
6662
  // src/router/constants.ts
5992
6663
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
5993
6664
  var HTTP_METHOD_SET = new Set(HTTP_METHODS);
5994
6665
 
5995
6666
  // src/utils/normalizePath.ts
5996
- function normalizePath(path24) {
5997
- if (!path24) return "";
5998
- let result = path24.replace(/\\/g, "/");
6667
+ function normalizePath(path28) {
6668
+ if (!path28) return "";
6669
+ let result = path28.replace(/\\/g, "/");
5999
6670
  result = result.replace(/\/+/g, "/");
6000
6671
  result = result.replace(/\/+$/, "");
6001
6672
  if (result && !result.startsWith("/")) {
@@ -6056,34 +6727,34 @@ function extractExportsFromSource(source) {
6056
6727
  return names;
6057
6728
  }
6058
6729
  function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
6059
- const routeDir = path21.dirname(routeFilePath);
6060
- const resolvedRoot = path21.resolve(rootDir);
6730
+ const routeDir = path24.dirname(routeFilePath);
6731
+ const resolvedRoot = path24.resolve(rootDir);
6061
6732
  const paths = [];
6062
- let currentDir = path21.resolve(rootDir, routeDir);
6733
+ let currentDir = path24.resolve(rootDir, routeDir);
6063
6734
  while (true) {
6064
6735
  if (dist) {
6065
- const mwTsPath = path21.join(currentDir, "middlewares.ts");
6066
- const mwJsPath = path21.join(currentDir, "middlewares.js");
6067
- const absTsPath = path21.resolve(rootDir, mwTsPath);
6068
- const absJsPath = path21.resolve(rootDir, mwJsPath);
6069
- const absMwPath = fs18.existsSync(absTsPath) ? absTsPath : fs18.existsSync(absJsPath) ? absJsPath : null;
6736
+ const mwTsPath = path24.join(currentDir, "middlewares.ts");
6737
+ const mwJsPath = path24.join(currentDir, "middlewares.js");
6738
+ const absTsPath = path24.resolve(rootDir, mwTsPath);
6739
+ const absJsPath = path24.resolve(rootDir, mwJsPath);
6740
+ const absMwPath = fs19.existsSync(absTsPath) ? absTsPath : fs19.existsSync(absJsPath) ? absJsPath : null;
6070
6741
  if (absMwPath) {
6071
- const relMwPath = path21.relative(rootDir, absMwPath);
6072
- const prodAbsPath = path21.resolve(rootDir, toProdFilePath(relMwPath, dist));
6742
+ const relMwPath = path24.relative(rootDir, absMwPath);
6743
+ const prodAbsPath = path24.resolve(rootDir, toProdFilePath(relMwPath, dist));
6073
6744
  paths.push(prodAbsPath);
6074
6745
  }
6075
6746
  } else {
6076
6747
  for (const ext of [".ts", ".js"]) {
6077
- const mwPath = path21.join(currentDir, `middlewares${ext}`);
6078
- const absMwPath = path21.resolve(rootDir, mwPath);
6079
- if (fs18.existsSync(absMwPath)) {
6748
+ const mwPath = path24.join(currentDir, `middlewares${ext}`);
6749
+ const absMwPath = path24.resolve(rootDir, mwPath);
6750
+ if (fs19.existsSync(absMwPath)) {
6080
6751
  paths.push(absMwPath);
6081
6752
  break;
6082
6753
  }
6083
6754
  }
6084
6755
  }
6085
6756
  if (currentDir === resolvedRoot) break;
6086
- const parentDir = path21.dirname(currentDir);
6757
+ const parentDir = path24.dirname(currentDir);
6087
6758
  if (parentDir === currentDir) break;
6088
6759
  currentDir = parentDir;
6089
6760
  }
@@ -6091,7 +6762,7 @@ function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
6091
6762
  return paths;
6092
6763
  }
6093
6764
  async function scanRoutes(rootDir, patterns, dist) {
6094
- const files = await fg2(patterns, {
6765
+ const files = await fg3(patterns, {
6095
6766
  cwd: rootDir,
6096
6767
  onlyFiles: true,
6097
6768
  absolute: false
@@ -6102,7 +6773,7 @@ async function scanRoutes(rootDir, patterns, dist) {
6102
6773
  const normalizedFile = file.replace(/\\/g, "/");
6103
6774
  const fileName = normalizedFile.split("/").pop();
6104
6775
  if (fileName === "handler.ts" || fileName === "handler.js") {
6105
- const absPath = path21.resolve(rootDir, normalizedFile);
6776
+ const absPath = path24.resolve(rootDir, normalizedFile);
6106
6777
  const urlPath = filePathToUrlPath(normalizedFile);
6107
6778
  const paramNames = extractParamNames(urlPath);
6108
6779
  const isDynamic = paramNames.length > 0;
@@ -6115,7 +6786,7 @@ async function scanRoutes(rootDir, patterns, dist) {
6115
6786
  const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
6116
6787
  middlewareBundle = await loadMergedMiddlewares(mwPaths);
6117
6788
  }
6118
- const source = await fs18.promises.readFile(absPath, "utf8").catch(() => "");
6789
+ const source = await fs19.promises.readFile(absPath, "utf8").catch(() => "");
6119
6790
  const exportNames = extractExportsFromSource(source);
6120
6791
  const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
6121
6792
  for (const method of methods) {
@@ -6150,9 +6821,9 @@ async function scanRoutes(rootDir, patterns, dist) {
6150
6821
  }
6151
6822
 
6152
6823
  // src/tools/scanTools.ts
6153
- import fg3 from "fast-glob";
6154
- import path22 from "path";
6155
- import fs19 from "fs";
6824
+ import fg4 from "fast-glob";
6825
+ import path25 from "path";
6826
+ import fs20 from "fs";
6156
6827
  var TOOL_PATTERNS = ["src/tools/**/*.ts"];
6157
6828
  var TOOL_EXPORT_RE = new RegExp(
6158
6829
  String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
@@ -6189,7 +6860,7 @@ function buildToolName(namespace, functionName) {
6189
6860
  return namespace ? `${namespace}.${functionName}` : functionName;
6190
6861
  }
6191
6862
  async function scanTools(rootDir, patterns) {
6192
- const files = await fg3(patterns, {
6863
+ const files = await fg4(patterns, {
6193
6864
  cwd: rootDir,
6194
6865
  onlyFiles: true,
6195
6866
  absolute: false
@@ -6202,8 +6873,8 @@ async function scanTools(rootDir, patterns) {
6202
6873
  if (fileName !== "handler.ts" && fileName !== "handler.js") {
6203
6874
  continue;
6204
6875
  }
6205
- const absPath = path22.resolve(rootDir, normalizedFile);
6206
- const source = await fs19.promises.readFile(absPath, "utf8").catch(() => "");
6876
+ const absPath = path25.resolve(rootDir, normalizedFile);
6877
+ const source = await fs20.promises.readFile(absPath, "utf8").catch(() => "");
6207
6878
  const exportNames = extractToolExportsFromSource(source);
6208
6879
  const namespace = filePathToToolNamespace(normalizedFile);
6209
6880
  for (const fnName of exportNames) {
@@ -6226,9 +6897,9 @@ async function scanTools(rootDir, patterns) {
6226
6897
  }
6227
6898
 
6228
6899
  // src/agents/scanAgents.ts
6229
- import fg4 from "fast-glob";
6230
- import path23 from "path";
6231
- import fs20 from "fs";
6900
+ import fg5 from "fast-glob";
6901
+ import path26 from "path";
6902
+ import fs21 from "fs";
6232
6903
  var DEFAULT_AGENT_PATTERNS = ["src/agents/**/handler.ts"];
6233
6904
  var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
6234
6905
  function extractAgentNameFromPath(filePath) {
@@ -6247,7 +6918,7 @@ function detectAgentExports(source) {
6247
6918
  };
6248
6919
  }
6249
6920
  async function scanAgents(rootDir, patterns) {
6250
- const files = await fg4(patterns, {
6921
+ const files = await fg5(patterns, {
6251
6922
  cwd: rootDir,
6252
6923
  onlyFiles: true,
6253
6924
  absolute: false
@@ -6260,8 +6931,8 @@ async function scanAgents(rootDir, patterns) {
6260
6931
  if (fileName !== "handler.ts" && fileName !== "handler.js") {
6261
6932
  continue;
6262
6933
  }
6263
- const absPath = path23.resolve(rootDir, normalizedFile);
6264
- const source = await fs20.promises.readFile(absPath, "utf8").catch(() => "");
6934
+ const absPath = path26.resolve(rootDir, normalizedFile);
6935
+ const source = await fs21.promises.readFile(absPath, "utf8").catch(() => "");
6265
6936
  const { hasRun } = detectAgentExports(source);
6266
6937
  const name = extractAgentNameFromPath(normalizedFile);
6267
6938
  const prevFile = seen.get(name);
@@ -6326,6 +6997,19 @@ async function createDevApp(options) {
6326
6997
  await generateAgentArtifacts(agents, ctx.rootDir, ctx.dist);
6327
6998
  await loadAndHydrateAgents(ctx.rootDir, ctx.dist, ctx.registries);
6328
6999
  };
7000
+ devApp.reloadTasks = async () => {
7001
+ setLoadTimestamp(Date.now());
7002
+ invalidateProgramCache();
7003
+ const tasks = await scanTasks(ctx.rootDir, TASK_PATTERNS);
7004
+ for (const task of tasks) {
7005
+ await ensureCompiled(path27.resolve(ctx.rootDir, task.filePath), ctx.rootDir, ctx.dist);
7006
+ }
7007
+ await generateTaskArtifacts(tasks, ctx.rootDir, ctx.dist);
7008
+ ctx.taskQueue.invalidateModules();
7009
+ ctx.taskQueue.invalidateSchemas();
7010
+ await ctx.taskQueue.reload();
7011
+ await loadAndHydrateTasks(ctx.rootDir, ctx.dist, ctx.registries);
7012
+ };
6329
7013
  return devApp;
6330
7014
  }
6331
7015
 
@@ -6341,16 +7025,21 @@ export {
6341
7025
  ModuleLoadError,
6342
7026
  RouteNotFoundError,
6343
7027
  SchemaExtractionError,
7028
+ TASK_PATTERNS,
6344
7029
  ValidationError,
6345
7030
  clearAgentHandleFactory,
6346
7031
  collectRouteSchemaSources,
6347
7032
  cors,
6348
7033
  createProdApp as createApp,
6349
7034
  createAppRegistries,
7035
+ createCronScheduler,
6350
7036
  createDevApp,
7037
+ createMemoryDriver,
6351
7038
  createProdApp,
6352
7039
  createProgram,
6353
7040
  createPrograms,
7041
+ createTaskQueue,
7042
+ createTaskRegistry,
6354
7043
  extractTypeInfo,
6355
7044
  getAgent,
6356
7045
  getAgentEntry,
@@ -6366,6 +7055,7 @@ export {
6366
7055
  loadAgentModule,
6367
7056
  loadConfig,
6368
7057
  loadEnv,
7058
+ loadTaskDriver,
6369
7059
  loadToolModule,
6370
7060
  loadToolSchema,
6371
7061
  logger,
@@ -6374,6 +7064,7 @@ export {
6374
7064
  resolveAgentTools,
6375
7065
  resolveSubAgents,
6376
7066
  resolveTypeNode,
7067
+ scanTasks,
6377
7068
  upsertSkill
6378
7069
  };
6379
7070
  //# sourceMappingURL=index.js.map