@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/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,604 @@ 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/taskWorker.ts
1945
+ import { Worker } from "worker_threads";
1946
+ import { pathToFileURL } from "url";
1947
+ var KILL_GRACE_MS = 5e3;
1948
+ var TaskCancelledError = class extends Error {
1949
+ constructor(message) {
1950
+ super(message);
1951
+ this.name = "TaskCancelledError";
1952
+ }
1953
+ };
1954
+ function safeConfig(config) {
1955
+ if (config === void 0 || config === null) return config;
1956
+ try {
1957
+ structuredClone(config);
1958
+ return config;
1959
+ } catch {
1960
+ }
1961
+ try {
1962
+ return JSON.parse(JSON.stringify(config));
1963
+ } catch {
1964
+ return void 0;
1965
+ }
1966
+ }
1967
+ function buildWrapperSource(moduleUrl) {
1968
+ return `
1969
+ import { parentPort } from 'node:worker_threads';
1970
+ import * as mod from '${moduleUrl}';
1971
+
1972
+ const run = mod.run;
1973
+ if (typeof run !== 'function') {
1974
+ parentPort.postMessage({ type: 'error', message: 'Task module has no run export' });
1975
+ } else {
1976
+ let controller = null;
1977
+ parentPort.on('message', async (msg) => {
1978
+ if (msg?.type === 'abort') {
1979
+ controller?.abort(new Error(msg.reason));
1980
+ return;
1981
+ }
1982
+ if (msg?.type !== 'run') return;
1983
+ controller = new AbortController();
1984
+ const { payload, taskCtx } = msg;
1985
+ try {
1986
+ const result = await run(payload, { ...taskCtx, signal: controller.signal });
1987
+ parentPort.postMessage({ type: 'done', result });
1988
+ } catch (err) {
1989
+ parentPort.postMessage({
1990
+ type: 'error',
1991
+ message: err instanceof Error ? err.message : String(err),
1992
+ });
1993
+ }
1994
+ });
1995
+ }
1996
+ `;
1997
+ }
1998
+ async function runTaskInWorker(options) {
1999
+ const { taskModulePath, payload, taskCtx, timeoutMs, externalSignal } = options;
2000
+ const killGraceMs = options.killGraceMs ?? KILL_GRACE_MS;
2001
+ const moduleUrl = pathToFileURL(taskModulePath).href;
2002
+ const wrapperUrl = new URL(
2003
+ `data:text/javascript,${encodeURIComponent(buildWrapperSource(moduleUrl))}`
2004
+ );
2005
+ return new Promise((resolve, reject) => {
2006
+ let worker;
2007
+ try {
2008
+ worker = new Worker(wrapperUrl);
2009
+ } catch (err) {
2010
+ reject(err);
2011
+ return;
2012
+ }
2013
+ let phase = "running";
2014
+ let cancelReason = "";
2015
+ let graceTimer;
2016
+ const finish = (settle) => {
2017
+ if (phase === "settled") return;
2018
+ phase = "settled";
2019
+ clearTimeout(timeoutTimer);
2020
+ if (graceTimer) clearTimeout(graceTimer);
2021
+ externalSignal?.removeEventListener("abort", onExternalAbort);
2022
+ void worker.terminate();
2023
+ settle();
2024
+ };
2025
+ const startCancel = (reason) => {
2026
+ if (phase !== "running") return;
2027
+ phase = "grace";
2028
+ cancelReason = reason;
2029
+ worker.postMessage({ type: "abort", reason });
2030
+ graceTimer = setTimeout(() => {
2031
+ finish(
2032
+ () => reject(new TaskCancelledError(`Task "${taskCtx.job.name}" ${reason} and was terminated`))
2033
+ );
2034
+ }, killGraceMs);
2035
+ };
2036
+ const onExternalAbort = () => startCancel("cancelled (driver stop timeout)");
2037
+ if (externalSignal) {
2038
+ if (externalSignal.aborted) {
2039
+ onExternalAbort();
2040
+ } else {
2041
+ externalSignal.addEventListener("abort", onExternalAbort, { once: true });
2042
+ }
2043
+ }
2044
+ const timeoutTimer = setTimeout(
2045
+ () => startCancel(`timed out after ${timeoutMs}ms`),
2046
+ timeoutMs
2047
+ );
2048
+ worker.on("message", (msg) => {
2049
+ if (phase === "grace") {
2050
+ if (msg?.type === "done") {
2051
+ finish(
2052
+ () => reject(
2053
+ new TaskCancelledError(
2054
+ `Task "${taskCtx.job.name}" ${cancelReason} (task completed after the timeout)`
2055
+ )
2056
+ )
2057
+ );
2058
+ } else if (msg?.type === "error") {
2059
+ finish(() => reject(new TaskCancelledError(msg.message ?? cancelReason)));
2060
+ }
2061
+ return;
2062
+ }
2063
+ if (phase !== "running") return;
2064
+ if (msg?.type === "done") {
2065
+ finish(() => resolve(msg.result));
2066
+ } else if (msg?.type === "error") {
2067
+ finish(() => reject(new Error(msg.message ?? "task worker error")));
2068
+ }
2069
+ });
2070
+ worker.on("error", (err) => {
2071
+ finish(() => reject(err));
2072
+ });
2073
+ worker.on("exit", (code) => {
2074
+ if (phase === "grace") {
2075
+ finish(() => reject(new TaskCancelledError(`Task "${taskCtx.job.name}" ${cancelReason}`)));
2076
+ } else if (phase === "running") {
2077
+ finish(() => reject(new Error(`Task worker exited unexpectedly (code ${code})`)));
2078
+ }
2079
+ });
2080
+ try {
2081
+ worker.postMessage({
2082
+ type: "run",
2083
+ payload,
2084
+ taskCtx: { config: safeConfig(taskCtx.config), job: taskCtx.job }
2085
+ });
2086
+ } catch (err) {
2087
+ finish(
2088
+ () => reject(
2089
+ new Error(`Task "${taskCtx.job.name}" payload/config is not cloneable: ${String(err)}`)
2090
+ )
2091
+ );
2092
+ }
2093
+ });
2094
+ }
2095
+
2096
+ // src/task/taskQueue.ts
2097
+ function createTaskQueue(deps) {
2098
+ const { registry, rootDir, driver } = deps;
2099
+ const records = /* @__PURE__ */ new Map();
2100
+ const moduleCache2 = /* @__PURE__ */ new Map();
2101
+ const schemaCache = /* @__PURE__ */ new Map();
2102
+ let started = false;
2103
+ let stopped = false;
2104
+ const loadTaskModule = deps.loadTaskModule ?? (async (filePath) => await import(filePath));
2105
+ const loadPayloadSchema = deps.loadPayloadSchema ?? (async (filePath) => {
2106
+ const zodPath = path3.join(path3.dirname(filePath), "zod.js");
2107
+ try {
2108
+ const mod = await import(zodPath);
2109
+ const schemaKey = Object.keys(mod).find((k) => k.endsWith("Schema"));
2110
+ return schemaKey ? mod[schemaKey] : void 0;
2111
+ } catch {
2112
+ return void 0;
2113
+ }
2114
+ });
2115
+ function assertKnownTask(name) {
2116
+ const meta = registry.get(name);
2117
+ if (!meta) {
2118
+ const known = registry.list().map((t) => t.name).join(", ") || "(none)";
2119
+ throw new Error(`[faapi] Unknown task "${name}". Registered tasks: ${known}`);
2120
+ }
2121
+ }
2122
+ async function validatePayload(name, payload) {
2123
+ if (!schemaCache.has(name)) {
2124
+ const meta = registry.get(name);
2125
+ schemaCache.set(
2126
+ name,
2127
+ await loadPayloadSchema(path3.resolve(rootDir, meta.filePath)).catch(() => void 0)
2128
+ );
2129
+ }
2130
+ const schema = schemaCache.get(name);
2131
+ if (!schema || typeof schema.safeParse !== "function") {
2132
+ return payload;
2133
+ }
2134
+ const result = schema.safeParse(payload);
2135
+ if (!result.success) {
2136
+ throw new ValidationError(
2137
+ `Task payload validation failed for "${name}": ${JSON.stringify(result.error)}`,
2138
+ []
2139
+ );
2140
+ }
2141
+ return result.data;
2142
+ }
2143
+ async function runJob(job) {
2144
+ const meta = registry.get(job.name);
2145
+ if (!meta) throw new Error(`[faapi] Unknown task "${job.name}" at worker dispatch`);
2146
+ const record = records.get(job.id) ?? {
2147
+ id: job.id,
2148
+ name: job.name,
2149
+ payload: job.payload,
2150
+ status: "pending",
2151
+ attempts: 0,
2152
+ createdAt: Date.now()
2153
+ };
2154
+ record.attempts = job.attempt;
2155
+ record.status = "running";
2156
+ record.error = void 0;
2157
+ records.set(job.id, record);
2158
+ try {
2159
+ let result;
2160
+ if (meta.timeoutMs !== void 0 && meta.timeoutMs > 0) {
2161
+ result = await (deps.runIsolated ?? runTaskInWorker)({
2162
+ taskModulePath: path3.resolve(rootDir, meta.filePath),
2163
+ payload: job.payload,
2164
+ taskCtx: {
2165
+ config: deps.config,
2166
+ job: { id: job.id, name: job.name, attempt: job.attempt }
2167
+ },
2168
+ timeoutMs: meta.timeoutMs,
2169
+ externalSignal: job.signal
2170
+ });
2171
+ } else {
2172
+ let mod = moduleCache2.get(job.name);
2173
+ if (!mod) {
2174
+ mod = await loadTaskModule(path3.resolve(rootDir, meta.filePath));
2175
+ moduleCache2.set(job.name, mod);
2176
+ }
2177
+ if (typeof mod.run !== "function") {
2178
+ throw new Error(`Task "${job.name}" module has no run export`);
2179
+ }
2180
+ const taskCtx = {
2181
+ signal: job.signal,
2182
+ config: deps.config,
2183
+ job: { id: job.id, name: job.name, attempt: job.attempt }
2184
+ };
2185
+ result = await mod.run(job.payload, taskCtx);
2186
+ }
2187
+ record.status = "done";
2188
+ record.result = result;
2189
+ return result;
2190
+ } catch (err) {
2191
+ const cancelled = err instanceof TaskCancelledError || job.signal.aborted;
2192
+ record.status = cancelled ? "cancelled" : "failed";
2193
+ record.error = err instanceof Error ? err.message : String(err);
2194
+ if (deps.onFailed) {
2195
+ void Promise.resolve().then(
2196
+ () => deps.onFailed({
2197
+ task: job.name,
2198
+ jobId: job.id,
2199
+ attempt: job.attempt,
2200
+ willRetry: job.attempt <= (meta.retries ?? 0),
2201
+ cancelled,
2202
+ error: record.error
2203
+ })
2204
+ ).catch(() => {
2205
+ });
2206
+ }
2207
+ throw err;
2208
+ }
2209
+ }
2210
+ async function startWorkers() {
2211
+ for (const meta of registry.list()) {
2212
+ await driver.startWorker(meta.name, {
2213
+ concurrency: meta.concurrency ?? 1,
2214
+ process: runJob
2215
+ });
2216
+ }
2217
+ }
2218
+ const queue = {
2219
+ async enqueue(name, payload = {}, opts) {
2220
+ if (stopped) {
2221
+ throw new Error("[faapi] Task queue is stopped and no longer accepts jobs");
2222
+ }
2223
+ assertKnownTask(name);
2224
+ const data = await validatePayload(name, payload);
2225
+ const meta = registry.get(name);
2226
+ const id = await driver.enqueue(name, data, {
2227
+ delayMs: opts?.delayMs,
2228
+ retries: meta.retries ?? 0,
2229
+ dedupId: opts?.dedupId
2230
+ });
2231
+ if (!records.has(id)) {
2232
+ records.set(id, {
2233
+ id,
2234
+ name,
2235
+ payload: data,
2236
+ status: "pending",
2237
+ attempts: 0,
2238
+ createdAt: Date.now(),
2239
+ ...opts?.delayMs ? { runAt: Date.now() + opts.delayMs } : {}
2240
+ });
2241
+ }
2242
+ return { id };
2243
+ },
2244
+ list(name) {
2245
+ const snapshot = [];
2246
+ for (const job of records.values()) {
2247
+ if (name !== void 0 && job.name !== name) continue;
2248
+ snapshot.push({ ...job });
2249
+ }
2250
+ return snapshot;
2251
+ },
2252
+ async listQueued(name) {
2253
+ if (!driver.list) {
2254
+ throw new Error(
2255
+ "[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."
2256
+ );
2257
+ }
2258
+ const queued = await driver.list({ name, limit: 50 });
2259
+ const merged = /* @__PURE__ */ new Map();
2260
+ for (const record of queued) {
2261
+ if (name !== void 0 && record.name !== name) continue;
2262
+ merged.set(record.id, {
2263
+ id: record.id,
2264
+ name: record.name,
2265
+ payload: record.payload,
2266
+ status: record.status,
2267
+ attempts: record.attempts,
2268
+ createdAt: record.createdAt,
2269
+ ...record.result !== void 0 ? { result: record.result } : {},
2270
+ ...record.error !== void 0 ? { error: record.error } : {},
2271
+ ...record.runAt !== void 0 ? { runAt: record.runAt } : {}
2272
+ });
2273
+ }
2274
+ for (const local of records.values()) {
2275
+ if (name !== void 0 && local.name !== name) continue;
2276
+ merged.set(local.id, { ...local });
2277
+ }
2278
+ return [...merged.values()];
2279
+ },
2280
+ async cancel(name, id) {
2281
+ if (!driver.cancel) {
2282
+ throw new Error(
2283
+ "[faapi] Task driver does not support cancelling tasks (TaskDriver.cancel is not implemented)."
2284
+ );
2285
+ }
2286
+ await driver.cancel(name, id);
2287
+ const record = records.get(id);
2288
+ if (record) record.status = "cancelled";
2289
+ },
2290
+ async retry(name, id) {
2291
+ if (!driver.retry) {
2292
+ throw new Error(
2293
+ "[faapi] Task driver does not support retrying tasks (TaskDriver.retry is not implemented)."
2294
+ );
2295
+ }
2296
+ await driver.retry(name, id);
2297
+ const record = records.get(id);
2298
+ if (record) record.status = "pending";
2299
+ },
2300
+ async start() {
2301
+ if (stopped) {
2302
+ throw new Error("[faapi] Task queue is stopped and cannot be restarted");
2303
+ }
2304
+ if (started) return;
2305
+ started = true;
2306
+ await startWorkers();
2307
+ },
2308
+ async stop(timeoutMs = 1e4) {
2309
+ stopped = true;
2310
+ await driver.stop(timeoutMs);
2311
+ },
2312
+ async reload() {
2313
+ await driver.stopWorkers?.();
2314
+ await startWorkers();
2315
+ },
2316
+ invalidateModules() {
2317
+ moduleCache2.clear();
2318
+ },
2319
+ invalidateSchemas() {
2320
+ schemaCache.clear();
2321
+ }
2322
+ };
2323
+ return queue;
2324
+ }
2325
+
2326
+ // src/task/loadTaskDriver.ts
2327
+ async function loadTaskDriver(driver, driverOptions) {
2328
+ if (driver === void 0) {
2329
+ throw new Error(
2330
+ "[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)."
2331
+ );
2332
+ }
2333
+ if (driver === "memory") {
2334
+ throw new Error(
2335
+ "[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'."
2336
+ );
2337
+ }
2338
+ if (typeof driver === "object") {
2339
+ return driver;
2340
+ }
2341
+ if (driver !== "pgboss" && driver !== "bullmq") {
2342
+ throw new Error(
2343
+ `[faapi] Unknown task driver "${driver}". Supported: 'pgboss', 'bullmq', or a TaskDriver instance.`
2344
+ );
2345
+ }
2346
+ const specifier = `@faapi/task-${driver}`;
2347
+ let mod;
2348
+ try {
2349
+ mod = await import(
2350
+ /* @vite-ignore */
2351
+ specifier
2352
+ );
2353
+ } catch {
2354
+ throw new Error(
2355
+ `[faapi] Task driver "${driver}" requires package "${specifier}" to be installed in your project.`
2356
+ );
2357
+ }
2358
+ const factoryName = driver === "pgboss" ? "createPgBossDriver" : "createBullMQDriver";
2359
+ const factory = mod[factoryName];
2360
+ if (typeof factory !== "function") {
2361
+ throw new Error(
2362
+ `[faapi] Package "${specifier}" does not export ${factoryName}() \u2014 check the package version.`
2363
+ );
2364
+ }
2365
+ return factory(driverOptions);
2366
+ }
2367
+
2368
+ // src/task/cronScheduler.ts
2369
+ import { Cron } from "croner";
2370
+ function createCronScheduler(registry, enqueue) {
2371
+ let schedules = [];
2372
+ return {
2373
+ start() {
2374
+ this.stop();
2375
+ for (const task of registry.list()) {
2376
+ if (!task.cron) continue;
2377
+ const schedule = new Cron(task.cron, () => {
2378
+ const runAt = schedule.currentRun();
2379
+ void Promise.resolve().then(
2380
+ () => enqueue(task.name, {
2381
+ dedupId: runAt ? `cron:${task.name}:${runAt.toISOString()}` : void 0
2382
+ })
2383
+ ).catch((err) => {
2384
+ console.error(`[faapi] Cron enqueue failed for task "${task.name}":`, err);
2385
+ });
2386
+ });
2387
+ schedules.push(schedule);
2388
+ }
2389
+ },
2390
+ stop() {
2391
+ for (const schedule of schedules) {
2392
+ schedule.stop();
2393
+ }
2394
+ schedules = [];
2395
+ }
2396
+ };
2397
+ }
2398
+
2399
+ // src/task/scanTasks.ts
2400
+ import fg from "fast-glob";
2401
+ import path4 from "path";
2402
+ import fs2 from "fs";
2403
+ var TASK_PATTERNS = ["src/tasks/**/task.ts"];
2404
+ var TASK_FILENAME = "task.ts";
2405
+ var CRON_RE = /(?:^|\n)\s*cron:\s*['"`]([^'"`\n]+)['"`]/;
2406
+ var CONCURRENCY_RE = /(?:^|\n)\s*concurrency:\s*(\d+)/;
2407
+ var RETRIES_RE = /(?:^|\n)\s*retries:\s*(\d+)/;
2408
+ var TIMEOUT_MS_RE = /(?:^|\n)\s*timeoutMs:\s*(\d+)/;
2409
+ function filePathToTaskName(filePath) {
2410
+ const normalized = filePath.replace(/\\/g, "/");
2411
+ const match = normalized.match(/(?:^|\/)tasks\/(.+)\/task\.ts$/);
2412
+ if (!match) {
2413
+ throw new Error(`[faapi] Invalid task file path: ${filePath}`);
2414
+ }
2415
+ return match[1].split("/").join(".");
2416
+ }
2417
+ async function scanTasks(rootDir, patterns) {
2418
+ const files = await fg(patterns, {
2419
+ cwd: rootDir,
2420
+ onlyFiles: true,
2421
+ absolute: false
2422
+ });
2423
+ const tasks = [];
2424
+ const seen = /* @__PURE__ */ new Map();
2425
+ for (const file of files) {
2426
+ const normalizedFile = file.replace(/\\/g, "/");
2427
+ const fileName = path4.posix.basename(normalizedFile);
2428
+ if (fileName !== TASK_FILENAME) {
2429
+ continue;
2430
+ }
2431
+ const absPath = path4.resolve(rootDir, normalizedFile);
2432
+ const source = await fs2.promises.readFile(absPath, "utf8").catch(() => "");
2433
+ const name = filePathToTaskName(normalizedFile);
2434
+ const prevFile = seen.get(name);
2435
+ if (prevFile) {
2436
+ throw new Error(
2437
+ `Task conflict: "${name}" declared in both ${prevFile} and ${normalizedFile}`
2438
+ );
2439
+ }
2440
+ seen.set(name, normalizedFile);
2441
+ const manifest = { name, filePath: normalizedFile };
2442
+ const cron = CRON_RE.exec(source)?.[1];
2443
+ const concurrency = CONCURRENCY_RE.exec(source)?.[1];
2444
+ const retries = RETRIES_RE.exec(source)?.[1];
2445
+ const timeoutMs = TIMEOUT_MS_RE.exec(source)?.[1];
2446
+ if (cron !== void 0) manifest.cron = cron;
2447
+ if (concurrency !== void 0) manifest.concurrency = Number(concurrency);
2448
+ if (retries !== void 0) manifest.retries = Number(retries);
2449
+ if (timeoutMs !== void 0) manifest.timeoutMs = Number(timeoutMs);
2450
+ tasks.push(manifest);
2451
+ }
2452
+ return tasks;
2453
+ }
2454
+
1841
2455
  // src/injection/agentRegistry.ts
1842
2456
  function getAgent(name) {
1843
2457
  return defaultRegistries.agent.getAgent(name);
@@ -1878,7 +2492,7 @@ function listSkills() {
1878
2492
  }
1879
2493
 
1880
2494
  // src/loader/loadAgentModule.ts
1881
- import fs9 from "fs";
2495
+ import fs10 from "fs";
1882
2496
 
1883
2497
  // src/loader/resolveExports.ts
1884
2498
  function resolveExport(module, exportName) {
@@ -1896,7 +2510,7 @@ function resolveExport(module, exportName) {
1896
2510
  }
1897
2511
 
1898
2512
  // src/utils/importWithCacheBust.ts
1899
- import { pathToFileURL } from "url";
2513
+ import { pathToFileURL as pathToFileURL2 } from "url";
1900
2514
  var loadTs;
1901
2515
  function setLoadTimestamp(ts11) {
1902
2516
  loadTs = ts11;
@@ -1910,13 +2524,13 @@ async function importWithCacheBust(filePath, bustViteCache = false) {
1910
2524
  const importActual = getVitestImportActual();
1911
2525
  if (importActual) {
1912
2526
  if (bustViteCache) {
1913
- let url2 = pathToFileURL(filePath).href;
2527
+ let url2 = pathToFileURL2(filePath).href;
1914
2528
  url2 += `?t=${Date.now()}`;
1915
2529
  return await import(url2);
1916
2530
  }
1917
2531
  return await importActual(filePath);
1918
2532
  }
1919
- let url = pathToFileURL(filePath).href;
2533
+ let url = pathToFileURL2(filePath).href;
1920
2534
  if (loadTs !== void 0) {
1921
2535
  url += `?t=${loadTs}`;
1922
2536
  }
@@ -1924,18 +2538,18 @@ async function importWithCacheBust(filePath, bustViteCache = false) {
1924
2538
  }
1925
2539
 
1926
2540
  // src/cli/compileOnDemand.ts
1927
- import path10 from "path";
1928
- import fs8 from "fs";
2541
+ import path12 from "path";
2542
+ import fs9 from "fs";
1929
2543
 
1930
2544
  // src/cli/compileSourceFiles.ts
1931
- import path6 from "path";
1932
- import fs5 from "fs";
1933
- import fg from "fast-glob";
2545
+ import path8 from "path";
2546
+ import fs6 from "fs";
2547
+ import fg2 from "fast-glob";
1934
2548
 
1935
2549
  // src/cli/aliasPlugin.ts
1936
2550
  import ts7 from "typescript";
1937
- import path5 from "path";
1938
- import fs4 from "fs";
2551
+ import path7 from "path";
2552
+ import fs5 from "fs";
1939
2553
 
1940
2554
  // src/utils/resolveAlias.ts
1941
2555
  function resolveAlias(specifier, config) {
@@ -1961,8 +2575,8 @@ function resolveAlias(specifier, config) {
1961
2575
  }
1962
2576
 
1963
2577
  // src/utils/prodPaths.ts
1964
- import path3 from "path";
1965
- import fs2 from "fs";
2578
+ import path5 from "path";
2579
+ import fs3 from "fs";
1966
2580
  var APP_DIR = "src";
1967
2581
  var ROUTE_PATTERNS = ["src/api/**/*.ts"];
1968
2582
  function toProdFilePath(filePath, dist) {
@@ -1981,27 +2595,27 @@ function toProdExtension(filePath) {
1981
2595
  }
1982
2596
  function toRealPath(p) {
1983
2597
  try {
1984
- return fs2.realpathSync(p);
2598
+ return fs3.realpathSync(p);
1985
2599
  } catch {
1986
2600
  return p;
1987
2601
  }
1988
2602
  }
1989
2603
  function isInsideDir(filePath, dir) {
1990
- const rel = path3.relative(dir, filePath);
1991
- return rel !== "" && !rel.startsWith("..") && !path3.isAbsolute(rel);
2604
+ const rel = path5.relative(dir, filePath);
2605
+ return rel !== "" && !rel.startsWith("..") && !path5.isAbsolute(rel);
1992
2606
  }
1993
2607
 
1994
2608
  // src/utils/readTsconfig.ts
1995
2609
  import ts6 from "typescript";
1996
- import path4 from "path";
1997
- import fs3 from "fs";
2610
+ import path6 from "path";
2611
+ import fs4 from "fs";
1998
2612
  var tsconfigCache = /* @__PURE__ */ new Map();
1999
2613
  function readTsconfig(rootDir) {
2000
- const tsconfigPath = path4.resolve(rootDir, "tsconfig.json");
2001
- if (!fs3.existsSync(tsconfigPath)) return null;
2614
+ const tsconfigPath = path6.resolve(rootDir, "tsconfig.json");
2615
+ if (!fs4.existsSync(tsconfigPath)) return null;
2002
2616
  let mtimeMs;
2003
2617
  try {
2004
- mtimeMs = fs3.statSync(tsconfigPath).mtimeMs;
2618
+ mtimeMs = fs4.statSync(tsconfigPath).mtimeMs;
2005
2619
  } catch {
2006
2620
  return null;
2007
2621
  }
@@ -2017,7 +2631,7 @@ function readTsconfig(rootDir) {
2017
2631
  const config = rawPaths ? (() => {
2018
2632
  const paths = {};
2019
2633
  for (const [pattern, targets] of Object.entries(rawPaths)) {
2020
- paths[pattern] = targets.map((t) => path4.resolve(baseUrl, t));
2634
+ paths[pattern] = targets.map((t) => path6.resolve(baseUrl, t));
2021
2635
  }
2022
2636
  return { baseUrl, paths };
2023
2637
  })() : null;
@@ -2027,26 +2641,26 @@ function readTsconfig(rootDir) {
2027
2641
 
2028
2642
  // src/cli/aliasPlugin.ts
2029
2643
  function toProdImportPath(sourceFile, importer) {
2030
- const importerDir = path5.dirname(importer);
2031
- let rel = path5.relative(importerDir, sourceFile);
2032
- rel = rel.split(path5.sep).join("/");
2644
+ const importerDir = path7.dirname(importer);
2645
+ let rel = path7.relative(importerDir, sourceFile);
2646
+ rel = rel.split(path7.sep).join("/");
2033
2647
  if (!rel.startsWith(".")) rel = "./" + rel;
2034
2648
  return toProdExtension(rel);
2035
2649
  }
2036
2650
  function toStrippedProdImportPath(sourceFile, rootDir) {
2037
- const appDirAbs = toRealPath(path5.resolve(rootDir, APP_DIR));
2651
+ const appDirAbs = toRealPath(path7.resolve(rootDir, APP_DIR));
2038
2652
  const sourceReal = toRealPath(sourceFile);
2039
- let rel = path5.relative(appDirAbs, sourceReal);
2040
- rel = rel.split(path5.sep).join("/");
2653
+ let rel = path7.relative(appDirAbs, sourceReal);
2654
+ rel = rel.split(path7.sep).join("/");
2041
2655
  if (!rel.startsWith(".")) rel = "./" + rel;
2042
2656
  return toProdExtension(rel);
2043
2657
  }
2044
2658
  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));
2659
+ const importerRel = path7.relative(toRealPath(path7.resolve(rootDir)), toRealPath(importer)).split(path7.sep).join("/");
2660
+ const importerProdDir = path7.posix.dirname(toProdExtension(importerRel));
2047
2661
  if (importerProdDir === ".") return relFromDist;
2048
2662
  const target = relFromDist.replace(/^\.\//, "");
2049
- let rel = path5.posix.relative(importerProdDir, target);
2663
+ let rel = path7.posix.relative(importerProdDir, target);
2050
2664
  if (!rel.startsWith(".")) rel = "./" + rel;
2051
2665
  return rel;
2052
2666
  }
@@ -2061,41 +2675,41 @@ var INDEX_EXTS = [
2061
2675
  "/index.cjs"
2062
2676
  ];
2063
2677
  function resolveRelativeSpecifier(importer, specifier) {
2064
- const importerDir = path5.dirname(importer);
2065
- const base = path5.resolve(importerDir, specifier);
2678
+ const importerDir = path7.dirname(importer);
2679
+ const base = path7.resolve(importerDir, specifier);
2066
2680
  if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
2067
- if (fs4.existsSync(base)) return base;
2681
+ if (fs5.existsSync(base)) return base;
2068
2682
  if (base.endsWith(".js")) {
2069
2683
  for (const ext of [".ts", ".tsx", ".jsx"]) {
2070
2684
  const file = base.slice(0, -3) + ext;
2071
- if (fs4.existsSync(file)) return file;
2685
+ if (fs5.existsSync(file)) return file;
2072
2686
  }
2073
2687
  }
2074
2688
  return null;
2075
2689
  }
2076
2690
  if (/\.(ts|tsx|jsx)$/.test(specifier)) {
2077
- return fs4.existsSync(base) ? base : null;
2691
+ return fs5.existsSync(base) ? base : null;
2078
2692
  }
2079
2693
  for (const ext of SOURCE_EXTS) {
2080
2694
  const file = base + ext;
2081
- if (fs4.existsSync(file)) return file;
2695
+ if (fs5.existsSync(file)) return file;
2082
2696
  }
2083
2697
  for (const indexExt of INDEX_EXTS) {
2084
2698
  const file = base + indexExt;
2085
- if (fs4.existsSync(file)) return file;
2699
+ if (fs5.existsSync(file)) return file;
2086
2700
  }
2087
2701
  return null;
2088
2702
  }
2089
2703
  function createAliasPlugin(config, options) {
2090
- const appDirAbs = options?.rootDir ? toRealPath(path5.resolve(options.rootDir, APP_DIR)) : null;
2704
+ const appDirAbs = options?.rootDir ? toRealPath(path7.resolve(options.rootDir, APP_DIR)) : null;
2091
2705
  const probeCandidateFile = (candidate) => {
2092
2706
  for (const ext of SOURCE_EXTS) {
2093
2707
  const file = candidate + ext;
2094
- if (fs4.existsSync(file)) return file;
2708
+ if (fs5.existsSync(file)) return file;
2095
2709
  }
2096
2710
  for (const indexExt of INDEX_EXTS) {
2097
2711
  const file = candidate + indexExt;
2098
- if (fs4.existsSync(file)) return file;
2712
+ if (fs5.existsSync(file)) return file;
2099
2713
  }
2100
2714
  return null;
2101
2715
  };
@@ -2105,7 +2719,7 @@ function createAliasPlugin(config, options) {
2105
2719
  build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
2106
2720
  let source;
2107
2721
  try {
2108
- source = fs4.readFileSync(args.path, "utf8");
2722
+ source = fs5.readFileSync(args.path, "utf8");
2109
2723
  } catch {
2110
2724
  return void 0;
2111
2725
  }
@@ -2221,7 +2835,7 @@ function buildAliasPlugins(rootDir) {
2221
2835
  // src/cli/compileSourceFiles.ts
2222
2836
  async function compileSourceFiles(options) {
2223
2837
  const { rootDir, dist, files, logLevel = "silent", production, atomicWrite } = options;
2224
- const entryPoints = files ?? await fg([`${APP_DIR}/**/*.ts`], {
2838
+ const entryPoints = files ?? await fg2([`${APP_DIR}/**/*.ts`], {
2225
2839
  cwd: rootDir,
2226
2840
  onlyFiles: true,
2227
2841
  absolute: true,
@@ -2230,11 +2844,11 @@ async function compileSourceFiles(options) {
2230
2844
  if (entryPoints.length === 0) {
2231
2845
  return { compiledFiles: [] };
2232
2846
  }
2233
- const absDist = path6.resolve(rootDir, dist);
2234
- await fs5.promises.mkdir(absDist, { recursive: true });
2847
+ const absDist = path8.resolve(rootDir, dist);
2848
+ await fs6.promises.mkdir(absDist, { recursive: true });
2235
2849
  const plugins = buildAliasPlugins(rootDir);
2236
2850
  const esbuild = await import("esbuild");
2237
- const outbase = path6.resolve(rootDir, APP_DIR);
2851
+ const outbase = path8.resolve(rootDir, APP_DIR);
2238
2852
  const result = await esbuild.build({
2239
2853
  entryPoints,
2240
2854
  outdir: absDist,
@@ -2254,10 +2868,10 @@ async function compileSourceFiles(options) {
2254
2868
  if (atomicWrite && result.outputFiles) {
2255
2869
  await Promise.all(
2256
2870
  result.outputFiles.map(async (file) => {
2257
- await fs5.promises.mkdir(path6.dirname(file.path), { recursive: true });
2871
+ await fs6.promises.mkdir(path8.dirname(file.path), { recursive: true });
2258
2872
  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);
2873
+ await fs6.promises.writeFile(tmp, file.contents);
2874
+ await fs6.promises.rename(tmp, file.path);
2261
2875
  })
2262
2876
  );
2263
2877
  }
@@ -2274,8 +2888,8 @@ init_generateSchemaFiles();
2274
2888
  init_generateSchemaFiles();
2275
2889
 
2276
2890
  // src/cli/collectImports.ts
2277
- import path9 from "path";
2278
- import fs7 from "fs";
2891
+ import path11 from "path";
2892
+ import fs8 from "fs";
2279
2893
  var SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
2280
2894
  function extractImportSpecifiers(source) {
2281
2895
  const specifiers = [];
@@ -2287,10 +2901,10 @@ function extractImportSpecifiers(source) {
2287
2901
  return specifiers;
2288
2902
  }
2289
2903
  function resolveSpecifierFromDir(dir, specifier) {
2290
- return resolveRelativeSpecifier(path9.join(dir, "__faapi_probe__.ts"), specifier);
2904
+ return resolveRelativeSpecifier(path11.join(dir, "__faapi_probe__.ts"), specifier);
2291
2905
  }
2292
2906
  async function collectRelativeImports(entryFiles, rootDir) {
2293
- const appDirAbs = toRealPath(path9.resolve(rootDir, "src"));
2907
+ const appDirAbs = toRealPath(path11.resolve(rootDir, "src"));
2294
2908
  const tsconfig = readTsconfig(rootDir);
2295
2909
  const visited = /* @__PURE__ */ new Set();
2296
2910
  const insideFiles = /* @__PURE__ */ new Set();
@@ -2300,7 +2914,7 @@ async function collectRelativeImports(entryFiles, rootDir) {
2300
2914
  visited.add(filePath);
2301
2915
  let source;
2302
2916
  try {
2303
- source = await fs7.promises.readFile(filePath, "utf8");
2917
+ source = await fs8.promises.readFile(filePath, "utf8");
2304
2918
  } catch {
2305
2919
  return;
2306
2920
  }
@@ -2322,7 +2936,7 @@ async function collectRelativeImports(entryFiles, rootDir) {
2322
2936
  resolved = null;
2323
2937
  }
2324
2938
  if (!resolved) continue;
2325
- if (!isInsideDir(toRealPath(resolved), toRealPath(path9.resolve(rootDir)))) continue;
2939
+ if (!isInsideDir(toRealPath(resolved), toRealPath(path11.resolve(rootDir)))) continue;
2326
2940
  if (isInsideDir(toRealPath(resolved), appDirAbs)) {
2327
2941
  insideFiles.add(resolved);
2328
2942
  } else {
@@ -2343,8 +2957,8 @@ async function collectRelativeImports(entryFiles, rootDir) {
2343
2957
  // src/cli/compileOnDemand.ts
2344
2958
  function isProductFresh(sourceAbsPath, productAbsPath) {
2345
2959
  try {
2346
- const srcStat = fs8.statSync(sourceAbsPath);
2347
- const prodStat = fs8.statSync(productAbsPath);
2960
+ const srcStat = fs9.statSync(sourceAbsPath);
2961
+ const prodStat = fs9.statSync(productAbsPath);
2348
2962
  return prodStat.mtimeMs >= srcStat.mtimeMs;
2349
2963
  } catch {
2350
2964
  return false;
@@ -2376,7 +2990,7 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
2376
2990
  if (state.compiledFiles.has(sourceAbsPath)) {
2377
2991
  return false;
2378
2992
  }
2379
- if (!fs8.existsSync(sourceAbsPath)) {
2993
+ if (!fs9.existsSync(sourceAbsPath)) {
2380
2994
  return false;
2381
2995
  }
2382
2996
  const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
@@ -2426,11 +3040,11 @@ async function ensureMiddlewaresCompiled(middlewarePaths, rootDir) {
2426
3040
  }
2427
3041
  }
2428
3042
  function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
2429
- const rel = path10.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
3043
+ const rel = path12.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
2430
3044
  if (!rel.startsWith("src/")) return null;
2431
3045
  const relWithoutSrc = rel.slice(4);
2432
3046
  const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
2433
- return path10.resolve(rootDir, dist, jsRel);
3047
+ return path12.resolve(rootDir, dist, jsRel);
2434
3048
  }
2435
3049
  function clearGeneratedSchemas() {
2436
3050
  state.generatedSchemas.clear();
@@ -2446,9 +3060,9 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
2446
3060
  if (state.generatedSchemas.has(schemaPath)) {
2447
3061
  return false;
2448
3062
  }
2449
- const prodAbsPath = path10.resolve(rootDir, routeFilePath);
3063
+ const prodAbsPath = path12.resolve(rootDir, routeFilePath);
2450
3064
  const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
2451
- if (!fs8.existsSync(sourceAbsPath)) {
3065
+ if (!fs9.existsSync(sourceAbsPath)) {
2452
3066
  return false;
2453
3067
  }
2454
3068
  if (isProductFresh(sourceAbsPath, schemaPath)) {
@@ -2459,7 +3073,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
2459
3073
  if (fileRoutes.length === 0) {
2460
3074
  return false;
2461
3075
  }
2462
- const sourceRelPath = path10.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
3076
+ const sourceRelPath = path12.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
2463
3077
  const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
2464
3078
  const generatePromise = (async () => {
2465
3079
  await generateSchemaFiles(sourceRoutes, rootDir, dist);
@@ -2480,7 +3094,7 @@ async function deleteSchemaFiles(routes, rootDir, dist) {
2480
3094
  if (deleted.has(schemaPath)) continue;
2481
3095
  deleted.add(schemaPath);
2482
3096
  try {
2483
- await fs8.promises.unlink(schemaPath);
3097
+ await fs9.promises.unlink(schemaPath);
2484
3098
  } catch {
2485
3099
  }
2486
3100
  }
@@ -2489,19 +3103,19 @@ var sourcePathCache = /* @__PURE__ */ new Map();
2489
3103
  function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
2490
3104
  const cached = sourcePathCache.get(prodAbsPath);
2491
3105
  if (cached) return cached;
2492
- const rel = path10.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
3106
+ const rel = path12.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
2493
3107
  let relWithoutDist = rel;
2494
3108
  if (relWithoutDist.startsWith(`${dist}/`)) {
2495
3109
  relWithoutDist = relWithoutDist.slice(dist.length + 1);
2496
3110
  }
2497
3111
  const srcRel = `src/${relWithoutDist}`;
2498
3112
  const tsRel = srcRel.replace(/\.js$/, ".ts");
2499
- const tsAbs = path10.resolve(rootDir, tsRel);
3113
+ const tsAbs = path12.resolve(rootDir, tsRel);
2500
3114
  let result;
2501
- if (fs8.existsSync(tsAbs)) {
3115
+ if (fs9.existsSync(tsAbs)) {
2502
3116
  result = tsAbs;
2503
3117
  } else {
2504
- result = path10.resolve(rootDir, srcRel);
3118
+ result = path12.resolve(rootDir, srcRel);
2505
3119
  }
2506
3120
  sourcePathCache.set(prodAbsPath, result);
2507
3121
  return result;
@@ -2519,7 +3133,7 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
2519
3133
  const dist = getDevDist();
2520
3134
  if (dist) {
2521
3135
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
2522
- if (sourcePath && fs9.existsSync(sourcePath)) {
3136
+ if (sourcePath && fs10.existsSync(sourcePath)) {
2523
3137
  try {
2524
3138
  await ensureCompiled(sourcePath, rootDir, dist);
2525
3139
  } catch (compileErr) {
@@ -2552,13 +3166,13 @@ async function loadAgentModule(filePath, hasRun, rootDir) {
2552
3166
  }
2553
3167
 
2554
3168
  // src/loader/loadToolModule.ts
2555
- import fs10 from "fs";
3169
+ import fs11 from "fs";
2556
3170
  async function loadToolModule(filePath, functionName, rootDir) {
2557
3171
  if (isDevOnDemandEnabled() && rootDir) {
2558
3172
  const dist = getDevDist();
2559
3173
  if (dist) {
2560
3174
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
2561
- if (sourcePath && fs10.existsSync(sourcePath)) {
3175
+ if (sourcePath && fs11.existsSync(sourcePath)) {
2562
3176
  try {
2563
3177
  await ensureCompiled(sourcePath, rootDir, dist);
2564
3178
  } catch (compileErr) {
@@ -2590,7 +3204,7 @@ async function loadToolModule(filePath, functionName, rootDir) {
2590
3204
  import { existsSync as existsSync2 } from "fs";
2591
3205
 
2592
3206
  // src/cli/generateToolArtifacts.ts
2593
- import path11 from "path";
3207
+ import path13 from "path";
2594
3208
  import { existsSync } from "fs";
2595
3209
 
2596
3210
  // src/ast/extractToolMetadata.ts
@@ -2714,7 +3328,7 @@ function collectToolSchemaSources(tools, rootDir) {
2714
3328
  const toolsByFile = /* @__PURE__ */ new Map();
2715
3329
  for (const tool of tools) {
2716
3330
  if (!tool.inputTypeName) continue;
2717
- const absPath = path11.resolve(rootDir, tool.filePath);
3331
+ const absPath = path13.resolve(rootDir, tool.filePath);
2718
3332
  let list = toolsByFile.get(absPath);
2719
3333
  if (!list) {
2720
3334
  list = [];
@@ -2774,15 +3388,15 @@ function generateToolSchemaFileSource(sources, resolveType, helpersImportPath) {
2774
3388
  }
2775
3389
  async function maybeGenerateHelpers(allSourceCode, distDir) {
2776
3390
  if (!usesCoerceHelpers(allSourceCode)) return;
2777
- const helpersPath = path11.resolve(distDir, HELPERS_FILENAME);
3391
+ const helpersPath = path13.resolve(distDir, HELPERS_FILENAME);
2778
3392
  if (existsSync(helpersPath)) return;
2779
3393
  await atomicWriteFile(helpersPath, generateHelpersFileSource());
2780
3394
  }
2781
3395
  async function generateToolArtifacts(tools, rootDir, dist, options) {
2782
3396
  const metadata = [];
2783
- const programByFile = createPrograms(tools.map((m) => path11.resolve(rootDir, m.filePath)));
3397
+ const programByFile = createPrograms(tools.map((m) => path13.resolve(rootDir, m.filePath)));
2784
3398
  for (const manifest of tools) {
2785
- const absPath = path11.resolve(rootDir, manifest.filePath);
3399
+ const absPath = path13.resolve(rootDir, manifest.filePath);
2786
3400
  const program = programByFile.get(absPath);
2787
3401
  const result = extractToolMetadata(program, absPath, manifest.functionName, {
2788
3402
  name: manifest.name,
@@ -2793,7 +3407,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
2793
3407
  }
2794
3408
  }
2795
3409
  const serialized = serializeTools(metadata, dist);
2796
- const toolsPath = path11.resolve(rootDir, dist, TOOLS_FILE);
3410
+ const toolsPath = path13.resolve(rootDir, dist, TOOLS_FILE);
2797
3411
  await writeToolsModule(serialized, toolsPath);
2798
3412
  if (options?.skipSchema) {
2799
3413
  return metadata;
@@ -2816,7 +3430,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
2816
3430
  }
2817
3431
  const fileEntries = [];
2818
3432
  for (const [filePath, fileSources] of sourcesByFile) {
2819
- const relFile = path11.relative(rootDir, filePath).replace(/\\/g, "/");
3433
+ const relFile = path13.relative(rootDir, filePath).replace(/\\/g, "/");
2820
3434
  const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
2821
3435
  const resolver = resolversByFile.get(filePath);
2822
3436
  let relForDir = relFile;
@@ -2834,7 +3448,7 @@ async function generateToolArtifacts(tools, rootDir, dist, options) {
2834
3448
  fileEntries.push({ outputPath, source });
2835
3449
  }
2836
3450
  const allSourceCode = fileEntries.map((e) => e.source).join("\n");
2837
- const distDir = path11.resolve(rootDir, dist);
3451
+ const distDir = path13.resolve(rootDir, dist);
2838
3452
  await maybeGenerateHelpers(allSourceCode, distDir);
2839
3453
  await Promise.all(
2840
3454
  fileEntries.map(({ outputPath, source }) => writeToolSchemaFile(outputPath, source))
@@ -3043,16 +3657,16 @@ function helmet(options = {}) {
3043
3657
  }
3044
3658
 
3045
3659
  // src/config/loadConfig.ts
3046
- import path12 from "path";
3047
- import fs11 from "fs";
3660
+ import path14 from "path";
3661
+ import fs12 from "fs";
3048
3662
  var CONFIG_PRODUCT_FILE = "faapi-config.js";
3049
3663
  async function loadConfig(rootDir, dist) {
3050
- const configProductPath = path12.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
3051
- if (fs11.existsSync(configProductPath)) {
3664
+ const configProductPath = path14.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
3665
+ if (fs12.existsSync(configProductPath)) {
3052
3666
  const module = await importWithCacheBust(configProductPath);
3053
3667
  return module.default ?? {};
3054
3668
  }
3055
- const hasSourceConfig = fs11.existsSync(path12.join(rootDir, "faapi.config.ts")) || fs11.existsSync(path12.join(rootDir, "faapi.config.js"));
3669
+ const hasSourceConfig = fs12.existsSync(path14.join(rootDir, "faapi.config.ts")) || fs12.existsSync(path14.join(rootDir, "faapi.config.js"));
3056
3670
  if (hasSourceConfig) {
3057
3671
  throw new Error(
3058
3672
  `[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 +3676,8 @@ async function loadConfig(rootDir, dist) {
3062
3676
  }
3063
3677
 
3064
3678
  // src/cli/loadEnv.ts
3065
- import fs12 from "fs";
3066
- import path13 from "path";
3679
+ import fs13 from "fs";
3680
+ import path15 from "path";
3067
3681
  function resolveEnv() {
3068
3682
  return process.env.NODE_ENV || "development";
3069
3683
  }
@@ -3130,9 +3744,9 @@ function loadEnv(rootDir) {
3130
3744
  const files = getEnvFiles(env);
3131
3745
  const merged = {};
3132
3746
  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");
3747
+ const filePath = path15.join(rootDir, file);
3748
+ if (!fs13.existsSync(filePath)) continue;
3749
+ const content = fs13.readFileSync(filePath, "utf-8");
3136
3750
  const parsed = parseEnvFile(content, merged);
3137
3751
  Object.assign(merged, parsed);
3138
3752
  }
@@ -3143,67 +3757,9 @@ function loadEnv(rootDir) {
3143
3757
  }
3144
3758
  }
3145
3759
 
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
3760
  // src/cli/createAppCore.ts
3205
- import fs17 from "fs";
3206
- import path20 from "path";
3761
+ import fs18 from "fs";
3762
+ import path23 from "path";
3207
3763
  import { PassThrough, Readable as Readable3 } from "stream";
3208
3764
 
3209
3765
  // src/router/sortRoutes.ts
@@ -3256,7 +3812,7 @@ import {
3256
3812
  import { createSecureServer as createHttp2SecureServer } from "http2";
3257
3813
  import { readFileSync } from "fs";
3258
3814
  import { Readable as Readable2 } from "stream";
3259
- import path15 from "path";
3815
+ import path17 from "path";
3260
3816
 
3261
3817
  // src/router/matchRoute.ts
3262
3818
  var httpIndexCache = /* @__PURE__ */ new WeakMap();
@@ -3298,18 +3854,18 @@ function getWsIndex(routes) {
3298
3854
  wsIndexCache.set(routes, index);
3299
3855
  return index;
3300
3856
  }
3301
- function matchRoute(routes, method, path24) {
3857
+ function matchRoute(routes, method, path28) {
3302
3858
  const index = getHttpIndex(routes);
3303
3859
  const upper = method.toUpperCase();
3304
- const hit = matchByMethod(index, upper, path24);
3860
+ const hit = matchByMethod(index, upper, path28);
3305
3861
  if (hit) return hit;
3306
3862
  if (upper === "HEAD") {
3307
- return matchByMethod(index, "GET", path24);
3863
+ return matchByMethod(index, "GET", path28);
3308
3864
  }
3309
3865
  return null;
3310
3866
  }
3311
- function matchByMethod(index, method, path24) {
3312
- const staticHit = index.static.get(`${method}|${path24}`);
3867
+ function matchByMethod(index, method, path28) {
3868
+ const staticHit = index.static.get(`${method}|${path28}`);
3313
3869
  if (staticHit) {
3314
3870
  return { route: staticHit, params: {} };
3315
3871
  }
@@ -3318,31 +3874,31 @@ function matchByMethod(index, method, path24) {
3318
3874
  if (route.method !== method) {
3319
3875
  continue;
3320
3876
  }
3321
- const params = matchSegments(entry.segments, path24, route.paramNames, route.isCatchAll);
3877
+ const params = matchSegments(entry.segments, path28, route.paramNames, route.isCatchAll);
3322
3878
  if (params !== null) {
3323
3879
  return { route, params };
3324
3880
  }
3325
3881
  }
3326
3882
  return null;
3327
3883
  }
3328
- function matchWsRoute(wsRoutes, path24) {
3884
+ function matchWsRoute(wsRoutes, path28) {
3329
3885
  const index = getWsIndex(wsRoutes);
3330
- const staticHit = index.static.get(path24);
3886
+ const staticHit = index.static.get(path28);
3331
3887
  if (staticHit) {
3332
3888
  return { route: staticHit, params: {} };
3333
3889
  }
3334
3890
  for (const route of index.dynamics) {
3335
- const params = matchDynamicPath(route.urlPath, path24, route.paramNames, route.isCatchAll);
3891
+ const params = matchDynamicPath(route.urlPath, path28, route.paramNames, route.isCatchAll);
3336
3892
  if (params !== null) {
3337
3893
  return { route, params };
3338
3894
  }
3339
3895
  }
3340
3896
  return null;
3341
3897
  }
3342
- function findAllowedMethods(routes, path24) {
3898
+ function findAllowedMethods(routes, path28) {
3343
3899
  const index = getHttpIndex(routes);
3344
3900
  const methods = /* @__PURE__ */ new Set();
3345
- const staticMethods = index.methodsByStaticPath.get(path24);
3901
+ const staticMethods = index.methodsByStaticPath.get(path28);
3346
3902
  if (staticMethods) {
3347
3903
  for (const method of staticMethods) {
3348
3904
  methods.add(method);
@@ -3351,7 +3907,7 @@ function findAllowedMethods(routes, path24) {
3351
3907
  for (const entry of index.dynamics) {
3352
3908
  const params = matchSegments(
3353
3909
  entry.segments,
3354
- path24,
3910
+ path28,
3355
3911
  entry.route.paramNames,
3356
3912
  entry.route.isCatchAll
3357
3913
  );
@@ -3364,11 +3920,11 @@ function findAllowedMethods(routes, path24) {
3364
3920
  }
3365
3921
  return Array.from(methods);
3366
3922
  }
3367
- function matchDynamicPath(pattern, path24, paramNames, isCatchAll) {
3368
- return matchSegments(pattern.split("/").filter(Boolean), path24, paramNames, isCatchAll);
3923
+ function matchDynamicPath(pattern, path28, paramNames, isCatchAll) {
3924
+ return matchSegments(pattern.split("/").filter(Boolean), path28, paramNames, isCatchAll);
3369
3925
  }
3370
- function matchSegments(patternSegments, path24, paramNames, isCatchAll) {
3371
- const pathSegments = path24.split("/").filter(Boolean);
3926
+ function matchSegments(patternSegments, path28, paramNames, isCatchAll) {
3927
+ const pathSegments = path28.split("/").filter(Boolean);
3372
3928
  if (isCatchAll) {
3373
3929
  const nonCatchAllCount = patternSegments.length - 1;
3374
3930
  if (pathSegments.length <= nonCatchAllCount) {
@@ -3773,6 +4329,7 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
3773
4329
  };
3774
4330
  if (registries) {
3775
4331
  ctx.registries = registries;
4332
+ ctx.tasks = registries.taskHandle.get(ctx);
3776
4333
  }
3777
4334
  const extend = config?.extendContext;
3778
4335
  if (typeof extend === "function") {
@@ -4062,6 +4619,9 @@ function getBuiltinInjectionValue(type, ctx, body) {
4062
4619
  // Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle
4063
4620
  case "agent":
4064
4621
  return ctx.registries ? ctx.registries.agentHandle.get(ctx) : getAgentHandle(ctx);
4622
+ // 任务子系统:注入 TaskClient(入队/查询);未注册工厂(无 app 编排)时 undefined
4623
+ case "tasks":
4624
+ return ctx.registries ? ctx.registries.taskHandle.get(ctx) : void 0;
4065
4625
  default:
4066
4626
  return void 0;
4067
4627
  }
@@ -4256,9 +4816,9 @@ async function validateInput(schemaPath, method, inputType, input) {
4256
4816
  function mapZodIssues(error) {
4257
4817
  return error.issues.map((issue) => {
4258
4818
  const code = mapZodCode(issue);
4259
- const path24 = issue.path.map(String).join(".") || "";
4819
+ const path28 = issue.path.map(String).join(".") || "";
4260
4820
  return {
4261
- path: path24,
4821
+ path: path28,
4262
4822
  code,
4263
4823
  expected: issue.expected ?? mapExpectedFromMessage(issue.message),
4264
4824
  received: issue.received ?? mapReceivedFromMessage(issue.message),
@@ -4490,9 +5050,9 @@ function etag(options = {}) {
4490
5050
  }
4491
5051
 
4492
5052
  // src/server/handleWsUpgrade.ts
4493
- import fs13 from "fs";
5053
+ import fs14 from "fs";
4494
5054
  import { WebSocketServer, WebSocket } from "ws";
4495
- import path14 from "path";
5055
+ import path16 from "path";
4496
5056
 
4497
5057
  // src/server/serverUtils.ts
4498
5058
  function nodeHttpToWebHeaders(req) {
@@ -4627,7 +5187,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
4627
5187
  const dist = getDevDist();
4628
5188
  if (dist) {
4629
5189
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
4630
- if (sourcePath && fs13.existsSync(sourcePath)) {
5190
+ if (sourcePath && fs14.existsSync(sourcePath)) {
4631
5191
  await ensureCompiled(sourcePath, rootDir, dist);
4632
5192
  }
4633
5193
  }
@@ -4741,7 +5301,7 @@ function attachWebSocket(options) {
4741
5301
  const finalHandler = async () => {
4742
5302
  let handlers;
4743
5303
  try {
4744
- const absoluteFilePath = path14.resolve(rootDir, route.filePath);
5304
+ const absoluteFilePath = path16.resolve(rootDir, route.filePath);
4745
5305
  handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
4746
5306
  } catch (err) {
4747
5307
  const reason = err instanceof Error ? err.message : String(err);
@@ -4994,7 +5554,7 @@ function getRoutePaths(route, rootDir, dist) {
4994
5554
  let cached = routePathCache.get(route);
4995
5555
  if (!cached) {
4996
5556
  cached = {
4997
- absFilePath: path15.resolve(rootDir, route.filePath),
5557
+ absFilePath: path17.resolve(rootDir, route.filePath),
4998
5558
  schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
4999
5559
  };
5000
5560
  routePathCache.set(route, cached);
@@ -5116,8 +5676,8 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
5116
5676
  }
5117
5677
 
5118
5678
  // src/cli/generateRoutes.ts
5119
- import fs14 from "fs";
5120
- import path16 from "path";
5679
+ import fs15 from "fs";
5680
+ import path18 from "path";
5121
5681
  async function hydrateRoutes(manifest) {
5122
5682
  const hydrateRoute = (serialized) => ({
5123
5683
  method: serialized.method,
@@ -5142,7 +5702,7 @@ async function hydrateRoutes(manifest) {
5142
5702
  }
5143
5703
 
5144
5704
  // src/cli/generateAgentArtifacts.ts
5145
- import path17 from "path";
5705
+ import path19 from "path";
5146
5706
 
5147
5707
  // src/ast/extractAgentMetadata.ts
5148
5708
  import ts10 from "typescript";
@@ -5415,9 +5975,9 @@ function validateAgentList(metadata) {
5415
5975
  }
5416
5976
  async function generateAgentArtifacts(agents, rootDir, dist) {
5417
5977
  const metadata = [];
5418
- const programByFile = createPrograms(agents.map((m) => path17.resolve(rootDir, m.filePath)));
5978
+ const programByFile = createPrograms(agents.map((m) => path19.resolve(rootDir, m.filePath)));
5419
5979
  for (const manifest of agents) {
5420
- const absPath = path17.resolve(rootDir, manifest.filePath);
5980
+ const absPath = path19.resolve(rootDir, manifest.filePath);
5421
5981
  const program = programByFile.get(absPath);
5422
5982
  const result = extractAgentMetadata(program, absPath, {
5423
5983
  name: manifest.name,
@@ -5433,24 +5993,202 @@ async function generateAgentArtifacts(agents, rootDir, dist) {
5433
5993
  }
5434
5994
  validateAgentList(metadata);
5435
5995
  const serialized = serializeAgents(metadata, dist);
5436
- const agentsPath = path17.resolve(rootDir, dist, AGENTS_FILE);
5996
+ const agentsPath = path19.resolve(rootDir, dist, AGENTS_FILE);
5437
5997
  await writeAgentsModule(serialized, agentsPath);
5438
5998
  return metadata;
5439
5999
  }
5440
6000
 
6001
+ // src/cli/generateTaskArtifacts.ts
6002
+ import path20 from "path";
6003
+ init_createProgram();
6004
+ init_atomicWrite();
6005
+ init_generateSchemaFiles();
6006
+ init_extractHandlerTypes();
6007
+ init_generateZodSchema();
6008
+ var TASKS_FILE = "faapi-tasks.js";
6009
+ function serializeTasks(tasks, dist = "dist") {
6010
+ return tasks.map((t) => ({
6011
+ name: t.name,
6012
+ filePath: toProdFilePath(t.filePath, dist),
6013
+ ...t.cron !== void 0 ? { cron: t.cron } : {},
6014
+ ...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
6015
+ ...t.retries !== void 0 ? { retries: t.retries } : {},
6016
+ ...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {}
6017
+ }));
6018
+ }
6019
+ async function writeTasksModule(manifest, outputPath) {
6020
+ const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
6021
+ export const tasks = ${JSON.stringify(manifest, null, 2)};
6022
+ `;
6023
+ await atomicWriteFile(outputPath, content);
6024
+ }
6025
+ function hydrateTasks(manifest) {
6026
+ return manifest.map((t) => ({
6027
+ name: t.name,
6028
+ filePath: t.filePath,
6029
+ cron: t.cron ?? void 0,
6030
+ concurrency: t.concurrency ?? void 0,
6031
+ retries: t.retries ?? void 0,
6032
+ timeoutMs: t.timeoutMs ?? void 0
6033
+ }));
6034
+ }
6035
+ function collectTaskSchemaSources(tasks, rootDir) {
6036
+ const tasksByFile = /* @__PURE__ */ new Map();
6037
+ for (const task of tasks) {
6038
+ if (!task.inputTypeName) continue;
6039
+ const absPath = path20.resolve(rootDir, task.filePath);
6040
+ let list = tasksByFile.get(absPath);
6041
+ if (!list) {
6042
+ list = [];
6043
+ tasksByFile.set(absPath, list);
6044
+ }
6045
+ list.push(task);
6046
+ }
6047
+ const programByFile = createPrograms([...tasksByFile.keys()]);
6048
+ const resolversByFile = /* @__PURE__ */ new Map();
6049
+ for (const filePath of tasksByFile.keys()) {
6050
+ resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath), filePath));
6051
+ }
6052
+ const sources = [];
6053
+ for (const [filePath, fileTasks] of tasksByFile) {
6054
+ const program = programByFile.get(filePath);
6055
+ for (const task of fileTasks) {
6056
+ const typeInfo = extractTypeInfo(program, filePath, task.inputTypeName);
6057
+ sources.push({
6058
+ name: task.taskName,
6059
+ filePath,
6060
+ schemaName: task.inputTypeName,
6061
+ typeInfo
6062
+ });
6063
+ }
6064
+ }
6065
+ return { sources, resolversByFile };
6066
+ }
6067
+ function generateTaskSchemaFileSource(sources, resolveType, helpersImportPath) {
6068
+ const lines = ["import { z } from 'zod';"];
6069
+ const schemaBlocks = [];
6070
+ for (const source of sources) {
6071
+ if (!source.typeInfo) continue;
6072
+ const block = [`// task ${source.name} \u2192 ${source.schemaName}`];
6073
+ const schemaCode = generateZodSchemaSource(
6074
+ source.typeInfo,
6075
+ resolveType,
6076
+ source.schemaName,
6077
+ false
6078
+ ).replace(/^import \{ z \} from 'zod';\s*\n\s*\n/, "");
6079
+ block.push(schemaCode);
6080
+ block.push("");
6081
+ schemaBlocks.push(block.join("\n"));
6082
+ }
6083
+ const allSchemaCode = schemaBlocks.join("\n");
6084
+ if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
6085
+ lines.push(
6086
+ `import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
6087
+ );
6088
+ }
6089
+ lines.push("");
6090
+ lines.push(...schemaBlocks);
6091
+ return lines.join("\n").replace(/\n+$/, "\n");
6092
+ }
6093
+ async function generateTaskArtifacts(manifests, rootDir, dist) {
6094
+ const metadata = [];
6095
+ if (manifests.length > 0) {
6096
+ const programByFile = createPrograms(manifests.map((m) => path20.resolve(rootDir, m.filePath)));
6097
+ for (const manifest of manifests) {
6098
+ const absPath = path20.resolve(rootDir, manifest.filePath);
6099
+ const result = extractToolMetadata(programByFile.get(absPath), absPath, "run", {
6100
+ name: manifest.name,
6101
+ filePath: manifest.filePath
6102
+ });
6103
+ if (result) {
6104
+ metadata.push({ ...result, taskName: manifest.name });
6105
+ }
6106
+ }
6107
+ }
6108
+ const serialized = serializeTasks(manifests, dist);
6109
+ const tasksPath = path20.resolve(rootDir, dist, TASKS_FILE);
6110
+ await writeTasksModule(serialized, tasksPath);
6111
+ if (metadata.length === 0) {
6112
+ return hydrateTasks(serialized);
6113
+ }
6114
+ const { sources, resolversByFile } = collectTaskSchemaSources(metadata, rootDir);
6115
+ if (sources.length === 0) {
6116
+ return hydrateTasks(serialized);
6117
+ }
6118
+ const sourcesByFile = /* @__PURE__ */ new Map();
6119
+ for (const source of sources) {
6120
+ let list = sourcesByFile.get(source.filePath);
6121
+ if (!list) {
6122
+ list = [];
6123
+ sourcesByFile.set(source.filePath, list);
6124
+ }
6125
+ list.push(source);
6126
+ }
6127
+ const fileEntries = [];
6128
+ for (const [filePath, fileSources] of sourcesByFile) {
6129
+ const relFile = path20.relative(rootDir, filePath).replace(/\\/g, "/");
6130
+ const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
6131
+ const resolver = resolversByFile.get(filePath);
6132
+ let relForDir = relFile;
6133
+ if (relForDir.startsWith("src/")) {
6134
+ relForDir = relForDir.slice(4);
6135
+ }
6136
+ const dirIdx = relForDir.lastIndexOf("/");
6137
+ const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
6138
+ const helpersImportPath = getHelpersImportPath(zodRelDir);
6139
+ const source = generateTaskSchemaFileSource(
6140
+ fileSources,
6141
+ (name) => resolver?.resolve(name)?.runtimeType,
6142
+ helpersImportPath
6143
+ );
6144
+ fileEntries.push({ outputPath, source });
6145
+ }
6146
+ const allSourceCode = fileEntries.map((e) => e.source).join("\n");
6147
+ if (usesCoerceHelpers(allSourceCode)) {
6148
+ const helpersPath = path20.resolve(rootDir, dist, HELPERS_FILENAME);
6149
+ const { existsSync: existsSync3 } = await import("fs");
6150
+ if (!existsSync3(helpersPath)) {
6151
+ await atomicWriteFile(helpersPath, generateHelpersFileSource());
6152
+ }
6153
+ }
6154
+ await Promise.all(
6155
+ fileEntries.map(({ outputPath, source }) => atomicWriteFile(outputPath, source))
6156
+ );
6157
+ return hydrateTasks(serialized);
6158
+ }
6159
+
6160
+ // src/task/idleTaskDriver.ts
6161
+ function createIdleTaskDriver() {
6162
+ return {
6163
+ enqueue() {
6164
+ return Promise.reject(
6165
+ new Error(
6166
+ "[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."
6167
+ )
6168
+ );
6169
+ },
6170
+ startWorker() {
6171
+ },
6172
+ async stop() {
6173
+ },
6174
+ async stopWorkers() {
6175
+ }
6176
+ };
6177
+ }
6178
+
5441
6179
  // src/cli/loadPlugins.ts
5442
- import path19 from "path";
5443
- import fs16 from "fs";
5444
- import { pathToFileURL as pathToFileURL2 } from "url";
6180
+ import path22 from "path";
6181
+ import fs17 from "fs";
6182
+ import { pathToFileURL as pathToFileURL3 } from "url";
5445
6183
 
5446
6184
  // src/cli/compileConfig.ts
5447
- import path18 from "path";
5448
- import fs15 from "fs";
6185
+ import path21 from "path";
6186
+ import fs16 from "fs";
5449
6187
  async function compileProjectModules(entryPoints, rootDir, dist) {
5450
6188
  const { insideFiles, outsideFiles } = await collectRelativeImports(entryPoints, rootDir);
5451
6189
  const esbuild = await import("esbuild");
5452
6190
  const aliasPlugins = buildAliasPlugins(rootDir);
5453
- const absDist = path18.resolve(rootDir, dist);
6191
+ const absDist = path21.resolve(rootDir, dist);
5454
6192
  await esbuild.build({
5455
6193
  entryPoints: [...entryPoints, ...outsideFiles],
5456
6194
  outdir: absDist,
@@ -5464,7 +6202,7 @@ async function compileProjectModules(entryPoints, rootDir, dist) {
5464
6202
  logLevel: "silent"
5465
6203
  });
5466
6204
  if (insideFiles.length > 0) {
5467
- const appOutbase = path18.resolve(rootDir, "src");
6205
+ const appOutbase = path21.resolve(rootDir, "src");
5468
6206
  await esbuild.build({
5469
6207
  entryPoints: insideFiles,
5470
6208
  outdir: absDist,
@@ -5545,38 +6283,38 @@ async function loadPlugins(declarations, ctx, rootDir, dist) {
5545
6283
  var TS_EXTS = /\.(ts|tsx|mts|cts)$/;
5546
6284
  var JS_EXTS = /\.(js|mjs|cjs|jsx)$/;
5547
6285
  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;
6286
+ const base = path22.isAbsolute(specifier) ? specifier : path22.resolve(baseDir, specifier);
6287
+ if ((TS_EXTS.test(base) || JS_EXTS.test(base)) && fs17.existsSync(base)) return base;
5550
6288
  for (const ext of [".ts", ".js"]) {
5551
- if (fs16.existsSync(base + ext)) return base + ext;
6289
+ if (fs17.existsSync(base + ext)) return base + ext;
5552
6290
  }
5553
6291
  for (const indexExt of ["/index.ts", "/index.js"]) {
5554
- if (fs16.existsSync(base + indexExt)) return base + indexExt;
6292
+ if (fs17.existsSync(base + indexExt)) return base + indexExt;
5555
6293
  }
5556
6294
  return null;
5557
6295
  }
5558
6296
  function mirrorProductPath(sourcePath, baseDir, distDir) {
5559
- let rel = path19.relative(baseDir, sourcePath).replace(/\\/g, "/");
6297
+ let rel = path22.relative(baseDir, sourcePath).replace(/\\/g, "/");
5560
6298
  if (rel.startsWith("src/")) rel = rel.slice(4);
5561
- return path19.resolve(distDir, rel.replace(TS_EXTS, ".js"));
6299
+ return path22.resolve(distDir, rel.replace(TS_EXTS, ".js"));
5562
6300
  }
5563
6301
  function isSourceNewer(sourcePath, productPath) {
5564
6302
  try {
5565
- return fs16.statSync(sourcePath).mtimeMs > fs16.statSync(productPath).mtimeMs;
6303
+ return fs17.statSync(sourcePath).mtimeMs > fs17.statSync(productPath).mtimeMs;
5566
6304
  } catch {
5567
6305
  return true;
5568
6306
  }
5569
6307
  }
5570
6308
  async function importPluginModule(specifier, baseDir, dist) {
5571
6309
  const isRelative = specifier.startsWith("./") || specifier.startsWith("../");
5572
- if (!isRelative && !path19.isAbsolute(specifier)) {
6310
+ if (!isRelative && !path22.isAbsolute(specifier)) {
5573
6311
  return import(specifier);
5574
6312
  }
5575
- const distDir = dist ? path19.resolve(baseDir, dist) : null;
6313
+ const distDir = dist ? path22.resolve(baseDir, dist) : null;
5576
6314
  const sourcePath = resolveLocalPluginSource(specifier, baseDir);
5577
6315
  const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
5578
- if (productPath !== null && fs16.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
5579
- return import(pathToFileURL2(productPath).href);
6316
+ if (productPath !== null && fs17.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
6317
+ return import(pathToFileURL3(productPath).href);
5580
6318
  }
5581
6319
  if (sourcePath && TS_EXTS.test(sourcePath)) {
5582
6320
  if (!isDevOnDemandEnabled() || !distDir) {
@@ -5584,25 +6322,25 @@ async function importPluginModule(specifier, baseDir, dist) {
5584
6322
  `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
6323
  );
5586
6324
  }
5587
- const relFromRoot = path19.relative(baseDir, sourcePath).replace(/\\/g, "/");
6325
+ const relFromRoot = path22.relative(baseDir, sourcePath).replace(/\\/g, "/");
5588
6326
  if (relFromRoot.startsWith("src/")) {
5589
6327
  await ensureCompiled(sourcePath, baseDir, dist);
5590
6328
  } else {
5591
6329
  await compileProjectModules([sourcePath], baseDir, dist);
5592
6330
  }
5593
- if (productPath && fs16.existsSync(productPath)) {
5594
- return import(pathToFileURL2(productPath).href);
6331
+ if (productPath && fs17.existsSync(productPath)) {
6332
+ return import(pathToFileURL3(productPath).href);
5595
6333
  }
5596
6334
  }
5597
6335
  if (sourcePath && JS_EXTS.test(sourcePath)) {
5598
- return import(pathToFileURL2(sourcePath).href);
6336
+ return import(pathToFileURL3(sourcePath).href);
5599
6337
  }
5600
6338
  const candidates = [
5601
6339
  `${specifier}.ts`,
5602
6340
  `${specifier}/index.ts`,
5603
6341
  `${specifier}.js`,
5604
6342
  `${specifier}/index.js`
5605
- ].map((c) => path19.isAbsolute(c) ? c : path19.join(baseDir, c));
6343
+ ].map((c) => path22.isAbsolute(c) ? c : path22.join(baseDir, c));
5606
6344
  throw new Error(
5607
6345
  `Cannot find local plugin "${specifier}" (resolved from ${baseDir}). Expected one of:
5608
6346
  ${candidates.join("\n ")}
@@ -5633,8 +6371,8 @@ var ROUTES_FILE = "faapi-routes.js";
5633
6371
  var TOOLS_FILE2 = "faapi-tools.js";
5634
6372
  var AGENTS_FILE2 = "faapi-agents.js";
5635
6373
  async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries) {
5636
- const toolsPath = path20.resolve(rootDir, dist, TOOLS_FILE2);
5637
- if (!fs17.existsSync(toolsPath)) {
6374
+ const toolsPath = path23.resolve(rootDir, dist, TOOLS_FILE2);
6375
+ if (!fs18.existsSync(toolsPath)) {
5638
6376
  return [];
5639
6377
  }
5640
6378
  const serialized = await importWithCacheBust(toolsPath);
@@ -5643,8 +6381,8 @@ async function loadAndHydrateTools(rootDir, dist, registries = defaultRegistries
5643
6381
  return hydrated;
5644
6382
  }
5645
6383
  async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistries) {
5646
- const agentsPath = path20.resolve(rootDir, dist, AGENTS_FILE2);
5647
- if (!fs17.existsSync(agentsPath)) {
6384
+ const agentsPath = path23.resolve(rootDir, dist, AGENTS_FILE2);
6385
+ if (!fs18.existsSync(agentsPath)) {
5648
6386
  return [];
5649
6387
  }
5650
6388
  const serialized = await importWithCacheBust(agentsPath);
@@ -5652,6 +6390,22 @@ async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistrie
5652
6390
  registries.agent.hydrate(hydrated);
5653
6391
  return hydrated;
5654
6392
  }
6393
+ function getTaskDriverOptions(config) {
6394
+ if (!config?.task) return void 0;
6395
+ if (config.task.driver === "pgboss") return config.task.pgboss;
6396
+ if (config.task.driver === "bullmq") return config.task.bullmq;
6397
+ return void 0;
6398
+ }
6399
+ async function loadAndHydrateTasks(rootDir, dist, registries = defaultRegistries) {
6400
+ const tasksPath = path23.resolve(rootDir, dist, TASKS_FILE);
6401
+ if (!fs18.existsSync(tasksPath)) {
6402
+ return [];
6403
+ }
6404
+ const serialized = await importWithCacheBust(tasksPath);
6405
+ const hydrated = hydrateTasks(serialized.tasks ?? []);
6406
+ registries.task.hydrate(hydrated);
6407
+ return hydrated;
6408
+ }
5655
6409
  var APP_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("faapi.app.instance");
5656
6410
  function getCurrentApp() {
5657
6411
  return globalThis[APP_INSTANCE_KEY] ?? null;
@@ -5703,7 +6457,8 @@ var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
5703
6457
  "logger",
5704
6458
  "http2",
5705
6459
  "trustedProxy",
5706
- "response"
6460
+ "response",
6461
+ "task"
5707
6462
  ]);
5708
6463
  function isFaapiConfigKey(key) {
5709
6464
  return FAAPI_CONFIG_KEYS.has(key);
@@ -5711,8 +6466,8 @@ function isFaapiConfigKey(key) {
5711
6466
  async function createAppBase(options) {
5712
6467
  const rootDir = options?.rootDir ?? process.cwd();
5713
6468
  const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
5714
- const routesPath = path20.resolve(rootDir, dist, ROUTES_FILE);
5715
- if (!fs17.existsSync(routesPath)) {
6469
+ const routesPath = path23.resolve(rootDir, dist, ROUTES_FILE);
6470
+ if (!fs18.existsSync(routesPath)) {
5716
6471
  throw new Error(
5717
6472
  `[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
6473
  );
@@ -5734,6 +6489,29 @@ async function createAppBase(options) {
5734
6489
  const registries = createAppRegistries();
5735
6490
  const tools = await loadAndHydrateTools(rootDir, dist, registries);
5736
6491
  const agents = await loadAndHydrateAgents(rootDir, dist, registries);
6492
+ const taskMetas = await loadAndHydrateTasks(rootDir, dist, registries);
6493
+ const taskDriver = taskMetas.length ? await loadTaskDriver(config?.task?.driver, getTaskDriverOptions(config)) : createIdleTaskDriver();
6494
+ const taskQueue = createTaskQueue({
6495
+ registry: registries.task,
6496
+ rootDir,
6497
+ config,
6498
+ driver: taskDriver,
6499
+ onFailed: config?.task?.onFailed
6500
+ });
6501
+ const cronScheduler = createCronScheduler(
6502
+ registries.task,
6503
+ (name, opts) => taskQueue.enqueue(name, void 0, opts)
6504
+ );
6505
+ const taskEnabled = process.env.FAAPI_TASKS_DISABLED === "1" ? false : config?.task?.enabled ?? true;
6506
+ const stopTaskRuntime = async () => {
6507
+ cronScheduler.stop();
6508
+ await taskQueue.stop(config?.task?.shutdownTimeoutMs ?? 1e4);
6509
+ };
6510
+ if (taskEnabled) {
6511
+ taskQueue.start();
6512
+ cronScheduler.start();
6513
+ }
6514
+ registries.taskHandle.register(() => taskQueue);
5737
6515
  const pluginConfig = config ? Object.fromEntries(Object.entries(config).filter(([k]) => !isFaapiConfigKey(k))) : {};
5738
6516
  const { server, routesRef } = createServer({
5739
6517
  routes: sorted,
@@ -5775,14 +6553,22 @@ async function createAppBase(options) {
5775
6553
  routes: sorted,
5776
6554
  wsRoutes,
5777
6555
  rootDir,
6556
+ tasks: taskQueue,
5778
6557
  async listen(listenPort) {
5779
6558
  const envPort = process.env.PORT ? Number(process.env.PORT) : void 0;
5780
6559
  const actualPort = listenPort ?? options?.port ?? envPort ?? DEFAULT_PORT;
5781
6560
  if (config?.lifecycle?.onBoot) {
5782
6561
  try {
5783
- await config.lifecycle.onBoot({ rootDir, routes: sorted, server, registries });
6562
+ await config.lifecycle.onBoot({
6563
+ rootDir,
6564
+ routes: sorted,
6565
+ server,
6566
+ registries,
6567
+ tasks: taskQueue
6568
+ });
5784
6569
  console.log("- onBoot hook executed");
5785
6570
  } catch (err) {
6571
+ await stopTaskRuntime();
5786
6572
  const message = err instanceof Error ? err.message : String(err);
5787
6573
  console.error(`[faapi] onBoot hook failed: ${message}`);
5788
6574
  throw err;
@@ -5830,9 +6616,22 @@ async function createAppBase(options) {
5830
6616
  console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
5831
6617
  }
5832
6618
  }
6619
+ if (taskMetas.length > 0) {
6620
+ console.log(`- Loaded ${taskMetas.length} task(s):`);
6621
+ for (const taskMeta of taskMetas) {
6622
+ const schedule = taskMeta.cron ? ` cron=${taskMeta.cron}` : "";
6623
+ console.log(` ${taskMeta.name}${schedule} ${taskMeta.filePath}`);
6624
+ }
6625
+ }
5833
6626
  registerDefaultShutdownHandlers();
5834
6627
  if (config?.lifecycle?.onReady) {
5835
- await config.lifecycle.onReady({ rootDir, routes: sorted, server, registries });
6628
+ await config.lifecycle.onReady({
6629
+ rootDir,
6630
+ routes: sorted,
6631
+ server,
6632
+ registries,
6633
+ tasks: taskQueue
6634
+ });
5836
6635
  console.log("- onReady hook executed");
5837
6636
  }
5838
6637
  app.server = server;
@@ -5925,13 +6724,22 @@ async function createAppBase(options) {
5925
6724
  closed = true;
5926
6725
  const s = server;
5927
6726
  s.closeIdleConnections?.();
6727
+ await stopTaskRuntime();
5928
6728
  if (config?.lifecycle?.onClose) {
5929
- await config.lifecycle.onClose({ rootDir, routes: sorted, server, registries });
6729
+ await config.lifecycle.onClose({
6730
+ rootDir,
6731
+ routes: sorted,
6732
+ server,
6733
+ registries,
6734
+ tasks: taskQueue
6735
+ });
5930
6736
  }
5931
6737
  registries.tool.clear();
5932
6738
  registries.agent.clear();
5933
6739
  registries.skill.clear();
6740
+ registries.task.clear();
5934
6741
  registries.agentHandle.clear();
6742
+ registries.taskHandle.clear();
5935
6743
  if (!server.listening) {
5936
6744
  app.server = null;
5937
6745
  if (getCurrentApp() === app) setCurrentApp(null);
@@ -5968,6 +6776,7 @@ async function createAppBase(options) {
5968
6776
  registries,
5969
6777
  dist,
5970
6778
  patterns: ROUTE_PATTERNS,
6779
+ taskQueue,
5971
6780
  server,
5972
6781
  routesRef,
5973
6782
  config,
@@ -5983,19 +6792,22 @@ async function createAppBase(options) {
5983
6792
  return { app, ctx };
5984
6793
  }
5985
6794
 
6795
+ // src/cli/createDevApp.ts
6796
+ import path27 from "path";
6797
+
5986
6798
  // src/router/scanRoutes.ts
5987
- import fg2 from "fast-glob";
5988
- import path21 from "path";
5989
- import fs18 from "fs";
6799
+ import fg3 from "fast-glob";
6800
+ import path24 from "path";
6801
+ import fs19 from "fs";
5990
6802
 
5991
6803
  // src/router/constants.ts
5992
6804
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
5993
6805
  var HTTP_METHOD_SET = new Set(HTTP_METHODS);
5994
6806
 
5995
6807
  // src/utils/normalizePath.ts
5996
- function normalizePath(path24) {
5997
- if (!path24) return "";
5998
- let result = path24.replace(/\\/g, "/");
6808
+ function normalizePath(path28) {
6809
+ if (!path28) return "";
6810
+ let result = path28.replace(/\\/g, "/");
5999
6811
  result = result.replace(/\/+/g, "/");
6000
6812
  result = result.replace(/\/+$/, "");
6001
6813
  if (result && !result.startsWith("/")) {
@@ -6056,34 +6868,34 @@ function extractExportsFromSource(source) {
6056
6868
  return names;
6057
6869
  }
6058
6870
  function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
6059
- const routeDir = path21.dirname(routeFilePath);
6060
- const resolvedRoot = path21.resolve(rootDir);
6871
+ const routeDir = path24.dirname(routeFilePath);
6872
+ const resolvedRoot = path24.resolve(rootDir);
6061
6873
  const paths = [];
6062
- let currentDir = path21.resolve(rootDir, routeDir);
6874
+ let currentDir = path24.resolve(rootDir, routeDir);
6063
6875
  while (true) {
6064
6876
  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;
6877
+ const mwTsPath = path24.join(currentDir, "middlewares.ts");
6878
+ const mwJsPath = path24.join(currentDir, "middlewares.js");
6879
+ const absTsPath = path24.resolve(rootDir, mwTsPath);
6880
+ const absJsPath = path24.resolve(rootDir, mwJsPath);
6881
+ const absMwPath = fs19.existsSync(absTsPath) ? absTsPath : fs19.existsSync(absJsPath) ? absJsPath : null;
6070
6882
  if (absMwPath) {
6071
- const relMwPath = path21.relative(rootDir, absMwPath);
6072
- const prodAbsPath = path21.resolve(rootDir, toProdFilePath(relMwPath, dist));
6883
+ const relMwPath = path24.relative(rootDir, absMwPath);
6884
+ const prodAbsPath = path24.resolve(rootDir, toProdFilePath(relMwPath, dist));
6073
6885
  paths.push(prodAbsPath);
6074
6886
  }
6075
6887
  } else {
6076
6888
  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)) {
6889
+ const mwPath = path24.join(currentDir, `middlewares${ext}`);
6890
+ const absMwPath = path24.resolve(rootDir, mwPath);
6891
+ if (fs19.existsSync(absMwPath)) {
6080
6892
  paths.push(absMwPath);
6081
6893
  break;
6082
6894
  }
6083
6895
  }
6084
6896
  }
6085
6897
  if (currentDir === resolvedRoot) break;
6086
- const parentDir = path21.dirname(currentDir);
6898
+ const parentDir = path24.dirname(currentDir);
6087
6899
  if (parentDir === currentDir) break;
6088
6900
  currentDir = parentDir;
6089
6901
  }
@@ -6091,7 +6903,7 @@ function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
6091
6903
  return paths;
6092
6904
  }
6093
6905
  async function scanRoutes(rootDir, patterns, dist) {
6094
- const files = await fg2(patterns, {
6906
+ const files = await fg3(patterns, {
6095
6907
  cwd: rootDir,
6096
6908
  onlyFiles: true,
6097
6909
  absolute: false
@@ -6102,7 +6914,7 @@ async function scanRoutes(rootDir, patterns, dist) {
6102
6914
  const normalizedFile = file.replace(/\\/g, "/");
6103
6915
  const fileName = normalizedFile.split("/").pop();
6104
6916
  if (fileName === "handler.ts" || fileName === "handler.js") {
6105
- const absPath = path21.resolve(rootDir, normalizedFile);
6917
+ const absPath = path24.resolve(rootDir, normalizedFile);
6106
6918
  const urlPath = filePathToUrlPath(normalizedFile);
6107
6919
  const paramNames = extractParamNames(urlPath);
6108
6920
  const isDynamic = paramNames.length > 0;
@@ -6115,7 +6927,7 @@ async function scanRoutes(rootDir, patterns, dist) {
6115
6927
  const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
6116
6928
  middlewareBundle = await loadMergedMiddlewares(mwPaths);
6117
6929
  }
6118
- const source = await fs18.promises.readFile(absPath, "utf8").catch(() => "");
6930
+ const source = await fs19.promises.readFile(absPath, "utf8").catch(() => "");
6119
6931
  const exportNames = extractExportsFromSource(source);
6120
6932
  const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
6121
6933
  for (const method of methods) {
@@ -6150,9 +6962,9 @@ async function scanRoutes(rootDir, patterns, dist) {
6150
6962
  }
6151
6963
 
6152
6964
  // src/tools/scanTools.ts
6153
- import fg3 from "fast-glob";
6154
- import path22 from "path";
6155
- import fs19 from "fs";
6965
+ import fg4 from "fast-glob";
6966
+ import path25 from "path";
6967
+ import fs20 from "fs";
6156
6968
  var TOOL_PATTERNS = ["src/tools/**/*.ts"];
6157
6969
  var TOOL_EXPORT_RE = new RegExp(
6158
6970
  String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
@@ -6189,7 +7001,7 @@ function buildToolName(namespace, functionName) {
6189
7001
  return namespace ? `${namespace}.${functionName}` : functionName;
6190
7002
  }
6191
7003
  async function scanTools(rootDir, patterns) {
6192
- const files = await fg3(patterns, {
7004
+ const files = await fg4(patterns, {
6193
7005
  cwd: rootDir,
6194
7006
  onlyFiles: true,
6195
7007
  absolute: false
@@ -6202,8 +7014,8 @@ async function scanTools(rootDir, patterns) {
6202
7014
  if (fileName !== "handler.ts" && fileName !== "handler.js") {
6203
7015
  continue;
6204
7016
  }
6205
- const absPath = path22.resolve(rootDir, normalizedFile);
6206
- const source = await fs19.promises.readFile(absPath, "utf8").catch(() => "");
7017
+ const absPath = path25.resolve(rootDir, normalizedFile);
7018
+ const source = await fs20.promises.readFile(absPath, "utf8").catch(() => "");
6207
7019
  const exportNames = extractToolExportsFromSource(source);
6208
7020
  const namespace = filePathToToolNamespace(normalizedFile);
6209
7021
  for (const fnName of exportNames) {
@@ -6226,9 +7038,9 @@ async function scanTools(rootDir, patterns) {
6226
7038
  }
6227
7039
 
6228
7040
  // src/agents/scanAgents.ts
6229
- import fg4 from "fast-glob";
6230
- import path23 from "path";
6231
- import fs20 from "fs";
7041
+ import fg5 from "fast-glob";
7042
+ import path26 from "path";
7043
+ import fs21 from "fs";
6232
7044
  var DEFAULT_AGENT_PATTERNS = ["src/agents/**/handler.ts"];
6233
7045
  var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
6234
7046
  function extractAgentNameFromPath(filePath) {
@@ -6247,7 +7059,7 @@ function detectAgentExports(source) {
6247
7059
  };
6248
7060
  }
6249
7061
  async function scanAgents(rootDir, patterns) {
6250
- const files = await fg4(patterns, {
7062
+ const files = await fg5(patterns, {
6251
7063
  cwd: rootDir,
6252
7064
  onlyFiles: true,
6253
7065
  absolute: false
@@ -6260,8 +7072,8 @@ async function scanAgents(rootDir, patterns) {
6260
7072
  if (fileName !== "handler.ts" && fileName !== "handler.js") {
6261
7073
  continue;
6262
7074
  }
6263
- const absPath = path23.resolve(rootDir, normalizedFile);
6264
- const source = await fs20.promises.readFile(absPath, "utf8").catch(() => "");
7075
+ const absPath = path26.resolve(rootDir, normalizedFile);
7076
+ const source = await fs21.promises.readFile(absPath, "utf8").catch(() => "");
6265
7077
  const { hasRun } = detectAgentExports(source);
6266
7078
  const name = extractAgentNameFromPath(normalizedFile);
6267
7079
  const prevFile = seen.get(name);
@@ -6326,6 +7138,19 @@ async function createDevApp(options) {
6326
7138
  await generateAgentArtifacts(agents, ctx.rootDir, ctx.dist);
6327
7139
  await loadAndHydrateAgents(ctx.rootDir, ctx.dist, ctx.registries);
6328
7140
  };
7141
+ devApp.reloadTasks = async () => {
7142
+ setLoadTimestamp(Date.now());
7143
+ invalidateProgramCache();
7144
+ const tasks = await scanTasks(ctx.rootDir, TASK_PATTERNS);
7145
+ for (const task of tasks) {
7146
+ await ensureCompiled(path27.resolve(ctx.rootDir, task.filePath), ctx.rootDir, ctx.dist);
7147
+ }
7148
+ await generateTaskArtifacts(tasks, ctx.rootDir, ctx.dist);
7149
+ ctx.taskQueue.invalidateModules();
7150
+ ctx.taskQueue.invalidateSchemas();
7151
+ await ctx.taskQueue.reload();
7152
+ await loadAndHydrateTasks(ctx.rootDir, ctx.dist, ctx.registries);
7153
+ };
6329
7154
  return devApp;
6330
7155
  }
6331
7156
 
@@ -6341,16 +7166,20 @@ export {
6341
7166
  ModuleLoadError,
6342
7167
  RouteNotFoundError,
6343
7168
  SchemaExtractionError,
7169
+ TASK_PATTERNS,
6344
7170
  ValidationError,
6345
7171
  clearAgentHandleFactory,
6346
7172
  collectRouteSchemaSources,
6347
7173
  cors,
6348
7174
  createProdApp as createApp,
6349
7175
  createAppRegistries,
7176
+ createCronScheduler,
6350
7177
  createDevApp,
6351
7178
  createProdApp,
6352
7179
  createProgram,
6353
7180
  createPrograms,
7181
+ createTaskQueue,
7182
+ createTaskRegistry,
6354
7183
  extractTypeInfo,
6355
7184
  getAgent,
6356
7185
  getAgentEntry,
@@ -6366,6 +7195,7 @@ export {
6366
7195
  loadAgentModule,
6367
7196
  loadConfig,
6368
7197
  loadEnv,
7198
+ loadTaskDriver,
6369
7199
  loadToolModule,
6370
7200
  loadToolSchema,
6371
7201
  logger,
@@ -6374,6 +7204,7 @@ export {
6374
7204
  resolveAgentTools,
6375
7205
  resolveSubAgents,
6376
7206
  resolveTypeNode,
7207
+ scanTasks,
6377
7208
  upsertSkill
6378
7209
  };
6379
7210
  //# sourceMappingURL=index.js.map